fix(server): skip evicting keys in buckets a full sync hasn't capture… - #8109
fix(server): skip evicting keys in buckets a full sync hasn't capture…#8109Shikha-code36 wants to merge 1 commit into
Conversation
…d yet Heartbeat/policy eviction deletes keys without going through the CoW OnChange hook (it runs under FiberAtomicGuard, which disallows the hook's blocking call). This lets eviction delete a key whose bucket a full-sync snapshot hasn't serialized yet, or whose value is mid-flight across multiple chunks -- the journal DEL can then race ahead of the bucket's still-in-progress baseline, so the replica applies the DEL as a no-op and later resurrects the key once the baseline finishes loading. Skip evicting a candidate key when any registered snapshot consumer either hasn't reached its bucket yet or is currently mid-flight on some bucket. Both checks are non-blocking, safe under FiberAtomicGuard. Fixes dragonflydb#8090 Fixes dragonflydb#7925
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
PR Summary by QodoFix eviction/full-sync snapshot race by skipping unsafe keys during eviction
AI Description
Diagram
High-Level Assessment
Files changed (1)
|
🤖 Augment PR SummarySummary: Prevents cache eviction during full-sync windows where a replica snapshot has not yet covered a bucket or is streaming a bucket in multiple chunks. Technical Notes: The eviction loop now examines registered snapshot consumers and defers affected candidates so the baseline entry remains ordered before its journaled deletion. 🤖 Was this summary useful? React with 👍 or 👎 |
Code Review by Qodo
🟠 Medium 1. Eviction blocked by callbacks
|
Fixes a race between heartbeat/policy-based eviction and full sync that lets a replica retain a key the master has already evicted.
Root cause:
DbSlice::FreeMemWithEvictionStepAtomicdeletes evicted keys viaDel()without going through the CoWOnChangehook (PreUpdateBlocking/CallChangeCallbacks) — that hook can't be called here because eviction runs underFiberAtomicGuard, which disallows the hook's blocking call. Normally,OnChangeis what protects an in-progress full-sync snapshot: if a bucket hasn't been serialized yet, it forces the bucket's pre-mutation state to be captured before the mutation is applied.Without that protection, eviction can delete a key whose bucket a full-sync snapshot hasn't visited yet, or whose value is currently mid-flight across multiple chunks (large values get split across several
PushToConsumerIfNeededcalls). The eviction's journalDELis written as untagged raw bytes into the same output stream and can land between two chunks of that key's own still-in-progress baseline entry. On the replica,RDB_OPCODE_JOURNAL_BLOBentries are applied immediately in stream order (rdb_load.cc), so theDELbecomes a no-op (the key hasn't finished loading yet) — and once the remaining chunks arrive and the value is fully reassembled, the key gets inserted, resurrecting a key the master no longer has.test_heartbeat_eviction_propagation(#8090) reproduces this directly: it populates 1MB values (DEBUG POPULATE 233 size 1048576) against a 300KB serialization chunk size, guaranteeing multi-chunk transmission for the values in play.test_policy_based_eviction_propagation(#7925) shows the same failure signature (replica retains a key master evicted) — plausibly the same underlying gap, though I haven't independently confirmed its value sizes hit the identical multi-chunk condition as #8090.Fix
Before evicting a candidate key, skip it if any currently-registered full-sync consumer either:
evict_it.GetVersion() < cb->snapshot_version_), orcb->IsAnyBucketBlocked())Both checks reuse existing non-blocking infrastructure (
DbSlice::change_cb_,ChangeConsumerInterface::IsAnyBucketBlocked()) and add no locking or yielding, so they're safe to call under the existingFiberAtomicGuard. Skipped keys remain eligible for eviction on the next heartbeat tick once the snapshot has moved past them — this doesn't reduce total eviction throughput, only defers eviction of specific keys that are momentarily in the race window.Test plan
test_heartbeat_eviction_propagation— 5/5 passes locally with the fix (previously failing intermittently in CI, see test_heartbeat_eviction_propagation #8090)test_policy_based_eviction_propagation— 5/5 passes locally with the fix (previously failing intermittently in CI, see test_policy_based_eviction_propagation #7925)Notes for reviewers
This is a targeted mitigation, not the full fix implied by the
snapshot.cccomment referencing a "delayed deletion queue proposal" design doc — that would presumably address the same class of gap more comprehensively (e.g. for expiry too, not just eviction). Flagging in case this should be superseded by or coordinated with that design work rather than merged as a standalone patch.Fixes #8090
Fixes #7925