mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #4740] [Bug]: Partial SSD offload buckets lose task ownership across heartbeat cycles #1207
Labels
No labels
ascend
auto-closed
bug
bug
CI/Build
Common
documentation
enhancement
feature request
good first issue
help wanted
high priority
MetaX
nightly-failure
pull-request
PyTorch Backend
question
RFC
Roadmap
stale
Store
Tests
Transfer Engine
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/Mooncake#1207
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @MarinaMiao on GitHub (Oct 17, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/4740
Supplemental reproduction for #3006. Proposed default-on tail-flush fix: #4739. Related alternatives: #3009 (carry tasks across cycles) and #4673 (opt-in tail flushing).
We found a bucket offload lifecycle problem in a Mooncake Store checkout derived from 0.3.13, with additional SSD prefetch work. Small offload batches can retain only keys and sizes in the ungrouped pool while the current batch's full task metadata is released. The objects then fail to become usable SSD replicas. Separately, after fixing persistence, a sequential benchmark can still see no SSD hits because eager writes evict cached data before it is reused.
Environment and scope
Mooncake Store 0.3.13 derived checkout, Linux aarch64, Python 3.11, Ascend direct transport, bucket storage backend, LRU bucket eviction. This is a report against the tested checkout, with native test results referring to that checkout. The same grouping/task-cleanup interaction was also inspected on upstream main at
d750cddafa4b62387ca135949595008c51e05815; a native integration run of the current-main PR rebase remains pending.The persistence reproducer uses a standalone client and Master with 11 objects of 1 MiB each,
bucket_keys_limit=500, and a 256 MiB bucket byte limit. Prefetch and promotion are disabled.Reproduction
Start an isolated Master with SSD offload enabled and an empty backend.
Put 11 known objects and allow the offload heartbeat to run.
Observe the bucket allocation output, persisted object metadata, and Master SSD byte count.
Remove only registered DRAM cache replicas while preserving the SSD backend and the live client.
Get the same 11 objects and verify their SHA256 checksums.
With the original grouping path, the incomplete group remains in the ungrouped pool. Setting the key limit to 1 makes all 11 objects persist and allows all 11 checksum checks after clearing DRAM.
With the tail flush patch and the key limit restored to 500, all 11 objects persist. Master SSD bytes are 11,534,336 before and after clearing DRAM, DRAM bytes are zero, all 11 checksums match, and offload RPC reads increase by 11. This functional reproducer used shared storage and establishes correctness only; it is not an SSD bandwidth benchmark.
Expected behavior
A nonempty offload batch should either persist its objects with their task ownership intact or report a retryable failure. An incomplete tail should not cross the task lifetime boundary as an orphaned key list.
A bucket is a storage grouping unit. Its lifecycle is associated with the offload heartbeat batch, rather than with a model forward call.
Root cause in the tested checkout
BucketStorageBackend::GroupOffloadingKeysByBucket, called byAllocateOffloadingBuckets, moves the current incomplete group intoungrouped_offloading_objectsand returns without emitting a bucket when the input iterator reaches the end before the key limit.FileStorage::OffloadObjectsbuildstask_by_storage_keyfrom the current batch. Its success callback needs this mapping to callNotifyOffloadSuccesswith the original task identity. Keys not included in actual bucket writes are handled by the batch's failure cleanup. Keeping only keys and sizes for a later call does not preserve this task identity or the source replica pin.Consequently, repeated small heartbeat batches can lose most or all useful persistence opportunities. This is not limited to one partially filled bucket at the end of a forward.
Proposed correctness fix
Stop filling when the iterator is exhausted, then emit the nonempty tail bucket during the same call. Reuse the existing bucket write, success notification, and failure cleanup paths. Retain the key count limit, byte limit, oversized object handling, and empty input behavior.
The patch is limited to
mooncake-store/src/storage_backend.cppandmooncake-store/tests/file_storage_test.cpp.Validation completed on the tested checkout:
The real offload persistence regression fails before the fix and passes afterward.
FileStorage tests: 29 passed.
StorageBackend tests: 98 passed.
Standalone wheel test: 11 of 11 SSD objects and checksum reads passed with the original key limit of 500.
Full repository tests were not run.
Flushing small tails can produce smaller buckets and more I/O operations. A future cross-batch aggregation design would need to retain complete task ownership and release it correctly on all success, timeout, and failure paths.
Separate performance observation
Fixing persistence does not guarantee a speedup for a repeated sequential scan. In our workload, the unique KV working set was approximately 1,973 GiB, while available SSD cache was much smaller. Under eager Put-triggered offload and LRU eviction, the beginning of a new scan overwrote SSD objects from the previous scan before their requests were reached.
A controlled observation found all 152 SSD objects present before a DRAM-only reset were evicted about 54 seconds into the following scan, before the matching late request. The benchmark consequently recorded zero SSD lookup hits. An isolated request for an SSD-resident prompt did successfully read 152 objects, so this was distinct from a broken SSD read path.
Optional admission policy evaluated separately
We also evaluated an experimental local default-off policy controlled by
MOONCAKE_OFFLOAD_READ_ADMISSION_THRESHOLD:0 preserves eager Put-triggered offload.
A positive value counts valid demand metadata lookups for resident memory replicas and queues offload at that threshold. Our experiments use 2.
Duplicate keys within a batch count once.
Internal offload source lookups and idempotent Put healing lookups do not count as demand reads.
Existing SSD replicas and existing offload tasks are excluded.
The existing queue, source pinning, notification, and failure paths are reused.
These counts represent demand metadata lookups, rather than confirmed successful payload reads. Counters are volatile and reset when metadata is removed or the Master restarts. This policy is incompatible with offload-on-evict and is rejected in that combination.
Forty distinct targeted tests passed for admission behavior, client read-only queries, and existing offload scenarios, including a real RPC regression for idempotent Put. This optimization is a separate policy proposal; the tail flush correctness fix does not require it.
Discussion requested
Would maintainers prefer an immediate partial-bucket flush, or an aggregation design that explicitly retains full offload task ownership across batches? PR #4739 contains only the tail-flush correctness fix. The optional admission policy is outside that PR and is not claimed to be available upstream.
@github-actions[bot] commented on GitHub (Oct 17, 2026):
Thanks for opening this issue, @MarinaMiao!
46422913A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide