Uh oh!
There was an error while loading. Please reload this page.
Confirm budget crossings with a forced GC before refusing (LLP 0097) - #305
Conversation
The heap-growth guard read raw heapUsed deltas, which count uncollected garbage as growth. On the central server (500k rows) single-column streaming scans sampled ~3.3GB of "growth" and refused, while the same scan completes inside a 100MB --max-old-space-size cap: the delta was per-row scan garbage V8 had not collected yet, so the guard killed exactly the streaming aggregates LLP 0055/0098 made cheap. - guard.check and the interval watchdog now confirm a crossing by forcing one full GC and re-measuring; only growth that survives collection refuses - the GC handle is acquired at runtime (setFlagsFromString + vm.runInNewContext), no --expose-gc launch flag needed; if the runtime refuses, fall back to refusing on the raw delta - LLP 0097 updated: the "garbage trips are unlikely" bet is recorded as falsified, with the confirm-with-gc rule under a new anchor - test proves transient promoted garbage no longer trips (fails against the old guard) and retained growth still refuses
platypii
commented
Jul 12, 2026
ReviewClean, well-scoped change. The diff, the LLP update, and the tests hang together well. What's good
Worth flagging (low severity, not blockers)
Neither point is a correctness problem; both are inherent tradeoffs the LLP broadly acknowledges. I'd merge as-is. Consider a one-line note in the LLP cost paragraph that the near-budget-retention case pays more than one GC per budget-width, so the next reader isn't surprised by a GC-heavy profile. |
Deploying v1.13.3 to the central server surfaced a false-refusal in the heap-growth budget: filtered COUNTs and even unfiltered MIN/MAX over the 500k-row
ai_gateway_messagesrefuse with ~3.3GB of reported growth, while the same scans complete inside a 100MB--max-old-space-sizecap. The guard reads rawheapUseddeltas, which count uncollected garbage; on a large-heap host V8 defers major GC for gigabytes, so the delta tracks allocation rate, not retention, and the guard kills exactly the streaming aggregates LLP 0055/0098 made cheap. (WHERE pushdown itself works on the server: a fully-pruned filtered COUNT answers in 2.6s.)Fix: confirm every crossing before refusing. Force one full synchronous GC (handle acquired at runtime via
v8.setFlagsFromString('--expose-gc')+vm.runInNewContext('gc'), no launch flag needed) and re-measure; only growth that survives collection refuses. A garbage-heavy but well-bounded query pays one forced GC per budget-width of garbage; a genuinely retaining query (the issue-#9 crasher class) pays one GC then refuses as before. If no GC handle is available, the guard falls back to the old raw-delta behavior.Verified locally against the real cache: the previously false-tripping filtered COUNT and MIN now complete under 25MB/10MB budgets, and a retained ORDER BY buffer still refuses at 5MB with the typed error. The new test fails against the old guard and passes with the fix. LLP 0097 records the falsified "garbage trips are unlikely" bet and the confirm-with-gc rule.
Note: the server needs this deployed before remote filtered aggregates work; queries that read data there currently refuse at ~3.3GB regardless of selectivity.