mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #4431] [RFC]: [Store] Completing the cross-client move/migration control plane (DrainJob HA recovery, task lifecycle APIs, surface parity) #1118
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#1118
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 @stmatengss on GitHub (Sep 30, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/4431
Changes proposed
Summary
Mooncake Store already has a solid data plane for cross-client replica movement:
CopyStart/CopyEnd/CopyRevokeandMoveStart/MoveEnd/MoveRevoke(plus theDynamicReplicaCopy*variants),REPLICA_COPY/REPLICA_MOVEtask queue (CreateCopyTask/CreateMoveTask/QueryTask/FetchTasks/MarkTaskToComplete), andDrainJobthat fans out into per-key move tasks.The control plane around that data plane is incomplete, however. This RFC inventories the existing interfaces, lists the gaps, and proposes a phased completion plan:
Current state (inventory)
Synchronous lease RPCs (executor client ↔ master)
CopyStart/CopyEnd/CopyRevokeDynamicReplicaCopyStart/End/Revokefor dynamic replicationMoveStart/MoveEnd/MoveRevokeMoveStartResponse.targetisoptional(degenerates to source-only removal when the target replica already exists); target allocation is memory-segment onlyAll registered in
RegisterRpcService(rpc_service.cpp).Asynchronous task queue
CreateCopyTask(key, targets)/CreateMoveTask(key, source, target)/QueryTask/FetchTasks/MarkTaskToComplete, all registered RPCs.Client::PollAndDispatchTasks→ExecuteTask→Client::Copy/Client::Move).task_manageris one of the three snapshot payloads (MasterSnapshotPayloads{metadata, segments, task_manager}, see master_snapshot_codec.h).Segment-level migration (DrainJob)
CreateDrainJob/QueryDrainJob/CancelDrainJobare declared onWrappedMasterServicebut are not registered as coro_rpc handlers; they are exposed only through the admin HTTP server (POST /api/v1/drain_jobs,GET /api/v1/drain_jobs/query,POST /api/v1/drain_jobs/cancel,GET /api/v1/segments/status, see master_admin_service.cpp).ScheduleDrainJobTasksin master_service.cpp) expands a job into per-(tenant, key, segment) units, each executed as aREPLICA_MOVEtask, bounded bymax_concurrency(default 4) and retried up tokMaxDrainUnitRetries.OK → DRAINING → DRAINED(or back toOKon cancel/failure) is persisted:MountedSegment.statusis covered by the segment snapshot payload.Cross-media migration (separate mechanism)
memory→SSD offload and SSD→memory promotion are heartbeat-driven (
OffloadObjectHeartbeat/NotifyOffloadSuccess,PromotionObjectHeartbeat/PromotionAllocStart/NotifyPromotionSuccess/Failure), withEvictDiskReplica/BatchEvictDiskReplicafor disk-replica cleanup. They are not task-based "moves" and are only listed here for completeness.Client surfaces
MasterClient(C++)RealClientpublic APIcreate_copy_task,create_move_task,query_taskMooncakeDistributedStoreGaps
G1. DrainJob state is lost on master failover (correctness)
drain_jobs_is an in-memory map; the snapshot payloads are only{metadata, segments, task_manager}. After a failover:completed_unit_keys,terminal_failed_unit_keys,retry_counts) andmigrated_bytesare gone.DRAININGkeep that persisted status, yet no job exists to drive them toDRAINEDor back toOK. They stay non-allocatable indefinitely — and there is currently no admin endpoint to force a segment status change either.CancelDrainJob/QueryDrainJobon the new leader returnJOB_NOT_FOUND; operators cannot even tell that a drain was in progress.G2. DrainJob has no RPC surface
The three drain methods exist on
WrappedMasterServicebut are absent fromRegisterRpcService, andMasterClienthas no corresponding methods. Anything drain-related requires raw HTTP access to the admin server, bypassing the RPC path that gives clients service discovery, leader redirection, and retries.G3. No enumeration or per-task lifecycle
ListDrainJobs: a lostjob_idmakes the job invisible.ListTasks/ query-by-key:QueryTaskis by UUID only.CancelTask: an individual copy/move task cannot be canceled. Only a whole drain job can be, and only while it has no active tasks. A wrongly targetedCreateMoveTaskcan only be waited out.G4. No batch move primitive
CreateCopyTask/CreateMoveTasktake a single key;MoveStarttakes a single target. Migrating a key set (tenant compaction, rebalancer output) costs one RPC per key with no grouping handle. DrainJob covers the segment-level case only.G5. Drain granularity is segment-name only
CreateDrainJobRequest.segmentstakes segment names. Evacuating a client — the common decommissioning case — requires enumerating its segments out-of-band first; there is noclient_idsfield or resolution helper.G6. Surface parity
docs/.Proposal
Phase 1 — DrainJob recovery (correctness)
drain_jobs_(job id, request, status, per-unit progress, retry counts, counters) as a fourth snapshot payload and rebuild the bookkeeping on restore. The tasks a job owns already persist via the task-manager payload, so a restored leader can re-attach to them by id and resume scheduling the remaining units.DRAININGstatus whose owning job was not restored (snapshot skew, job created after the last snapshot) must not be left stuck — either recreate a job for it or reset it toOK, but always with an explicit, observable log + metric.JOB_CREATE/JOB_UPDATE/JOB_TERMINAL) as OpLog entries so standby replay keeps job state fresh instead of snapshot-granular.Phase 2 — Task/job lifecycle APIs (operability)
CreateDrainJob/QueryDrainJob/CancelDrainJob/QuerySegmentStatus(ById)inRegisterRpcServiceand add matchingMasterClientmethods; keep the admin HTTP endpoints as thin wrappers.ListDrainJobs(filter)andListTasks(filter by status/type/client/key)with bounded page size.CancelTask(task_id): PENDING tasks are removed immediately; PROCESSING tasks get a best-effort cancel flag the executor checks between transfer slices (final state still reported viaMarkTaskToComplete).QueryJobResponsewith bounded terminal-failed unit details (key, source segment, last error).Phase 3 — Batching and granularity (ergonomics)
BatchCreateCopyTask/BatchCreateMoveTask: one RPC, one task per key (preserves per-key retry/cancel granularity), returning per-keytask_id | error.CreateDrainJobRequest.client_ids: the master resolves them to the client's mounted segments at validation time (snapshot semantics: later unmounts do not silently shrink the job's scope).Phase 4 — Surface parity and docs
mooncake_store_create_move_task/mooncake_store_create_copy_task/mooncake_store_query_task.create_drain_job/query_drain_job/cancel_drain_job, plus the list/cancel APIs from Phase 2.POST /api/v1/move_tasks,GET /api/v1/tasks/{id}, and adocs/page for the full drain/task admin API.Non-goals / relation to existing work
MoveStartsingle-target invariant is proposed; multi-destination "move" remains expressible as copy + remove.Compatibility
DRAININGfail-safe.Open questions
DRAININGsegments on restore: auto-reset toOK, auto-recreate a drain job, or require an explicit admin decision (with a new force-status endpoint)?CancelTaskon PROCESSING tasks: is a best-effort flag checked between transfer slices enough, or do we want preemption at the Transfer Engine batch level?Before submitting a new issue...
@github-actions[bot] commented on GitHub (Sep 30, 2026):
Thanks for opening this issue, @stmatengss!
11641725A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide
@SuperGoodGame commented on GitHub (Oct 3, 2026):
Hi @stmatengss, I'd like to take the snapshot-only DrainJob recovery part of Phase 1. I've started a local implementation.
The plan is to reuse the existing master snapshot/storage path to persist job state, progress, retry budgets and task associations, then resume after cold restart or snapshot-only standby promotion. Initial investigation also found that DRAINING/DRAINED allocator recovery, client remount and in-flight COPY/MOVE bookkeeping need to remain consistent for safe continuation.
I'll keep legacy snapshots readable and add on-disk restart and failure regressions. For orphaned DRAINING segments, I propose preserving readable data, keeping allocation blocked, and reporting logs/metrics for explicit operator recovery through the existing segment-status API.
Job OpLog and batch-generated standby snapshot recovery would follow separately. This slice is separate from the native RPC work in #4436. Please let me know if someone is already working on it.