Skip to content

refactor(storage): one eviction entry point — evict_to_budget + EvictionRun replace the 13-name try_evict_if_needed* family (W4)#399

Merged
pilotspacex-byte merged 1 commit into
mainfrom
refactor/w4-eviction-api
Jul 20, 2026
Merged

refactor(storage): one eviction entry point — evict_to_budget + EvictionRun replace the 13-name try_evict_if_needed* family (W4)#399
pilotspacex-byte merged 1 commit into
mainfrom
refactor/w4-eviction-api

Conversation

@pilotspacex-byte

Copy link
Copy Markdown
Contributor

Summary

Fourth PR of the storage-unification campaign (stacked on #398#397#396). Fixes finding F4 (eviction API explosion).

Before

13 public try_evict_if_needed* variants encoded every optional-parameter combination as a function-name suffix (_budget, _with_spill, _and_total, _async_spill, _reporting, …). Five had zero external callers (forwarding-chain-internal only), and the two chain-bottom "cores" duplicated the same reclaim-loop skeleton five times (sync plain, sync batch, async plain-fallback, async durable-batch, async AOF-fast-path).

After

One entry point:

evict_to_budget(db, config, EvictionRun) -> Result<(), Frame>
  • EvictionSink (Plain / SyncSpill / AsyncSpill) makes the victim destination data instead of a name suffix.
  • EvictionRun constructors (plain() / sync_spill() / async_spill()) + chainable knobs .total(n) (aggregate accounting), .budget(n) (elastic per-shard override, clamped to instance maxmemory), .report(f) (plain-drop dual-plane DEL emission, task GPU: CI/CD infrastructure for CUDA builds and testing #34).
  • The reclaim loop exists once with a per-iteration sink dispatch. The --appendonly durability-ordering branches (AOF-backstopped fast path / durable-batch / plain-drop fallback) are decision-for-decision identical, now documented in one place on EvictionSink::AsyncSpill.

Pure refactor — no behavior change. Net −59 lines despite three new tests and consolidated docs (453 lines of wrappers deleted).

Migration

All ~27 call sites across 9 files (monoio/sharded/single connection handlers, SPSC cross-shard gate, Lua bridge, blocking path, memory-pressure cascade, eviction tick) plus every eviction pin test; stale doc references scrubbed repo-wide.

Gates

  • ✅ Red-first: 3 entry-point behavior pins (plain sink honors noeviction-OOM + reports every plain-dropped victim; sync-spill sink batches 40 victims into shared files, all cold-readable; elastic override respected and clamped) — compile-RED against the missing API, then GREEN
  • ✅ macOS lib 4425 · Linux VM release-fast 4447 (monoio) + 3614 (tokio,jemalloc)
  • crash_matrix_cross_plane 46/46 · spill_batch_kill9 2/2 · disk_offload_no_aof 1/1 · oom_bypass_closure 8/8 on Linux VM with pinned fresh-ELF MOON_BIN
  • ✅ clippy -D warnings ×2 feature sets · fmt · CHANGELOG entry

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@TinDang97, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 56 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c87264cd-b1c2-4008-802b-4aed2f01d1ed

📥 Commits

Reviewing files that changed from the base of the PR and between 29e0ab3 and 19a13ba.

📒 Files selected for processing (15)
  • CHANGELOG.md
  • src/config.rs
  • src/scripting/bridge.rs
  • src/server/conn/blocking.rs
  • src/server/conn/handler_monoio/mod.rs
  • src/server/conn/handler_sharded/mod.rs
  • src/server/conn/handler_single.rs
  • src/shard/persistence_tick.rs
  • src/shard/spsc_handler.rs
  • src/shard/timers.rs
  • src/storage/db_quota.rs
  • src/storage/eviction.rs
  • tests/container_growth_memory_accounting.rs
  • tests/oom_bypass_closure.rs
  • tests/used_memory_offload_truthful.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/w4-eviction-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…ionRun replace the 13-name try_evict_if_needed* family (W4)

The eviction API had grown 13 public variants that encoded every
optional parameter combination in the function name:

  try_evict_if_needed
  try_evict_if_needed_budget[_reporting]
  try_evict_if_needed_with_spill[_and_total[_budget[_reporting]]]
  try_evict_if_needed_async_spill[_with_total][_budget][_reporting]
  try_evict_if_needed_async_spill_with_total_budget[_reporting]

Five of them already had zero external callers (chain-internal only),
and the two "cores" at the bottom of each forwarding chain duplicated
the same reclaim-loop skeleton five times (sync plain, sync batch,
async plain-fallback, async durable-batch, async AOF-fast-path).

This PR replaces the family with one entry point:

  evict_to_budget(db, config, EvictionRun) -> Result<(), Frame>

where EvictionRun is an options struct built from three constructors
(plain / sync_spill / async_spill = the EvictionSink choice) plus
chainable knobs .total(n) (aggregate accounting), .budget(n) (elastic
per-shard override, capped at instance maxmemory), and .report(f)
(plain-drop DEL emission, task #34). The reclaim loop now exists once
with a per-iteration sink dispatch; the --appendonly durability-
ordering branches (AOF-backstopped fast path vs durable-batch vs
plain-drop fallback) are byte-for-byte the same decisions, now
documented in one place on EvictionSink::AsyncSpill.

Pure refactor — no behavior change:

- Red-first: three new entry-point behavior pins (plain sink honors
  noeviction-OOM + reports every plain-dropped victim; sync-spill sink
  batches 40 victims into shared .mpf files, all cold-readable;
  elastic override respected and clamped to maxmemory) written RED
  against the missing API, then GREEN.
- All ~27 call sites across 9 files migrated (connection handlers,
  SPSC cross-shard gate, Lua bridge, blocking path, memory-pressure
  cascade, eviction tick, db_quota docs); every historical eviction
  pin test migrated and green.
- Stale doc references to the old names scrubbed repo-wide.

Gates: lib suite 4425 green (4422 + 3 new pins) on macOS + Linux VM
release-fast, monoio + tokio,jemalloc feature sets; crash matrix +
kill-9 offload suites on Linux VM; clippy -D warnings on both feature
sets; fmt.

Stacked on refactor/w3-ttl-ms (PR #398).

author: Tin Dang
@TinDang97
TinDang97 force-pushed the refactor/w4-eviction-api branch from 888e46b to 19a13ba Compare July 20, 2026 05:36
@pilotspacex-byte
pilotspacex-byte merged commit 1f81ce2 into main Jul 20, 2026
7 checks passed
TinDang97 added a commit that referenced this pull request Jul 20, 2026
…generics replace Database's per-type accessor matrix (W5)

Database had grown a ~20-method typed-accessor matrix: get_X,
get_or_create_X, and get_X_ref_if_alive for each container type (hash,
list, set, sorted set, stream), every one a copy-paste of the same
skeleton — expiry check -> cold promote/read-through -> compact-
encoding upgrade -> variant projection — with only the enum arms
differing. Skeleton fixes (the P0 cold-collection-visibility fix, the
promote-before-fabricate fix) had to be applied N times and could
silently miss a copy.

The skeleton now exists ONCE per access shape as a generic on
Database:

  get_ref_if_alive::<K>   &self;  hot probe -> expiry -> classify ->
                          non-promoting cold read-through (Owned view)
  get_or_create::<K>      &mut;   expiry-drop -> cold promote ->
                          insert-on-miss -> upgrade -> project mut
  get_promoted::<K>       &mut;   expiry-drop -> cold promote ->
                          upgrade -> project shared

Everything genuinely per-type — which enum variants map to which view,
how a compact encoding upgrades (incl. the deliberate asymmetries:
sorted sets upgrade the legacy BTreeMap form but NOT SortedSetListpack,
whose upgrade lives in the zset command layer), what a fresh entry
looks like — lives in the new storage::db_kind module as
ValueKind/OwnedKind impls on zero-sized markers. Static dispatch only:
every K is a concrete marker resolved at compile time, so the
generated code is identical to the hand-written originals.

The public per-type methods (get_hash, get_or_create_set,
get_list_ref_if_alive, get_stream_if_alive, ...) remain as thin
delegators — command-layer call sites are untouched. The generic
get_or_create also replaces the per-type unwrap-after-insert with the
defensive log-and-error path only the hash accessor previously had.

Deleted: the four caller-less get_X_if_alive variants (hash/list/set/
sorted_set; compact encodings returned None — long superseded by the
_ref_if_alive family). get_or_create_intset and the *_listpack
threshold accessors keep their bespoke shapes (their Option-returning
contracts don't fit the trait and have single call sites).

Pure refactor — no behavior change:

- Behavior pins added GREEN BEFORE the refactor and re-verified after:
  hot-WRONGTYPE through all four ref accessors; a hot HashWithTtl entry
  maps to HashRef::WithTtl carrying the CALLER's now_ms (expired field
  filters, live field reads through). Cold-Owned legs, promote-instead-
  of-fabricate, and wrongtype pins already existed and still pass.
- Net -318 lines; db.rs 3431 -> 3113 lines.

Gates: lib suite 4427 green (4425 + 2 new pins) on macOS + Linux VM
release-fast, monoio + tokio,jemalloc feature sets; crash matrix +
kill-9 offload + cold-visibility suites on Linux VM; clippy -D
warnings on both feature sets; fmt.

Stacked on refactor/w4-eviction-api (PR #399).

author: Tin Dang
pilotspacex-byte added a commit that referenced this pull request Jul 20, 2026
…generics replace Database's per-type accessor matrix (W5) (#400)

Database had grown a ~20-method typed-accessor matrix: get_X,
get_or_create_X, and get_X_ref_if_alive for each container type (hash,
list, set, sorted set, stream), every one a copy-paste of the same
skeleton — expiry check -> cold promote/read-through -> compact-
encoding upgrade -> variant projection — with only the enum arms
differing. Skeleton fixes (the P0 cold-collection-visibility fix, the
promote-before-fabricate fix) had to be applied N times and could
silently miss a copy.

The skeleton now exists ONCE per access shape as a generic on
Database:

  get_ref_if_alive::<K>   &self;  hot probe -> expiry -> classify ->
                          non-promoting cold read-through (Owned view)
  get_or_create::<K>      &mut;   expiry-drop -> cold promote ->
                          insert-on-miss -> upgrade -> project mut
  get_promoted::<K>       &mut;   expiry-drop -> cold promote ->
                          upgrade -> project shared

Everything genuinely per-type — which enum variants map to which view,
how a compact encoding upgrades (incl. the deliberate asymmetries:
sorted sets upgrade the legacy BTreeMap form but NOT SortedSetListpack,
whose upgrade lives in the zset command layer), what a fresh entry
looks like — lives in the new storage::db_kind module as
ValueKind/OwnedKind impls on zero-sized markers. Static dispatch only:
every K is a concrete marker resolved at compile time, so the
generated code is identical to the hand-written originals.

The public per-type methods (get_hash, get_or_create_set,
get_list_ref_if_alive, get_stream_if_alive, ...) remain as thin
delegators — command-layer call sites are untouched. The generic
get_or_create also replaces the per-type unwrap-after-insert with the
defensive log-and-error path only the hash accessor previously had.

Deleted: the four caller-less get_X_if_alive variants (hash/list/set/
sorted_set; compact encodings returned None — long superseded by the
_ref_if_alive family). get_or_create_intset and the *_listpack
threshold accessors keep their bespoke shapes (their Option-returning
contracts don't fit the trait and have single call sites).

Pure refactor — no behavior change:

- Behavior pins added GREEN BEFORE the refactor and re-verified after:
  hot-WRONGTYPE through all four ref accessors; a hot HashWithTtl entry
  maps to HashRef::WithTtl carrying the CALLER's now_ms (expired field
  filters, live field reads through). Cold-Owned legs, promote-instead-
  of-fabricate, and wrongtype pins already existed and still pass.
- Net -318 lines; db.rs 3431 -> 3113 lines.

Gates: lib suite 4427 green (4425 + 2 new pins) on macOS + Linux VM
release-fast, monoio + tokio,jemalloc feature sets; crash matrix +
kill-9 offload + cold-visibility suites on Linux VM; clippy -D
warnings on both feature sets; fmt.

Stacked on refactor/w4-eviction-api (PR #399).

author: Tin Dang

Co-authored-by: Tin Dang <tindang.ht97@gmail.com>
@TinDang97
TinDang97 deleted the refactor/w4-eviction-api branch July 20, 2026 05:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants