[Store] Self-heal dangling LOCAL_DISK replicas on the read path - #3889
[Store] Self-heal dangling LOCAL_DISK replicas on the read path#3889he-yufeng wants to merge 4 commits into
Conversation
A COMPLETE LOCAL_DISK replica whose backing file physically disappears (manual wipe, RemoveAll broadcast) stayed registered forever, and every batch_get against it just logged "SSD read failed" and handed back an empty buffer (kvcache-ai#3465). kvcache-ai#3711 already heals this on the Put path; reads had no equivalent. On a failed SSD read, run the same heal: the offload-file probe proves the backing file gone, the dangling replica is evicted master-side, and the key is retried once so a surviving replica (or a fresh re-put) can serve it instead of returning empty. When nothing survives, the master stops advertising the key and the miss surfaces honestly. The retry can only heal distinct dead replicas, so it terminates. New regression test BatchGetBufferHealsDanglingLocalDiskReplica puts, offloads, wipes the SSD root, and asserts the read misses honestly with the replica evicted from metadata; it fails without the fix and passes with it (Docker, in-proc master harness), alongside the existing local-disk offload/batch-get tests. Refs kvcache-ai#3884 Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
|
I missed this when reviewing #3711: the existence probe receives the raw object key, but offload storage uses tenant-scoped keys, including for the default tenant. A healthy object can therefore be reported as missing. This PR exposes that existing bug on reads. I reproduced it locally with two disk-only keys, A and B, in separate bucket files on the same owner:
The new counterexample fails on this PR and passes when I remove the added read-healing block. The PR's existing regression test passes in the same build. Could we fix the probe before extending its use? It needs both the correct tenant-scoped storage key and a physical-file existence check: BucketStorageBackend::IsExist currently checks only the in-memory index, so fixing the key alone would still miss externally deleted files. Please also add a mixed healthy/missing-key regression to ensure a failed batch preserves healthy replicas. |
fcczzz's counterexample on the read-path heal: two disk-only keys in separate bucket files on one owner, only one's file wiped, and the failed batch evicted the healthy key too. Two roots: - the probe asked FileStorage::Exists with the raw object key while the offload index is keyed by tenant-scoped keys, default tenant included, so every answer came back "gone"; - BucketStorageBackend::IsExist answered from the in-memory index only, so even the right key could not see an externally wiped bucket file. The probe now wraps the key with the tenant scope at the install site, which fixes every heal call path at once, and IsExist checks the bucket data file on disk after an index hit (writes commit file-then-index, so an index hit can never precede its file). healDanglingLocalDiskReplica now returns a 4-state result instead of a bool, because the batch read dies wholesale on one missing bucket file: a key whose file is proven present must also be retried, or the caller gets a false miss for healthy data. The Put path contract is unchanged (retry only on kEvicted). BatchGetBufferWipedSiblingPreservesHealthyReplica pins the scenario: it fails on the pre-fix code (healthy replica evicted, data lost) and passes with the fix. Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
|
@fcczzz Fixed, and your counterexample is now a regression test. Three changes: 1. The probe now asks with the tenant-scoped key. The install site in 2. 3. Your mixed-key scenario is the new regression test ( That test exposed one more real bug in my original read-path code, which I'd rather admit than bury: the SSD batch read dies wholesale on A's missing bucket file, so B's read failed too, and since B's replica was (correctly) not evicted, B's result stayed null. Healing preserved the replica but the caller still got a false miss for a healthy key. So Verified in the dev container on this head: both heal tests pass, and the new test fails on the pre-fix code (B's replica evicted, B's data lost), passes with the fix. |
…g-disk-replica Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com> # Conflicts: # mooncake-store/src/real_client.cpp
|
Merged current main to clear the conflict (91d8e78). The disk-read section moved to op_status accounting upstream, so the heal retry now records the batch failure first and writes final_results only on a healed read, leaving the duplicate fan-out and handle assembly untouched. Both heal tests re-pass on the merged tree. |
Description
A COMPLETE LOCAL_DISK replica whose backing file physically disappears (manual wipe, RemoveAll broadcast, disk loss) stays registered forever, and every
batch_getagainst it just logs "SSD read failed" and hands back an empty buffer. #3711 already self-heals exactly this on the Put path (probe file existence,EvictDiskReplica, clean retry); the read path had no equivalent, which is the still-open half of #3465 and what #3884 tracks.On a failed SSD read in
batch_get_buffer_internal, the same heal now runs: the offload-file probe proves the backing file gone, the dangling replica is evicted master-side viaEvictDiskReplica, and the key is retried once so a surviving replica (or a fresh re-put) can serve it instead of returning empty. When nothing survives, the master stops advertising the key and the miss surfaces honestly. The retry only ever heals distinct dead replicas, so it terminates. Checksum failures keep their existing path (the file exists, just corrupt, so no heal fires). Healthy reads are untouched; this is failure-path only.healDanglingLocalDiskReplicamoves from private to public inclient_service.hso the RealClient read path can share the exact Put-side logic instead of growing a second copy.Refs #3884, #3465, #3711.
Module
mooncake-store)Type of Change
How Has This Been Tested?
Test commands:
Test results:
Docker (ubuntu 22.04, gcc) with the in-proc master harness: the new regression test
BatchGetBufferHealsDanglingLocalDiskReplica(put, wait for the LOCAL_DISK replica, clear MEMORY, wipe the SSD root, then batch_get) fails on the unpatched tree (the dead replica stays advertised) and passes with the fix (honest miss, replica evicted from metadata). Log sequence observed end to end:SSD read failed ... FILE_OPEN_FAIL->Evicted dangling LOCAL_DISK replica ... (backing file already gone)->Object not found. The neighboring local-disk / offload / batch-get tests pass (3/3). The full CTest suite was not run locally; it runs in CI.Checklist
./scripts/code_format.shAI Assistance Disclosure
Prepared with AI assistance (Kimi K3). The investigation, implementation, and tests were reviewed line by line, and the verification above was run in Docker as described.