Skip to content

Commit 8cf2c2e

Browse files
Pigbibiclaude
andcommitted
perf: stale-while-revalidate cache for /api/config
- Extend cache TTL to 2 min fresh / 10 min stale - Always return cached data immediately (even if expired) - Background refresh via ctx.waitUntil() when cache is stale - Never block the user on GitHub API after first cache population - Add _memRefreshing guard to prevent concurrent background refreshes User always gets < 50ms response after initial cache population. Cache self-heals in background without user-facing latency. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 012e707 commit 8cf2c2e

1 file changed

Lines changed: 47 additions & 13 deletions

File tree

web/strategy-switch-console/worker.js

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ const AUDIT_LOG_KEY = "audit_log";
1313
const AUDIT_LOG_LIMIT = 50;
1414
const CURRENT_STRATEGIES_TIMEOUT_MS = 25000;
1515
const CURRENT_STRATEGIES_CACHE_KEY = "current_strategies_cache";
16-
const CURRENT_STRATEGIES_CACHE_TTL_MS = 60_000;
16+
const CURRENT_STRATEGIES_CACHE_TTL_MS = 120_000; // 2 min — return cached data without refresh
17+
const CURRENT_STRATEGIES_STALE_TTL_MS = 600_000; // 10 min — return stale + background refresh
1718
const GITHUB_API_TIMEOUT_MS = 8000;
1819

1920
const SUPPORTED_PLATFORMS = ["longbridge", "ibkr", "schwab", "firstrade", "qmt", "binance"];
@@ -576,10 +577,9 @@ async function loadPlatformMeta() {
576577
}
577578

578579
// In-memory cache for the lifetime of this Worker isolate.
579-
// Falls back to KV on cold starts; KV write uses ctx.waitUntil
580-
// so it survives past the response.
581580
let _memCurrentStrategies = null;
582581
let _memCurrentStrategiesTs = 0;
582+
let _memRefreshing = false; // prevent concurrent background refreshes
583583

584584
async function configPayload(request, env, ctx) {
585585
const session = await readSession(request, env);
@@ -589,23 +589,57 @@ async function configPayload(request, env, ctx) {
589589
const strategyProfiles = await loadStrategyProfilesConfig(env);
590590

591591
let currentStrategies = null;
592-
593-
// 1) In-memory cache (fastest — same Worker isolate)
594-
if (_memCurrentStrategies && (Date.now() - _memCurrentStrategiesTs) < CURRENT_STRATEGIES_CACHE_TTL_MS) {
595-
currentStrategies = _memCurrentStrategies;
592+
let cacheFresh = false;
593+
594+
// 1) In-memory cache
595+
if (_memCurrentStrategies) {
596+
const age = Date.now() - _memCurrentStrategiesTs;
597+
if (age < CURRENT_STRATEGIES_CACHE_TTL_MS) {
598+
currentStrategies = _memCurrentStrategies;
599+
cacheFresh = true;
600+
} else if (age < CURRENT_STRATEGIES_STALE_TTL_MS) {
601+
currentStrategies = _memCurrentStrategies;
602+
// stale — trigger background refresh below
603+
}
596604
}
597605

598-
// 2) KV cache (survives cold starts)
606+
// 2) KV cache
599607
if (!currentStrategies && hasConfigStore(env)) {
600608
const cached = await readConfigJson(env, CURRENT_STRATEGIES_CACHE_KEY);
601-
if (cached?.ts && (Date.now() - cached.ts) < CURRENT_STRATEGIES_CACHE_TTL_MS && cached.data) {
602-
currentStrategies = cached.data;
603-
_memCurrentStrategies = cached.data;
604-
_memCurrentStrategiesTs = cached.ts;
609+
if (cached?.ts && cached.data) {
610+
const age = Date.now() - cached.ts;
611+
if (age < CURRENT_STRATEGIES_CACHE_TTL_MS) {
612+
currentStrategies = cached.data;
613+
cacheFresh = true;
614+
} else if (age < CURRENT_STRATEGIES_STALE_TTL_MS) {
615+
currentStrategies = cached.data;
616+
// stale — trigger background refresh below
617+
}
618+
if (currentStrategies && !_memCurrentStrategies) {
619+
_memCurrentStrategies = currentStrategies;
620+
_memCurrentStrategiesTs = cached.ts;
621+
}
605622
}
606623
}
607624

608-
// 3) Cache miss — fetch from GitHub
625+
// 3) Background refresh when stale (return old data immediately)
626+
if (currentStrategies && !cacheFresh && !_memRefreshing && hasConfigStore(env) && ctx) {
627+
_memRefreshing = true;
628+
ctx.waitUntil((async () => {
629+
try {
630+
const fresh = await loadCurrentStrategiesSafely(accountConfig.options, env);
631+
_memCurrentStrategies = fresh;
632+
_memCurrentStrategiesTs = Date.now();
633+
await writeConfigJson(env, CURRENT_STRATEGIES_CACHE_KEY, {
634+
ts: _memCurrentStrategiesTs,
635+
data: fresh,
636+
});
637+
} catch { /* keep stale data */ }
638+
finally { _memRefreshing = false; }
639+
})());
640+
}
641+
642+
// 4) Complete miss — must wait for GitHub
609643
if (!currentStrategies) {
610644
currentStrategies = await loadCurrentStrategiesSafely(accountConfig.options, env);
611645
_memCurrentStrategies = currentStrategies;

0 commit comments

Comments
 (0)