mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #3826] [Bug]: [NoF] Remount after HA recovery may reallocate offsets referenced by restored replicas #979
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#979
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 @cage-goat on GitHub (Sep 2, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/3826
Bug Report
Problem Description
Based on the current main branch, Mooncake restores NoF replica descriptors during HA + OpLog recovery, including the endpoint, offset, size, and status.
The restored NoF replicas are associated with a
DummyBufferAllocator. This dummy allocator preserves the descriptors and endpoint but does not maintain the actual allocation state.When the same NoF namespace is subsequently re-registered or remounted, Mooncake creates a new real allocator without importing the restored NoF descriptors into it.
As a result, the new allocator may still consider offsets referenced by restored replicas to be free and allocate them to new objects.
Potential Impact
Mooncake indexes metadata by
(tenant_id, user_key)and does not check whether descriptors belonging to different keys reference the same physical range.Different keys may therefore reference the same
(endpoint, offset range). A later write can overwrite the physical data of an older object while its metadata remainsCOMPLETEand readable. A subsequentGetmay then return incorrect data, resulting in silent data corruption.Questions for the Community
Before submitting...
@github-actions[bot] commented on GitHub (Sep 2, 2026):
Thanks for opening this issue, @cage-goat!
318081269A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide
@he-yufeng commented on GitHub (Sep 2, 2026):
Verified both halves of this against current main, and the report holds.
The restore side:
RestoreFromStandbySnapshotrebuilds NoF replicas on aDummyBufferAllocator(master_service.cpp, theis_nof_replica()branch), which preserves endpoint/offset/size but no allocation state. The segment loop skips non-memory segments entirely (if (!seg.is_memory_segment) continue;), so a restored NoF endpoint never lands ininvalid_replica_endpoints_. Since the dummy allocator is kept alive by the standby keepalive map,has_invalid_nof_handle()is false as well. Net: a restored NoF replica isIsReplicaReadableimmediately after promotion, before any remount.The remount side:
ReMountNoFSegment->ScopedNoFSegmentAccess::ReMountSegment->MountSegmentcreates a fresh real allocator over the namespace range and imports nothing. There is no NoF counterpart to the memory-side remount flow (allocator replacement, descriptor rewrite, invalid-endpoint erase, lease grant). So offsets referenced by readable restored replicas look free to the new allocator, and the next write can land on them while the old key still serves a COMPLETE, readable replica. That is the silent-corruption sequence you describe.On your questions, my read:
invalid_replica_endpoints_treatment memory segments get) and only clear them on a remount that has imported the restored ranges.I can take the fix. My current leaning is to do the fail-closed gate first (small, kills the corruption window), then the range import as a second step so restored NoF data actually becomes usable again. If you or the HA folks prefer a different split, say so and I will follow it. One coordination note: the SegmentPool work in #3705/#3707 moves segment lifecycle code around, so whichever lands second will need a rebase pass.
@cage-goat commented on GitHub (Sep 3, 2026):
@he-yufeng Thanks for the thorough investigation! I support the proposed two-step approach.