perf: leaderboard SWR cache + threadpool fix for season-III stalls

- Fix 1: UV_THREADPOOL_SIZE=24 via start_server.sh wrapper (libuv reads OS
  environ; process.env and PM2 env blocks don't propagate on this system)
- Fix 2: Stale-while-revalidate for leaderboards — serve cached/stale data
  instantly, refresh in background; dedicated aggregateCache isolated from
  the 100-entry responseCache; single-flight dedup for concurrent computes
- Fix 3: Background warmer precomputes current + last-completed season
  leaderboards at +20s boot and every 4 min
- Fix 5: Adaptive TTL (5 min live, 24 h completed) via aggregateCacheTtl
- Fixes 1+2 combined: player page stall 95s -> 3.6s under concurrent heavy
  leaderboard load; warm hits served in 1-4ms (was 13-53s)
This commit is contained in:
deploy
2026-06-30 12:03:43 +00:00
parent 0f8f22df29
commit 659785f8f3
4 changed files with 144 additions and 84 deletions
+17 -18
View File
@@ -509,31 +509,30 @@ async function getCachedData(type) {
return cache.data;
}
// Initialize cache on startup — staggered to avoid overloading SQLite
// Initialize only lightweight caches on the primary worker.
// Player and vehicle leaderboards are too expensive to refresh in the background:
// recent production runs took 140s+ and kept the API CPU saturated after callers timed out.
async function initializeCache() {
log.info('[CACHE] Initializing leaderboard cache (staggered)...');
// Stats is lightest, do it first
if (!IS_PRIMARY_WORKER) return;
log.info('[CACHE] Initializing lightweight leaderboard cache (staggered)...');
await updateCache('stats');
log.info('[CACHE] Stats cache ready');
// Squadrons next
await updateCache('squadrons');
log.info('[CACHE] Squadrons cache ready');
// Players is heaviest
await updateCache('players');
log.info('[CACHE] Players cache ready');
// Vehicles last
await updateCache('vehicles');
log.info('[CACHE] Vehicles cache ready — all caches populated!');
}
// Auto-refresh caches staggered to avoid hammering the API all at once
const cacheTypes = ['players', 'vehicles', 'stats', 'squadrons'];
cacheTypes.forEach((type, i) => {
setInterval(async () => {
log.debug(`[CACHE] Auto-refreshing ${type} cache...`);
await updateCache(type).catch(log.error);
}, CACHE_DURATION + i * 15000); // stagger each by 15s
});
// Auto-refresh only lightweight caches from the primary worker. Heavy caches are
// refreshed lazily by request so background work cannot pin SQLite indefinitely.
if (IS_PRIMARY_WORKER) {
const cacheTypes = ['stats', 'squadrons'];
cacheTypes.forEach((type, i) => {
setInterval(async () => {
log.debug(`[CACHE] Auto-refreshing ${type} cache...`);
await updateCache(type).catch(log.error);
}, CACHE_DURATION + i * 15000);
});
}
// Log search cache statistics every 10 minutes
setInterval(() => {