perf(8.0): bound per-id generation history chains (O(W+L) resident, was O(N))

The MVCC time-travel layer kept nounChains/verbChains as unbounded
Map<id, number[]> — one resident chain per id ever touched across retained
history (O(N) RAM, defeating billion-scale time travel). Replace with a hot-tail
window + bounded cold LRU + a mutex-free bulk resolver, keeping resolveAt exactly
correct.

The most-recent W generations stay resident as full chains (the common recent-pin
read is O(log), zero scan); deeper pins reconstruct one id's chain on demand into
an L-bounded LRU. Reconstruction is LOCK-LIGHT — it holds no commit mutex, so a
historical read never stalls writers; correctness holds because a live pin's
answer is always > minPinnedGeneration, which compaction can never reclaim, and a
concurrently-reclaimed gen below that is skipped. materializeAtGeneration routes
through a new mutex-free resolveManyAt (one forward pass, O(R + |ids|)) — without
it a deep-pin materialize both regresses to O(N*R) and deadlocks on snapshotWith's
mutex. The cold cache is invalidated on re-touch to stay coherent. Resident RAM is
O(W*d + L), independent of N. 11-case test (oracle-vs-bruteforce, held-Db across
eviction + concurrent compaction, no-deadlock, write-path I/O-free); unit 1735 +
integration 613 (db-mvcc/db-temporal/db-asof green).
This commit is contained in:
David Snelling 2026-06-30 10:30:17 -07:00
parent 8f4787bb10
commit a859d6ecf8
4 changed files with 956 additions and 72 deletions

View file

@ -82,7 +82,7 @@ describe('generation history chains (Model-B scalability)', () => {
const g0 = await seedX()
for (let v = 1; v <= 12; v++) await bumpX(v) // 12 gens > cap 4 → evictions
// asOf still resolves correctly (ensureChains re-read every evicted delta to build the chain)…
// asOf still resolves correctly (the window build re-read every evicted delta)…
expect(await vAt(g0)).toBe(0)
// …and a range op (since scans committedGens via getDelta, re-reading evicted deltas) still works.
const now = await brain.now()