Commit Graph

5 Commits

Author SHA1 Message Date
will.anderson ce0d33ba93 engram tiered storage M5: online compaction + background checkpointer (Phase 3, additive)
Reclaims space held by dead records (tombstoned prune/forget nodes, superseded
ids, stale re-put/hebb versions, and their orphaned overflow chains). On-disk
format UNCHANGED — pure behavior.

COMPACTION (store_compact): copy-live + atomic-swap.
  A. checkpoint/sync to quiesce (WAL reduced to CHECKPOINT{C}); crash here => pre.
  B. build <path>.compact with only the live records, re-placed bit-exact into
     fresh densely-packed pages + fresh id/adjacency B+-trees, every page stamped
     LSN=C, new SB last_checkpoint_lsn=C; fsync. crash here => pre-compaction.
  C. rename(<path>.compact -> <path>) — POSIX-atomic commit; crash after => post.
  D. reopen in place: swap fd, INVALIDATE every pool frame (M4 remap of relocated
     pages), reload SB, re-autopin.
Crash at any instant recovers to pre- OR post-compaction, never a corrupt mix.
Single-threaded => "online" = safe between mutations; takes a checkpoint quiesce
at entry. M4 cooperation: temp build has its own pool honoring ENGRAM_POOL_FRAMES
(evict/re-fault + no-steal + pins); live pool fully invalidated on reopen.

BACKGROUND CHECKPOINTER: ckpt_maybe now fires on ANY armed trigger — ops (default
100000), dirty pool frames, WAL bytes-since-reclaim (default 64 MiB), or a
wall-clock interval (checked on the write path; no extra thread). Same M2
checkpoint semantics (calls engram_checkpoint). Env: ENGRAM_CKPT_OPS/_DIRTY/
_WAL_BYTES/_INTERVAL_MS; runtime setter store_set_checkpoint_policy().

Tests: engram/test/test_compaction.c (+runner). Plain gcc, ASan/UBSan clean.
  36 passed, 0 failed (O2 and ASan+UBSan builds).
  reclaim: page_count 655 -> 153, file 10731520 -> 2506752 bytes (76.6% reclaimed),
    every live record + adjacency bit-exact at new locations.
  crash-during-compaction phases 0/1/2: crc clean, live set intact, writable.
  background checkpointer: ops / WAL-bytes / dirty triggers each auto-fire; WAL
    prefix reclaimed (30 B after 600 puts); recovery correct.
  pool cooperation: compact under 24-frame pool correct, no stale frames.
No regression: M1 33, M2 36, M3 parity, M3.5, M4 bufpool 37 — all green.
On-disk format unchanged (additive).
2026-08-12 16:03:14 -05:00
will.anderson 02dc12d785 engram tiered storage M4: demand-paging buffer pool (Phase 2, additive)
Turn M2's write-back/no-steal cache into a bounded, demand-paged buffer pool so
the paged store can exceed RAM while keeping only hot pages resident. On-disk
format UNCHANGED (additive residency only; no migration). Default budget is large
enough that today's store stays fully resident, so default behaviour == Phase 1.

- Frame table capped at `cap` frames (env ENGRAM_POOL_FRAMES; 0 = unlimited;
  default 1<<20). Not-resident access faults in from neuron.egm.
- LRU eviction of CLEAN, unpinned frames only. Dirty frames are never stolen
  (M2 no-steal / WAL durability preserved) — turned evictable by a checkpoint's
  pc_flush, which then trims the pool back to budget.
- Pinning: superblocks (0,1) + index root/interior pages auto-pinned; explicit
  store_pin_page/unpin and store_pin_layer/unpin (hot WM/core layers).
- Bounded sequential read-ahead on scans (env ENGRAM_PREFETCH, default 8).
- Correctness rests on callers copying page bytes into local buffers and never
  retaining a frame pointer across another access, so evict+re-fault is safe.

Gates (plain gcc, ASan/UBSan clean):
  M4 run_bufpool_tests.sh  ......  37 passed, 0 failed  (+ ASan/UBSan: 37/0)
    small-pool round-trip (cap=32 vs 1599 pages, 2708 evictions): 5000 nodes +
      4000 sampled edges bit-exact, crc clean, pool bounded to cap.
    eviction: hot set 0 re-faults, cold evicted, hit-rate 0.989; no-steal burst
      (cap=8) holds 309 dirty frames > cap, reads correct from dirty pages.
    pinning: superblocks/roots/explicit page/hot-layer(19 pages) stay resident;
      unpin makes them evictable.
    prefetch: sequential scan 511 demand-faults OFF -> 4 ON.
    crash-under-paging (ENGRAM_POOL_FRAMES=16): WAL replay + checkpoint-crash
      phases 0-4 all recover bit-exact.
    default pool: 0 evictions, whole store resident (== Phase 1).
  No regression: M1 33/0, M2 36/0, M3 parity PASS, M3.5 PASS.
2026-08-12 15:42:49 -05:00
will.anderson a72145b44e engram tiered storage M3: wire store behind ENGRAM_STORE (default off) + .egm rename
Caller-side shim in el_runtime.c maps EngramNode/Edge <-> StoreNode/Edge; engine
keeps zero soul deps (libengram boundary, design §10). Flag off = today's JSON
path byte-for-byte (proven: no neuron.egm created, graph identical). Flag on =
engram_open (import snapshot.json once into neuron.egm, else WAL-replay) +
resident load; node/edge create + forget dual-write via guarded hooks. Files
renamed engram.store->neuron.egm, engram.wal->neuron.wal.

Gate: M3 parity PASS (graph on==off byte-exact modulo ordering; snapshot round-trip;
reboot-from-egm with snapshot.json deleted; activation set+sequence identical;
ASan/UBSan clean). M1 33/33 + M2 36/36 green post-rename.

Known gap (pre-flip): in-place hebb/WM/activation_count updates during activation
are not yet persisted to the store (create/connect/forget are). Must close before
live flip so learned edges survive restart.
2026-08-11 23:21:21 -05:00
will.anderson 8affb1d6e0 engram tiered storage M2: WAL + checkpoint + crash recovery + legacy import
Write-back no-steal buffer pool makes the fsync'd WAL load-bearing (M1 was
write-through). Logical WAL with record-granularity page-LSN redo idempotency.
Checkpoint = flush dirty pages, fsync store, advance last_checkpoint_lsn,
reclaim WAL prefix. One-time snapshot.json import only when store absent;
JSON never read as the ongoing store thereafter.

Gates: 33/33 M1 (no regression) + 36/36 M2 — replay parity, torn-tail fuzz
(every byte offset), checkpoint-crash at all 5 phases, torn-page+WAL redo,
legacy-import parity, hebb-survives-crash.
2026-08-11 23:00:20 -05:00
will.anderson fa47b98d18 engram tiered storage M1: on-disk paged store format + round-trip tests
Self-contained paged store (lang/runtime/engram_store.{c,h}): 16KiB slotted pages,
u32 TLV self-describing records (forward-compatible), overflow chains, B+-tree
id-index + from/to adjacency, page free-list, tombstones, double superblock + crc
recovery. Not yet wired to activation (M3). 33/33 tests pass (ASan/UBSan clean);
5k nodes/20k edges round-trip bit-exact incl 768xf32 emb + hebb; store 25MB vs 64MB
JSON. Format is final — see design §2.4.
2026-08-11 22:26:05 -05:00