← back to Ticket System
TK-11506: bound cache retention with idle eviction
965470bec7cb8293bd19527180a5890d4c0fb6c2 · 2026-09-11 13:46:27 -0700 · Steve Abrams
Caching the fold costs ~350MB of steady RSS — measured hot-vs-hot against the
pre-fix build on a scratch port, 98-176MB before vs 444MB after, not the 233MB
cold-start figure which understates it. This box runs its swap-pressure canary at
WARN, so the retention gets a ceiling: an entry nobody has read for 120s is
dropped and rebuilt on next use at one ~190ms fold.
Proven to actually fire, on an unpolled scratch instance with the window shortened
through the TK_VIEW_IDLE_MS test seam: warm 0.0039s -> post-sweep 0.281s (a real
rebuild) -> re-warm 0.0037s. On the LIVE board it rarely fires, because the board
is continuously polled by the UI and TicketBar, so live RSS stays ~450MB; the
bound that matters there is the measured plateau (465/662/663/524/525MB across
960 requests), not eviction. Stating that rather than claiming memory it does not
actually give back.
The env override is a test seam only; no plist or pm2 config passes it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Files touched
Diff
commit 965470bec7cb8293bd19527180a5890d4c0fb6c2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 11 13:46:27 2026 -0700
TK-11506: bound cache retention with idle eviction
Caching the fold costs ~350MB of steady RSS — measured hot-vs-hot against the
pre-fix build on a scratch port, 98-176MB before vs 444MB after, not the 233MB
cold-start figure which understates it. This box runs its swap-pressure canary at
WARN, so the retention gets a ceiling: an entry nobody has read for 120s is
dropped and rebuilt on next use at one ~190ms fold.
Proven to actually fire, on an unpolled scratch instance with the window shortened
through the TK_VIEW_IDLE_MS test seam: warm 0.0039s -> post-sweep 0.281s (a real
rebuild) -> re-warm 0.0037s. On the LIVE board it rarely fires, because the board
is continuously polled by the UI and TicketBar, so live RSS stays ~450MB; the
bound that matters there is the measured plateau (465/662/663/524/525MB across
960 requests), not eviction. Stating that rather than claiming memory it does not
actually give back.
The env override is a test seam only; no plist or pm2 config passes it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---
server.js | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/server.js b/server.js
index 0f85242d..f21636fb 100644
--- a/server.js
+++ b/server.js
@@ -105,15 +105,25 @@ function eventsKey() {
}
// The board's own writes must be visible to the very next read, so skip the stat TTL.
function invalidateViews() { viewStat.at = 0; }
+// Retention is bounded in BOTH directions: a generation is replaced when the log changes,
+// and an entry nobody has read for VIEW_IDLE_MS is dropped. Caching the fold costs ~350MB
+// of steady RSS (measured hot-vs-hot: 98-176MB before, 444MB after), which is worth paying
+// while the board is actually being polled and not worth holding when it is idle — this box
+// runs its swap-pressure canary at WARN. A rebuild after idling costs one ~190ms fold.
+const VIEW_IDLE_MS = Number(process.env.TK_VIEW_IDLE_MS) || 120000; // env override is a TEST SEAM; no plist/pm2 config passes it
const views = new Map();
function cachedView(name, build) {
const key = eventsKey();
const hit = views.get(name);
- if (hit && hit.key === key) return hit.value;
+ if (hit && hit.key === key) { hit.seen = Date.now(); return hit.value; }
const value = build();
- views.set(name, { key, value });
+ views.set(name, { key, value, seen: Date.now() });
return value;
}
+setInterval(() => {
+ const cutoff = Date.now() - VIEW_IDLE_MS;
+ for (const [name, entry] of views) if (entry.seen < cutoff) views.delete(name);
+}, 60000).unref();
// A cached payload is the encoded body, not the object — stringify is ~31ms of the cost.
function cachedPayload(name, build) {
return cachedView('payload:' + name, () => {
← 81f857a4 TK-11506: ship the head-of-line harness with its negative te
·
back to Ticket System
·
run-ticket: add local-qwen-14b-mac1 runner to offload qwen t 516f09bb →