Skip to content

memory: bound cache admission and eviction - #2

Open
ravi-pplx wants to merge 1 commit into
pplx/mainfrom
ravi/bound-cachew-memory-eviction
Open

memory: bound cache admission and eviction#2
ravi-pplx wants to merge 1 commit into
pplx/mainfrom
ravi/bound-cachew-memory-eviction

Conversation

@ravi-pplx

@ravi-pplx ravi-pplx commented Aug 31, 2026

Copy link
Copy Markdown

Problem

The old memory tier evicted at capacity by collecting and selection-sorting
every cached object while holding one process-wide write lock. Admission cost
grew quadratically with cache cardinality, so a miss burst could block unrelated
hits. Incomplete writers and reader-pinned generations also did not share one
enforceable memory budget.

Change

  • Shard entries and single-key operations across 16 locks.
  • Replace the global scan and sort with fixed-work CLOCK admission.
  • Limit each trim to 64 victims and 64 scans per shard.
  • Revalidate planned victims with hit generations before removal.
  • Account retained entries, incomplete writers, replacement buffers, and
    reader-pinned generations against one ceiling.
  • Allocate declared-length bodies only as bytes arrive; a header alone cannot
    trigger its declared allocation.
  • Reserve only net buffer growth so equivalent streams are admitted regardless
    of write chunk boundaries.
  • Stop cancelled eviction plans before committing victims.
  • Reject negative or overflowing memory limits instead of silently treating
    them as unlimited.
  • Decline only the optional memory-tier copy when bounded admission cannot make
    room. Authoritative-tier writes and client delivery continue.

Capacity semantics

limit-mb bounds Cachew's accounted memory, not process RSS. Accounted memory
includes retained buffers, estimated entry metadata, and retired buffers still
pinned by readers. Go runtime and allocator overhead remain outside this value.

inflight-limit-mb defaults to zero for compatibility. When positive, it is a
sub-limit inside limit-mb, not extra capacity. With limit-mb = 32768 and
inflight-limit-mb = 1024, retained data is trimmed toward 31 GiB and retained
plus incomplete-writer accounting cannot exceed 32 GiB. Publishing transfers
the writer reservation to retained accounting without a double charge.

Growing a writer transfers its reservation from the old capacity to the new
capacity before copying. The allocator can briefly retain both buffers, so RSS
may transiently exceed accounted memory by at most the old buffer capacity. For
the production 1 GiB inflight sub-limit, that overage is bounded within the
existing process-memory headroom.

Before and after

Measured on an Apple M4 Max (darwin/arm64). The comparison runs the same
public Create + 16 KiB Write + Close benchmark against pre-PR commit
410ebdf and final head cb06519; values are medians of three one-second runs.

Approximate entries Before After Speedup Before B/op After B/op
256 130 us/op 3.57 us/op 36.5x 83,888 18,823
1,024 1.80 ms/op 3.50 us/op 514x 214,859 18,815
4,096 29.4 ms/op 3.42 us/op 8,610x 1,484,649 18,820

The old path grows quadratically with entry count. The replacement stays near
3.5 us/op and near 19 KiB allocated per configured write across this range.

Final-head cardinality checks:

Workload 1K entries 10K entries 100K entries
Admission only 0.43 us/op 0.45 us/op 0.51 us/op
Configured Create + Write + Close 2.12 us/op 2.18 us/op 2.49 us/op

Parallel hot hits remain approximately 0.27 us/op with six allocations per
operation. Saturated parallel admission completes in approximately 0.50 us per
attempt and accepts 56.4% of optional memory copies; bounded declines do not
shed client requests.

Correctness coverage

  • Post-plan hits invalidate older CLOCK eviction plans.
  • Accounting is reconstructed through create, grow, publish, replace, abort,
    delete, reader-pinned retirement, and shutdown.
  • Declared lengths do not allocate before body bytes arrive.
  • Equivalent unknown-length streams remain cacheable across write chunk sizes.
  • Cancellation before victim commit leaves existing entries intact.
  • Invalid limits fail construction instead of disabling bounds.
  • A declined memory copy still writes the complete object to the authoritative
    tier.

@ravi-pplx
ravi-pplx marked this pull request as ready for review August 31, 2026 20:37
@ravi-pplx
ravi-pplx force-pushed the ravi/bound-cachew-memory-eviction branch from 2aa97c3 to 5a43c73 Compare September 1, 2026 04:38
Replace the process-wide quadratic eviction path with sharded, fixed-work CLOCK admission. Revalidate planned victims with hit generations so competing planners cannot erase a recent reference.

Account retained entries, incomplete writers, and reader-pinned generations against one configured ceiling. Grow writer buffers lazily and reserve only net capacity so declared lengths cannot allocate memory before body data and streaming chunk boundaries do not change admission.

Reject invalid limit conversions and stop cancelled eviction plans before committing victims. Decline only the optional memory copy when bounded admission cannot obtain capacity, preserving authoritative-tier and client delivery.

Cover replacement, cancellation, concurrent admission, tier fallback, accounting transitions, shutdown, and writer growth. Add cardinality and configured-path benchmarks that distinguish successful admissions from bounded declines.

Verification: bin/just fmt; bin/just lint; bin/just test.
@ravi-pplx
ravi-pplx force-pushed the ravi/bound-cachew-memory-eviction branch from 5a43c73 to cb06519 Compare September 1, 2026 05:12
@ravi-pplx ravi-pplx changed the title memory: bound eviction work by shard memory: bound cache admission and eviction Sep 1, 2026
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.

1 participant