Demote CDC chunks in the eviction order after splice - #2627
Open
erneestoc wants to merge 2 commits into
Open
Conversation
A spliced blob stores its bytes twice in the same CAS: the chunk blobs (retained as dedup hints for future incremental uploads) plus the assembled blob, and the splice's existence checks and chunk reads promote every chunk in the store's LRU. Under a size-capped store the assembled blob's own insert-time eviction pass therefore reclaims space from unrelated, still-referenced blobs while the freshly-promoted chunks survive — reproduced in production as a deterministic NotFound on a 78-byte intermediate output that a build-without-the-bytes client cannot re-upload. Add EvictingMap::demote (move entries to the least-recently-used end of the eviction order without firing callbacks or changing sizes) exposed via a default-no-op StoreDriver::demote_keys, overridden by the filesystem and memory stores and forwarded through fast_slow. SpliceBlob demotes all chunk entries once their contents are fully consumed — before the EOF releases the assembled blob for commit — so the commit's eviction pass evicts the reproducible chunks first; the no-op splice fast path demotes likewise. Regression test reproduces the failure (fails without the demotion) with an unrelated small blob surviving a splice that exceeds the store cap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXVtatcR9YMecBiu9RjwpC
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
erneestoc
marked this pull request as draft
July 25, 2026 01:50
erneestoc
marked this pull request as ready for review
July 27, 2026 00:13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem (production failure)
A cold iOS RBE build on a size-capped filesystem CAS with CDC chunking
failed deterministically: four actions reported the same missing 78-byte
intermediate output, and the Buildkite retry failed immediately on the
same digest. With build-without-the-bytes the client cannot re-upload an
evicted intermediate output, so a mid-build eviction of a
still-referenced blob is fatal.
Three code facts compose into the failure:
~2× its size (chunk blobs + assembled blob) in the same store.
in the store's LRU (
sizes_for_keys(..., peek=false)), so chunksmonopolize the LRU head while early-build small blobs form the
eviction tail.
eviction pass reclaims from the tail regardless of what queued
actions still reference.
Reproduced in an integration test: a 20MiB-capped store holding a small
early blob plus a 12MiB blob — a plain upload keeps the small blob
alive; a chunk+splice of the same bytes evicts it.
Mechanism
EvictingMap::demote: moves entries to the least-recently-used end ofthe eviction order — no callbacks, no size changes, absent keys
ignored (
lru::LruCache::demote).StoreDriver::demote_keys: default no-op eviction-ordering hint;overridden by the filesystem and memory stores, forwarded through
fast_slow. Wrappers that don't forward it simply leave orderingunchanged — there is deliberately no advertisement bit to keep
forwarding contracts trivial.
SpliceBlobdemotes all chunk entries once their contents are fullyconsumed, before the EOF releases the assembled blob for commit, so
the commit's own eviction pass evicts the reproducible chunks first
(the ordering matters: demoting after the commit is too late). The
no-op splice fast path demotes likewise, undoing the promotion done by
its existence check.
Chunks remain in the store as dedup hints for future incremental
uploads; under pressure they are now the first casualties instead of the
last, and
SplitBlobregenerates layouts on demand if they are gone.Scope
This predates the open chunked-uploads PR #2595 — any chunking client
(Bazel
--experimental_remote_cache_chunkingagainst a NativeLink CASsince #2497) can trigger it. Operator guidance regardless of this fix: a
size-capped CAS whose eviction can outpace a build's working set is
unsafe under build-without-the-bytes; with chunking, budget roughly 2×
the large-output working set as headroom.
Tests
splice_evicts_demoted_chunks_not_unrelated_blobs: verified to FAILwithout the demotion (the small blob is evicted by the splice's
commit) and pass with it; also byte-verifies the assembled blob and
asserts real eviction pressure (some chunks reclaimed).
demote_moves_entry_to_eviction_front,demote_absent_key_is_noop(EvictingMap unit tests).
cargo check --tests --workspaceclean; bazelutil+store+service+config 68/68 with clippy pedantic + nightly rustfmt
aspects.
🤖 Generated with Claude Code
https://claude.ai/code/session_01UXVtatcR9YMecBiu9RjwpC
This change is