Repository navigation
[Store] Walk tenants through shard-backed cursors and build the removes on them - #4796
Conversation
Add the iteration primitives that the remaining phase-3 steps of kvcache-ai#4557 build on. VisitTenants names every tenant that holds an object and calls back with no lock held. ReadCursor and WriteCursor walk one tenant's objects under each shard's read or write lock, and hand out the same Object view (key, generation, ref, metadata, state) as the route's cursor, so the swap to NamespaceTable keeps the loop bodies as they are. Only the tests use them so far. Co-authored-by: CAICAIIs <3360776475@qq.com>
RemoveByRegex and both RemoveAll overloads now walk the tenant with ReadCursor, keep the ObjectRef of each object that looks removable, and come back to it under its own write lock to check it again and remove it. BatchRemove holds each key through MetadataAccessorRW instead of grouping the keys by shard, so like the single-key Remove it now runs the invalid-memory-replica cleanup first: a key left with no valid replica comes back OBJECT_NOT_FOUND. The four removes share the lease, replica and replication-task checks and the HA mark-removed-and-persist step. The ObjectRef accessor no longer runs the invalid-memory-replica cleanup: the cleanup's KV sync bumps the tenant epoch, which would make RemoveAll suppress its own `cleared`. Its only other callers are the durable finalizers, which run with the oplog on, where the cleanup was already a no-op. RemoveAll now visits only the tenants that hold an object, so it no longer drops the empty tenant rows a failed standby restore left behind. The restore rollback drops them itself. The RemoveAll test hook now fires once per tenant instead of once per shard, and RemoveByRegex logs the keys it skips at VLOG(1). Co-authored-by: CAICAIIs <3360776475@qq.com>
Icedcoco
left a comment
There was a problem hiding this comment.
Thanks for putting this together. I understand this PR is groundwork for the #4557 migration, and the tenant-scoped cursors seem like a useful step in that direction.
I had a question about RemoveTenantObjects: it collects all eligible ObjectRefs for a tenant before deleting them. For tenants with a large number of objects, that could require a sizable temporary allocation. Do you think buffering the full candidate set is acceptable for the expected workloads, or would bounded batches be worth considering?
One other point for discussion: MarkRemovedAndPersist marks replicas as REMOVED before appending the oplog entry, but doesn’t restore their status if the append fails. I noticed the previous inline removal code had the same behavior, so this may be outside the scope of this PR. Since the logic is now centralized, though, should we handle the rollback here or track it separately?
Curious to hear your thoughts on both.
MarkRemovedAndPersist marked the object's completed replicas REMOVED before appending the REMOVE. The append can still fail after the slot is reserved (the writer stopping or failed, or the entry rejected), and then no durable callback ever drops those replicas: the object is left unreadable, and no remove can take it, since every remove wants all replicas complete. Collect the replica ids first and mark the replicas only once the append succeeds, so a failed append leaves the object as it was. The durable finalizer takes the object's shard lock, which the caller holds, so it cannot run before the marks land.
|
@Icedcoco Thanks for the careful read! Buffering the candidates. It's a consequence of the cursor contract: the cursor holds the shard lock across the loop body, so the body can neither tear an object down nor block on the oplog reservation. It keeps the REMOVED without rollback. Good catch. |
…20261021 * upstream/main: (507 commits) [Reshard] Plan Store KV reuse across TP, PP and host layouts (kvcache-ai#4762) [Store] Walk tenants through shard-backed cursors and build the removes on them (kvcache-ai#4796) [Store-RS][Bugfix] Match TENT Request Layout to the C ABI (kvcache-ai#4732) [TransferEngine] Recover auto GID after data-path retry exhaustion (kvcache-ai#4290) [Store] Add the namespace layer over the object route (kvcache-ai#4721) [Store] Fix silent loss of offload keys deferred to the ungrouped pool (kvcache-ai#3006) (kvcache-ai#3009) [Store] Remove redundant alignment headroom from DFS shard allocation (kvcache-ai#4753) [CI/Build] Gate ROCm hardware tests and bound nightly Docker builds (kvcache-ai#4750) [Bugfix][Store] Fix double-counting of segment OpLog metrics in OpLogApplier (kvcache-ai#4771) [Store] Skip hot-cache writeback for device-resident read destinations (kvcache-ai#4764) [Reshard] Add bounded in-flight execution for weight transfers (kvcache-ai#4628) [Store] Avoid racing with detached hot-cache fills during key scans (kvcache-ai#4334) [Store] HA: drain the master on SIGTERM/SIGINT before exit (part 1 of kvcache-ai#4710) (kvcache-ai#4745) TENT: report a notification the receiver could not take (kvcache-ai#4754) [Store] Reach object metadata through a per-key accessor contract (kvcache-ai#4717) [Store][Bugfix] Fix DFS sync client test retry failure (kvcache-ai#4781) [Wheel] Keep integer dtype for typed_ragged rows when a row is an empty list (kvcache-ai#4611) [Doc] Sync README news onto the docs homepage (kvcache-ai#4767) [CI] remove stale Torch version binding after PG JIT (kvcache-ai#4760) [TransferEngine] Add buffer-protocol and page-gathered batch transfer APIs (kvcache-ai#4763) ...
…-ranges-20261021 * upstream/main: (394 commits) [Reshard] Plan Store KV reuse across TP, PP and host layouts (kvcache-ai#4762) [Store] Walk tenants through shard-backed cursors and build the removes on them (kvcache-ai#4796) [Store-RS][Bugfix] Match TENT Request Layout to the C ABI (kvcache-ai#4732) [TransferEngine] Recover auto GID after data-path retry exhaustion (kvcache-ai#4290) [Store] Add the namespace layer over the object route (kvcache-ai#4721) [Store] Fix silent loss of offload keys deferred to the ungrouped pool (kvcache-ai#3006) (kvcache-ai#3009) [Store] Remove redundant alignment headroom from DFS shard allocation (kvcache-ai#4753) [CI/Build] Gate ROCm hardware tests and bound nightly Docker builds (kvcache-ai#4750) [Bugfix][Store] Fix double-counting of segment OpLog metrics in OpLogApplier (kvcache-ai#4771) [Store] Skip hot-cache writeback for device-resident read destinations (kvcache-ai#4764) [Reshard] Add bounded in-flight execution for weight transfers (kvcache-ai#4628) [Store] Avoid racing with detached hot-cache fills during key scans (kvcache-ai#4334) [Store] HA: drain the master on SIGTERM/SIGINT before exit (part 1 of kvcache-ai#4710) (kvcache-ai#4745) TENT: report a notification the receiver could not take (kvcache-ai#4754) [Store] Reach object metadata through a per-key accessor contract (kvcache-ai#4717) [Store][Bugfix] Fix DFS sync client test retry failure (kvcache-ai#4781) [Wheel] Keep integer dtype for typed_ragged rows when a row is an empty list (kvcache-ai#4611) [Doc] Sync README news onto the docs homepage (kvcache-ai#4767) [CI] remove stale Torch version binding after PG JIT (kvcache-ai#4760) [TransferEngine] Add buffer-protocol and page-gathered batch transfer APIs (kvcache-ai#4763) ...
Description
Step 10 of #4557:
MasterServicewalks a tenant and its objects through shard-backed cursors, andRemove,RemoveByRegex,BatchRemoveand bothRemoveAlloverloads are built on that walk and on the per-key accessor from step 9 (#4717).The removes each walked the 1024 shards by hand, found the tenant's state, iterated its map, and carried their own copy of the lease, replica and replication-task checks and of the HA mark-removed-and-persist step.
VisitTenantscalls back with each tenant that holds an object, holding no lock, so the callback may reach any key.ReadCursor(tenant)andWriteCursor(tenant)walk one tenant's objects under each shard's read or write lock and hand out the sameObjectview (key, generation,ref(), metadata, state) as the route's cursor, so the swap in step 15 keeps the loop bodies as they are. A write cursor may change an object's metadata and state but not publish or tear one down; a body that acts later keepsref()and comes back throughMetadataAccessorRW(service, tenant_id, ref).RemoveByRegexand bothRemoveAlloverloads go through oneRemoveTenantObjects: aReadCursorpass names the objects that look removable, and each is checked again and removed under its own write lock, so an object republished meanwhile is left alone.BatchRemoveholds each key throughMetadataAccessorRWinstead of grouping the keys by shard, so like the single-keyRemoveit runs the invalid-memory-replica cleanup first. All four shareCheckRemovableandMarkRemovedAndPersist.ObjectRefaccessor. It no longer runs the invalid-memory-replica cleanup: that cleanup's KV sync bumps the tenant epoch, which would makeRemoveAllsuppress its owncleared. Its only other callers are the durable finalizers, which run with the OpLog on, where the cleanup is a no-op.RemoveAllnow visits only tenants that hold an object, so it no longer sweeps the empty rows a failed standby restore left behind; the restore rollback drops them itself.RemoveAlltest hook fires once per tenant instead of once per shard.RemoveByRegexlogs the keys it skips atVLOG(1)instead ofLOG(WARNING).Until the swap,
RemoveAll(force)walks every shard once per tenant (VisitTenantsthen oneReadCursoreach), where it used to visit each shard once; the namespace table removes that multiplier. No RPC, standby snapshot or OpLog format changes.Part of the code comes from #4370 by @CAICAIIs.
Module
mooncake-store)Type of Change
How Has This Been Tested?
Test commands:
Test results:
Unit tests pass
Integration tests pass (if applicable)
Manual testing done (describe below)
New
master_service_tenant_iteration_test(7 cases):VisitTenantsnames only tenants holding objects, aReadCursorvisits each object of its tenant once, leaving a cursor early releases its shard, aWriteCursorchanges state in place,RemoveByRegexstays in its tenant,RemoveAllwalks every tenant, andRemoveAllleaves leased objects unless forced.Full
cteston top oforigin/main(37642c5): 250 of 254 pass.pybind_client_testtimed out under-j8and passes alone (272 s). The other three are known local environment failures, unrelated to this change:tcp_transport_testandtransfer_metadata_testneed a network/RDMA environment this host lacks, andordered_oplog_writer_testdoes not link locally (libaio). Built with GCC 16 (Release,BUILD_UNIT_TESTS=ON).Formatting:
./scripts/code_format.sh --check --changed-lines --base origin/mainis clean with clang-format 20.1.8.pre-commit run --files <changed files>: codespell passes; cmake-format only rewrites lines inmooncake-store/tests/CMakeLists.txtthat this PR does not touch (already on main), so those edits are left out.Checklist
./scripts/code_format.shmaster_service_tenant_iteration_test)AI Assistance Disclosure
AI assistance was used for the refactor and to draft this description. The submitter has reviewed every changed line and can defend the change end to end.