From e96c408ac182cfa684066b225671b087d5abb123 Mon Sep 17 00:00:00 2001 From: openhands Date: Thu, 22 Oct 2026 18:31:33 +0200 Subject: [PATCH] perf(cache): bound reported-failures set and halve default in-process budget --- src/lib/cache.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/lib/cache.ts b/src/lib/cache.ts index cfdba148..f07be676 100644 --- a/src/lib/cache.ts +++ b/src/lib/cache.ts @@ -49,7 +49,7 @@ function readPositiveInt(name: string, fallback: number): number { // In-process budget. Entries are evicted least-recently-*used* (see getMemory), // so a hot key only leaves when a hotter one takes its place. The default is // generous because the layer exists to keep origin calls off the hot path. -const MAX_MEMORY_ENTRIES = readPositiveInt("CACHE_MEMORY_MAX_ENTRIES", 2_000); +const MAX_MEMORY_ENTRIES = readPositiveInt("CACHE_MEMORY_MAX_ENTRIES", 1_000); // Single-flight: a key being (re)computed is awaited by concurrent callers // instead of each starting its own `fn()` (cache-stampede protection). @@ -62,9 +62,20 @@ const generations = new Map(); const MAX_GENERATION_ENTRIES = MAX_MEMORY_ENTRIES * 2; // Keys whose most recent background refresh failed, so a persistently broken -// origin reports once instead of on every read of a hot key. +// origin reports once instead of on every read of a hot key. Bounded so a +// high-cardinality stream of failing keys can never grow this without limit. +const MAX_REPORTED_FAILURES = 500; const reportedFailures = new Set(); +function recordReportedFailure(key: string): void { + if (reportedFailures.has(key)) return; + if (reportedFailures.size >= MAX_REPORTED_FAILURES) { + const oldest = reportedFailures.values().next().value; + if (oldest !== undefined) reportedFailures.delete(oldest); + } + reportedFailures.add(key); +} + /** Drop a key from the in-process cache (used when an upstream value changes). */ export function invalidateMemory(key: string): void { generations.set(key, (generations.get(key) ?? 0) + 1); @@ -180,7 +191,7 @@ export async function cached( // read: a broken origin would otherwise flood the log from a // single hot key. if (reportedFailures.has(key)) return; - reportedFailures.add(key); + recordReportedFailure(key); logger.warn("[cache] background refresh failed", { key, error: String(error),