feat: Dash table cache-mode segment-local eviction (#285 stage 2)#486
Conversation
DASHTABLE.md's stage 2: the standalone ironcache-dashtable gains cache-mode eviction, the (b) lever of #285 (O(1) segment-local eviction). `insert_cache(key, value, freq_of)` replaces split-on-full with EVICT-on-full: when the routed segment is at capacity, it evicts the coldest slot IN THAT SEGMENT and places the new key, so memory stays bounded and eviction touches O(SEGMENT_CAP) = O(1) slots with NO table-wide scan and NO per-key side state (no evict_pool, no refill bookkeeping). The victim is the slot minimizing the deterministic total order (freq, scan_hash, key) -- the SAME freq-in-object order the store's refill_evict_pool ColdEntry uses (freq, scan_h, key[, db]; EVICTION.md, ADR-0003), so two shards with identical state evict identically. The 2-bit frequency lives IN the value (freq-in-object), read via the caller's `freq_of` accessor, so the table stays generic and carries no eviction metadata of its own. `with_directory_bits` pre-sizes the segment array for a known working set (a cache is sized up front; growth is by eviction, not splits). The stage-2 deliverable DASHTABLE.md calls for is "a model test asserting the same victim as the current freq-in-object selection on shared inputs" -- implemented as `cache_evicts_the_freq_in_object_victim`, which fills a segment, independently computes the expected victim via the store's exact (freq, scan_hash, key) order, and asserts insert_cache evicts precisely that key. Plus tests for freq-protects-the-hot-key, segment-locality (16 distinct segments: evicting in one leaves the others untouched), and overwrite-in-place-without-eviction. Still 100% safe (miri-trivial) and standalone -- wiring behind the `dashtable` feature flag is stage 3, and the memory/throughput WIN vs DragonflyDB is the pinned-Linux head-to-head (stage 4). What THIS PR proves, on any host, is the eviction VICTIM QUALITY: the O(1) segment-local choice is byte-identical to the store's current O(N/CAP)-amortized selection. 7 tests (3 stage-1 + 4 stage-2) pass; clippy (pedantic) + fmt + invariant lints clean; 0 dashes. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: ELares <zeke@butlr.io>
perf-gate (A5)Same-runner ratchet of HEAD against the merge-base (both rebuilt and measured in this job).
Overall: PASS
|
…order (#285) The adversarial review found the doc claim "identical to the store's refill_evict_pool victim order" was OVERSTATED: the tie-break used this crate's internal directory hash (hash_key, std DefaultHasher / SipHash), while the store's ColdEntry breaks freq ties with `scan_hash` -- a DIFFERENT function (a wyhash/FNV-1a fold + splitmix64 finalizer over the raw key bytes). Among equal-freq keys (the common case in a cold tail) the two would pick DIFFERENT victims, and the in-crate oracle reused hash_key, so the model test was self-consistent but blind to the divergence from the real store. Fix: the scan-order hash is now a CALLER-supplied accessor (`scan_hash_of: Fn(&K) -> u64`), parallel to `freq_of`. The store passes its own `scan_hash` when it wires this table (stage 3), so the victim is byte-identical to refill_evict_pool BY CONSTRUCTION -- not merely same-shaped. The generic table carries no hash-family assumption; passing the hash in is exactly what makes the parity real. The model test now ports the store's ACTUAL scan_hash (byte-exact from store/src/lib.rs) and feeds it as the accessor, so cache_evicts_the_freq_in_object_victim validates parity with the REAL store order, not the crate's internal one. The module + method docs are corrected to describe the caller-supplied scan_hash and the by-construction parity. 7 tests pass; clippy (pedantic) + fmt + invariant lints clean; 0 dashes. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: ELares <zeke@butlr.io>
Deep self-review: adversarial multi-agent workflowRan a 3-dimension adversarial review (eviction-correctness / edge-cases / API) with a per-finding skeptic-verify phase. It surfaced one genuine, sharp finding (reported by two dimensions) — now fixed — plus correctly refuted the rest (the The real finding: the "identical to the store" claim was overstatedMy docs claimed the victim order was "identical to the store's Fix: make the parity real, not just same-shapedRather than soften the doc, I made the claim true. The scan-order hash is now a caller-supplied accessor ( This is the better outcome: the generic table carries no hash-family assumption, and passing the hash in is exactly what makes stage-3 wiring correct. Post-fix: 7 tests pass; clippy (pedantic, |
What
Implements DASHTABLE.md stage 2 on the standalone
ironcache-dashtablecrate: cache-mode segment-local eviction, the (b) lever of #285 (O(1) eviction).insert_cache(key, value, freq_of)replaces split-on-full with EVICT-on-full: when the routed segment is at capacity, it evicts the coldest slot in that segment and places the new key — so memory stays bounded and eviction touchesO(SEGMENT_CAP) = O(1)slots with no table-wide scan and no per-key side state (noevict_pool, norefill_evict_poolamortization).Same victim as the store — the stage-2 acceptance
DASHTABLE.md stage 2 calls for exactly "a model test asserting the same victim as the current freq-in-object selection on shared inputs." The victim is the slot minimizing the total order
(freq, scan_hash, key)— byte-identical to the store'srefill_evict_poolColdEntryorder (freq, scan_h, key[, db]; EVICTION.md, ADR-0003). The 2-bit frequency lives in the value (freq-in-object), read via the caller'sfreq_ofaccessor, so the table stays generic with zero eviction metadata of its own.cache_evicts_the_freq_in_object_victimfills a segment, independently recomputes the expected victim via that exact order (an oracle mirroring the store), and assertsinsert_cacheevicts precisely that key. Plus:cache_higher_freq_survives_eviction— a hot slot (freq 3) is never the victim across 100 evictions.cache_eviction_is_segment_local— with 16 distinct segments, hammering segment 0 leaves every other segment's contents untouched (the O(1) locality claim).cache_insert_overwrites_in_place_without_eviction.Honest scope
dashtablefeature flag (stage 3), and the memory/throughput win vs DragonflyDB — which needs the pinned-Linux + bench harness (stage 4). I am not claiming the perf win here; only the correctness primitive it rests on.Tests
7 (3 stage-1 + 4 stage-2) pass; clippy (pedantic,
-D warnings) + fmt + invariant lints clean; 0 dashes.🤖 Generated with Claude Code