Skip to content

[Store] Add a per-tenant eviction watermark so quota pressure evicts instead of rejecting - #3805

Merged
Aionw merged 3 commits into
kvcache-ai:mainfrom
gongwei-130:feat/tenant-eviction-watermark
Sep 14, 2026
Merged

Aionw merged 3 commits into
kvcache-ai:mainfrom
gongwei-130:feat/tenant-eviction-watermark

Conversation

@gongwei-130

@gongwei-130 gongwei-130 commented Aug 31, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Once multi-tenancy partitions the pool, the background evictor stops protecting most tenants.

EvictionThreadFunc gates on a pool-global ratio:

double used_ratio = segment_manager_.GetMemoryUsage().used_ratio();
if (used_ratio > eviction_high_watermark_ratio_ ||
    (need_mem_eviction_ && eviction_ratio_ > 0.0)) {

BuildEffectiveQuotaAssignmentsImpl apportions effective = capacity * requested / sum(requested), so effective quotas sum to exactly capacity. At most one tenant's share can therefore exceed eviction_high_watermark_ratio. Every other tenant reaches its own ceiling while the pool is still under the pool-wide watermark, and BatchEvict never runs on its behalf.

The quota path cannot arm it either: need_mem_eviction_ is only set next to inc_put_start_alloc_failures(), and a quota rejection returns before allocation is attempted.

What remains is the synchronous fallback in the admission retry loop:

for (int attempt = 0; attempt <= kMaxTenantQuotaEvictionRetries; ++attempt) {
    ...
    EvictTenantMemoryForQuota(object_id.tenant_id, quota_deficit_bytes);
}

quota_deficit_bytes is exactly the shortfall of the object being admitted — no headroom is ever built, so under concurrency every writer redoes the work and races for the same freed extent, and after 2 retries the write is rejected with TENANT_QUOTA_EXCEEDED.

Measured

A 7168 GiB two-tenant cluster on 0.3.13 with a 90/10 split against the default 0.90 watermark. The tenant ceiling and the pool trigger landed 11 MB apart:

tenant effective quota = 900/(900+100) x 7168 GiB = 6451.20 GiB
pool eviction trigger  =        0.90    x 7168 GiB = 6451.20 GiB

Over the following 13 hours:

signal value
batch put items rejected 5-13%, 12,456 TENANT_QUOTA_EXCEEDED total
mooncake_tenant_quota_charged_bytes frozen at the ceiling, 21 MB under it, for the whole window
mooncake_tenant_evict_bytes_total 25-198 MB/s throughout — the tenant path was working, it just could never get below the ceiling
master_attempted_evictions_total unchanged — BatchEvict never fired
master_put_start_alloc_failures_total 0
mooncake_tenant_quota_over_quota 0 — a tenant pinned at its ceiling is not over it

Raising that tenant's share above the watermark moved the same cluster to 90-105 put items/s at a 0.005% reject rate. Twenty times the write pressure, two thousand times fewer rejections, same day and same hardware.

That workaround only helps one tenant, though, which is why this wants a mechanism rather than a runbook entry. Protecting N actively-writing tenants by lowering the pool watermark would need eviction_high_watermark_ratio < 1/N — under 0.50 for two, under 0.33 for three — which strands more of the pool than it saves. It would also be wrong: BatchEvict is global LRU, so at a 0.45 watermark a hot tenant's admissions would evict a cold tenant's data while the hot one still could not pass its own ceiling.

Change

Adds tenant_eviction_high_watermark_ratio: a background pass that evicts any tenant over its own watermark down to (watermark - eviction_ratio) of its effective quota. This gives the tenant path the same create-headroom-ahead-of-demand behaviour the pool path already has, instead of freeing exactly one object's deficit on the write path.

void MasterService::EvictTenantsOverWatermark() {
    if (!enable_multi_tenants_ ||
        tenant_eviction_high_watermark_ratio_ <= 0.0) {
        return;
    }
    const double target_ratio = std::max(
        0.0, tenant_eviction_high_watermark_ratio_ - eviction_ratio_);
    for (const auto& snapshot : tenant_quota_table_.ListTenantSnapshots()) {
        ...
        EvictTenantMemoryForQuota(snapshot.tenant_id, target_bytes);
    }
}

Notes on the shape of it:

  • EvictTenantMemoryForQuota is reused unchanged, so lease, hard-pin and soft-pin handling, the two-pass soft-pin escalation and the offload path all behave exactly as they do today. The only new thing is when it is called and how much it is asked for.
  • Called outside the pool-level branch, deliberately. The case this exists for is a tenant over its own watermark while the pool is under the pool-wide one, so it must not be gated on the pool condition having fired.
  • Throttled to 1 Hz. The eviction thread wakes every 10 ms (kEvictionThreadSleepMs), and this walks every registered tenant and locks every quota shard. Quota pressure builds over seconds; the synchronous admission fallback still covers the gap in between.
  • No new metric. EvictTenantMemoryForQuota already increments mooncake_tenant_evict_bytes_total.

Default

0.0, i.e. disabled — no behaviour change for anyone who does not opt in. 0.9 mirrors the pool default and is what we run. Happy to flip the default if you would rather this be on out of the box; it seemed better to make the first version obviously safe to merge and let you decide the policy.

The flag is added as an optional config parameter rather than a RequiredParam, so existing master.json / master.yaml files keep loading unchanged.

Tests

In master_service_tenant_quota_test.cpp, using the existing MasterServiceTenantQuotaTest friend fixture:

  • TenantEvictionWatermarkIsDisabledByDefault — the pass is a no-op at 0.0
  • TenantEvictionWatermarkLeavesTenantsBelowItAlone
  • TenantEvictionWatermarkEvictsDownToTargetRatio — 0.96 -> at most 0.85
  • TenantEvictionWatermarkOnlyTouchesTenantsOverIt — a quiet tenant does not pay for a noisy one

The test config sets default_kv_lease_ttl(0) (eviction skips live leases, so with the 10 s default these would pass for the wrong reason) and eviction_high_watermark_ratio(1.0) (keeps the pool-wide evictor from accounting for the freed bytes).

Not covered by tests: the contention that produces the 5-13% loss needs concurrent writers at a tenant ceiling. That part is from production measurement, not a unit test.

Caveat

I have not built this locally — the machine I wrote it on lacks boost, glog, gflags and yalantinglibs, so CI will be the first real compile. The change is mechanical plumbing plus one new function, but please treat the build as unverified on my side. I can iterate quickly on anything CI turns up, and can test a built image against the cluster above, where the failure reproduces on demand by setting the tenant share to 90% with the watermark at 0.90.

Refs #3741, where the same measurement is written up alongside the silent-eviction branch of the same code path.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional per-tenant eviction high-watermark to the master’s background eviction loop so that quota pressure can create headroom proactively (per tenant) instead of relying on synchronous per-object eviction/retry and eventual TENANT_QUOTA_EXCEEDED rejections.

Changes:

  • Introduces tenant_eviction_high_watermark_ratio config/gflag (default 0.0, disabled) and threads it through master config plumbing.
  • Implements MasterService::EvictTenantsOverWatermark() and schedules it (1 Hz) from EvictionThreadFunc when multi-tenancy is enabled.
  • Adds unit tests covering the disabled-by-default behavior, no-op when below watermark, eviction down to (watermark - eviction_ratio), and isolation between tenants.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
mooncake-store/tests/master_service_tenant_quota_test.cpp Adds tenant-watermark-specific config helper and unit tests for the new background pass.
mooncake-store/src/master.cpp Adds gflag + validator + config load/override wiring for the tenant watermark ratio.
mooncake-store/src/master_service.cpp Implements and schedules the tenant watermark eviction pass in the eviction thread.
mooncake-store/include/types.h Adds default constant DEFAULT_TENANT_EVICTION_HIGH_WATERMARK_RATIO.
mooncake-store/include/master_service.h Declares the new private eviction-pass method and check interval constant.
mooncake-store/include/master_config.h Threads the new config value through the various config wrapper/builder types.
mooncake-store/conf/master.yaml Documents the new setting and adds it to the example config.
mooncake-store/conf/master.json Adds the new setting to the example config.
Suppressed comments (5)

mooncake-store/tests/master_service_tenant_quota_test.cpp:1269

  • This direct call to EvictTenantsOverWatermark() will not compile because the method is private on MasterService and TEST_F creates a derived fixture type (friendship isn't inherited). Call the fixture helper (e.g. RunTenantEvictionWatermarkPass(service)) instead.
    service.EvictTenantsOverWatermark();

mooncake-store/tests/master_service_tenant_quota_test.cpp:1288

  • This direct call to EvictTenantsOverWatermark() will not compile because the method is private on MasterService and TEST_F creates a derived fixture type (friendship isn't inherited). Call the fixture helper (e.g. RunTenantEvictionWatermarkPass(service)) instead.
    service.EvictTenantsOverWatermark();

mooncake-store/tests/master_service_tenant_quota_test.cpp:1308

  • This direct call to EvictTenantsOverWatermark() will not compile because the method is private on MasterService and TEST_F creates a derived fixture type (friendship isn't inherited). Call the fixture helper (e.g. RunTenantEvictionWatermarkPass(service)) instead.
    service.EvictTenantsOverWatermark();

mooncake-store/tests/master_service_tenant_quota_test.cpp:1333

  • This direct call to EvictTenantsOverWatermark() will not compile because the method is private on MasterService and TEST_F creates a derived fixture type (friendship isn't inherited). Call the fixture helper (e.g. RunTenantEvictionWatermarkPass(service)) instead.
    mooncake-store/src/master_service.cpp:9155
  • Same as the trigger log above: this INFO log will repeat every check interval for every tenant remaining over watermark. Prefer VLOG(1) (or a rate-limited INFO) to avoid log flooding under sustained quota pressure.
        LOG(INFO) << "[TENANT-EVICT-DONE] tenant=" << snapshot.tenant_id
                  << " freed_bytes=" << result.freed_bytes
                  << " evicted_objects=" << result.evicted_objects;

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

.set_tenant_eviction_high_watermark_ratio(
tenant_high_watermark_ratio)
.build();
}
Comment on lines +1248 to +1251
// the pool-wide watermark. EvictionThreadFunc gates on a pool-global used_ratio
// (strictly greater), and the quota path cannot arm it either -- need_mem_evic-
// tion_ is only set next to inc_put_start_alloc_failures(), and a quota
// rejection returns before allocation is attempted. Admission then falls back
Comment thread mooncake-store/src/master_service.cpp Outdated
Comment on lines +9146 to +9151
LOG(INFO) << "[TENANT-EVICT-TRIGGER] tenant=" << snapshot.tenant_id
<< " used_ratio=" << used_ratio << " high_watermark="
<< tenant_eviction_high_watermark_ratio_
<< " target_ratio=" << target_ratio
<< " target_bytes=" << target_bytes;
const TenantQuotaEvictionResult result =
@gongwei-130
gongwei-130 force-pushed the feat/tenant-eviction-watermark branch from 5d959dc to 291b0a2 Compare August 31, 2026 19:41
@gongwei-130

Copy link
Copy Markdown
Collaborator Author

CI is green — following up on the "not built locally" caveat in the description, since that turned out to matter.

build-flags and all eight wheel builds passed on the first run, so the config plumbing and EvictTenantsOverWatermark itself compiled clean. The only failure was in my own test file: TEST_F bodies are a class derived from the fixture and friendship does not inherit, so service.EvictTenantsOverWatermark() was inaccessible even though MasterServiceTenantQuotaTest is a friend. Fixed by routing it through a fixture method, which is what the neighbouring helpers in that file already do for the same reason.

Now: build-flags pass, CTest unit tests pass (154/154, master_service_tenant_quota_test included), Check code format pass, all wheel builds pass.

I also verified the format check locally this time with clang-format 20.1.8 and the repo's own scripts/code_format.sh --check --changed-lines, rather than guessing.

@he-yufeng he-yufeng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the right mechanism for the concern I raised on #3788. Arming global BatchEvict on a tenant-quota shortfall forces eviction of other tenants' objects while the pool sits far below the pool-wide watermark, and it still cannot relieve the constrained tenant. A per-tenant watermark makes quota pressure evict the tenant's own data, which is precisely the safe shape for the collision regime you measured in #3741.

I read the implementation against that concern and the mechanics hold: the (watermark - eviction_ratio) target mirrors the pool path's hysteresis, used_ratio is computed per tenant against its own effective quota, EvictTenantMemoryForQuota is reused unchanged so pin/lease/offload semantics are preserved, the pass is deliberately outside the pool-level branch (a tenant over its own watermark while the pool is under the pool-wide one is exactly the case that needs it), and the 1 Hz throttle plus the default-off flag make it opt-in and backward compatible.

Coordination question for you and huangxiaobai-ydmy rather than a blocker: #3788 fixes the same 13-hour incident from the admission side (arming need_mem_eviction_ on quota shortfall, deficit+headroom eviction, global metrics coverage, the reject flag). This PR is the cleaner fix for the collision regime, so I would rather land this mechanism and keep from #3788 the parts that are orthogonal (headroom eviction, metrics coverage, the config warnings), dropping its arming line. If you two see them as composable instead, the arming should at least be scoped the way I suggested there, but a per-tenant watermark makes that line unnecessary.

@gongwei-130

Copy link
Copy Markdown
Collaborator Author

Thanks @he-yufeng — agreed on the split, and I'd like to land it exactly the way you described.

On the coordination question. #3788 and this PR are not composable as written, and the reason is the one you identified on #3788: need_mem_eviction_ arming is unconditional on every non-final over-quota attempt, while BatchEvict picks victims globally with no tenant weighting. That is correct only in the collision regime and actively harmful in the opposite one — a small-quota tenant streaming over-quota writes keeps the flag armed, and every armed pass eats roughly eviction_ratio of other tenants' hot objects below the pool watermark without ever relieving the constrained tenant, because its charged bytes only drop when its own objects get picked. So the case that motivated the line is the only case it helps, and a per-tenant watermark covers that case directly by construction: the pass reads used_ratio against the tenant's own effective quota and routes through EvictTenantMemoryForQuota, so the bytes freed are always the constrained tenant's own. Keeping both would mean the tenant pass creates the headroom and the arming line separately fires a global pass to do it again, less precisely.

Concretely, what I'd propose to @huangxiaobai-ydmy:

  • Drop from [Bugfix][Store] Fix tenant-quota eviction treadmill and watermark collision #3788: the two need_mem_eviction_.store(true, ...) lines in PutStart/UpsertStart, and the TenantQuotaWatermarkCollisionArmsNeedMemEviction test that pins that behaviour. Neither of your two scoping options is needed once the tenant watermark exists — option 1 (effective_quota_bytes >= watermark_bytes) is a proxy for "the tenant pass would have handled it anyway", and option 2 you already noted misses the regime I measured.
  • Keep in [Bugfix][Store] Fix tenant-quota eviction treadmill and watermark collision #3788, all orthogonal to this PR: the headroom target in EvictTenantMemoryForQuota (deficit + eviction_ratio * effective_quota with the overflow saturation), the global inc_eviction_success/inc_mem_eviction_* coverage so master_attempted_evictions_total is not silent on the tenant path, MaybeWarnTenantQuotaConfig, and -tenant_quota_reject_on_exceed.

The headroom change is worth calling out as genuinely complementary rather than redundant: it fixes the synchronous path, which still runs whenever admission outruns the 1 Hz background pass, and it is what stops the treadmill inside a single burst. This PR fixes the steady state; #3788's headroom fixes the transient. They compose cleanly once the arming line is gone.

One ordering note: the tenant_quota_reject_on_exceed semantics get sharper with a tenant watermark in place, since "reject immediately" then means "reject and let the background pass make room for the next attempt" rather than "reject and hope the pool crosses its watermark". No code conflict either way — the two PRs touch disjoint parts of EvictTenantMemoryForQuota's caller set — so whichever lands first, the other rebases without a merge conflict beyond the master_config.h plumbing blocks.

Copilot review, addressed in 58bb9a5. Both remaining items:

  • [TENANT-EVICT-TRIGGER]/[TENANT-EVICT-DONE] dropped from LOG(INFO) to VLOG(1). The pass runs at 1 Hz and emits two lines per over-watermark tenant, so sustained pressure across many tenants floods the log for what is by then routine steady-state behaviour. The pool-level [EVICT-TRIGGER]/[EVICT-DONE] pair stays at INFO — one line, gated on a pool-wide threshold, genuinely an event.
  • Rewrapped the test comment that split need_mem_eviction_ across a line break.

The earlier private-method finding was already fixed in the previous push, and CI was green on it.

I have not built this locally — it is a log-level change plus a comment, and VLOG is already in use in the same translation unit — so I am relying on CI, but the format check passes locally against scripts/code_format.sh --check --changed-lines with clang-format 20.1.8.

@gongwei-130

Copy link
Copy Markdown
Collaborator Author

CI note on the previous run (33443257136): two jobs failed, and I do not believe either is caused by this PR. Retriggered with an empty commit since I only have read access on this repo and cannot re-run the failed jobs directly.

1. CTest unit tests — MasterServiceSSDSnapshotTest.EvictObject, 153/154 otherwise green.

Pre-existing flake, not specific to this branch. The same test failed today on two unrelated branches: vin/store-upload-planning and czh/pg-enq-fix, neither of which touches tenant quotas.

The failure is CompareServiceState(state_before, state_after) at master_service_test_for_snapshot_base.h:799, preceded by ~30 replica_list mismatch for key: test_keyNNNN lines. state_before is captured with CaptureServiceState(service.get()) on the live service while its eviction thread is still running — the surrounding log shows [EVICT-TRIGGER] memory_ratio=1 high_watermark=0.9 firing throughout — so any object the live service evicts between the snapshot persist and the capture diverges from the restored copy. Inherent race in a test that deliberately runs under eviction pressure.

For completeness, on why it is not this PR: EvictTenantsOverWatermark is gated on enable_multi_tenants_ && tenant_eviction_high_watermark_ratio_ > 0.0, and the && short-circuits on enable_multi_tenants_ before the steady_clock::now() read, so with multi-tenancy off the pass adds one boolean test per loop iteration and nothing else. kEvictionThreadSleepMs is unchanged, so the eviction thread's cadence in that test is byte-for-byte what it was before.

2. test-wheel-ubuntu (ubuntu-22.04, 3.12) — test_pg_elastic.TestMooncakePGElasticCPU.test_recovery_p2p_after_graceful_group_destroy.

This is in mooncake-pg; the diff here is mooncake-store only. The failure is rank 2: TimeoutError -- timed out waiting to start replacement plus rank 0: P2P with rank 1 failed — a 4-rank elastic-recovery test over TCP loopback, timing out on a shared runner. The ubuntu-24.04, 3.10 leg of the same suite passed on this same commit.

Happy to file a separate issue for the EvictObject race if it is not already tracked — it is hitting several PRs a day right now. Keeping it out of this PR to avoid mixing an unrelated test fix into the change.

@gongwei-130
gongwei-130 force-pushed the feat/tenant-eviction-watermark branch from 339e0ba to fcfe294 Compare September 1, 2026 17:37
@gongwei-130

Copy link
Copy Markdown
Collaborator Author

Follow-up, and the evidence is now conclusive rather than circumstantial.

The retrigger was an empty commit, so runs 33443257136 and 33455330017 ran against a byte-identical tree (42e436e7). They failed different tests:

run tree CTest failure
33443257136 42e436e7 MasterServiceSSDSnapshotTest.EvictObject
33455330017 42e436e7 MasterServiceSnapshotTest.CleanupStaleHandlesTest

Each test passed in the run where the other failed, 153/154 both times. Same code in, different answer out — the ha/snapshot suite is non-deterministic; this is not a defect in this PR.

The mooncake-pg failure from the first run (test_recovery_p2p_after_graceful_group_destroy) passed on the second, as expected for a loopback timing flake.

On the new one specifically: CleanupStaleHandlesTest fails in the fixture's TestSnapshotAndRestore, on CompareSnapshotDirectories — metadata is 71 bytes in the live service's backup vs 47 in the re-snapshot of the restored service. The test body unmounts a segment twice and relies on stale replicas being reaped lazily, so whether those entries are still present when the teardown snapshot is taken depends on when the background cleanup runs. Not a config difference: the snapshot metadata payload is MasterSnapshotCodec::Encode output over object/replica state, and tenant_eviction_high_watermark_ratio is not part of it.

And the same gating argument as before applies to both: the test constructs a bare MasterService(), so enable_multi_tenants is false and tenant_eviction_high_watermark_ratio is DEFAULT_TENANT_EVICTION_HIGH_WATERMARK_RATIO = 0.0. EvictTenantsOverWatermark is unreachable, and the && in the eviction loop short-circuits on enable_multi_tenants_ before the steady_clock::now() read, so the loop does not even acquire a timestamp it did not acquire before.

Retriggered once more. If the suite keeps rolling dice I would rather fix the flake than keep re-rolling — happy to open a separate PR for it, but not to fold an unrelated test fix into this one.

@catyans catyans left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The default-off watermark is scoped to each tenant's effective quota and reuses the existing pin/lease/offload-aware eviction path while preserving hysteresis. Focused CTest coverage passes; the remaining Ubuntu 22.04 wheel failures are in unrelated mooncake-pg recovery tests.

@gongwei-130

Copy link
Copy Markdown
Collaborator Author

@he-yufeng — could I ask you for a re-run of the failed jobs on this PR when you have a moment? I only have triage on this repo, and POST /actions/runs/:id/rerun-failed-jobs comes back 403 Must have admin rights to Repository, so the only lever I have is force-pushing an empty commit, which is getting noisy on the PR.

The reason I am asking rather than re-rolling again: three runs so far, all against a byte-identical tree (42e436e7 — the retriggers were empty commits, no tree change), and each produced a different failure set.

run CTest unit tests test-wheel-ubuntu (22.04, 3.12)
33443257136 ❌ MasterServiceSSDSnapshotTest.EvictObject ❌ test_pg_elastic…test_recovery_p2p_after_graceful_group_destroy
33455330017 ❌ MasterServiceSnapshotTest.CleanupStaleHandlesTest ✅
33538917197 ✅ 154/154 ❌ same pg test + test_pg_init_functional…test_destroy_and_reinit

Every individual test that failed also passed in at least one of the other runs, so none of them is a deterministic consequence of this diff. Currently 19 pass / 2 fail, where one of the two is CI Gate aggregating the other.

The two mooncake-pg failures are the easy call: both are failing on main itself, independently of this branch —

which is exactly the pair that failed here. This diff is mooncake-store only and does not touch mooncake-pg at all.

For the two ha/snapshot ones, in case it is useful when someone gets around to that suite: EvictObject fails on CompareServiceState after ~30 replica_list mismatch lines, with state_before captured from the live service while [EVICT-TRIGGER] memory_ratio=1 is still firing — anything evicted between the persist and the capture diverges from the restored copy. CleanupStaleHandlesTest fails earlier, on CompareSnapshotDirectories (metadata 71 bytes vs 47), in a test whose body unmounts a segment twice and relies on stale replicas being reaped lazily. Both are timing-dependent by construction. EvictObject has also been failing on vin/store-upload-planning and czh/pg-enq-fix.

And to close off the obvious suspicion — the new pass cannot reach any of these: all four tests construct a bare MasterService() or run outside mooncake-store entirely, so enable_multi_tenants is false and tenant_eviction_high_watermark_ratio is 0.0. EvictTenantsOverWatermark is unreachable, and the && in EvictionThreadFunc short-circuits on enable_multi_tenants_ before the steady_clock::now() read, so the eviction loop does not even take a timestamp it was not taking before. kEvictionThreadSleepMs is unchanged.

Separately, if you think the ha/snapshot races and the two mooncake-pg timeouts are worth fixing rather than living with, I am happy to take them — as their own PRs, not folded into this one.

@gongwei-130
gongwei-130 force-pushed the feat/tenant-eviction-watermark branch from fcfe294 to af2ed54 Compare September 3, 2026 05:09
@gongwei-130

Copy link
Copy Markdown
Collaborator Author

Thanks for the re-run @he-yufeng. That one is a genuine break, but not this PR's and not a flake — and it is already fixed on main.

The re-run failed differently from all three earlier attempts: instead of a timing flake, all six mooncake-pg test modules failed to import:

ModuleNotFoundError: No module named 'mooncake.pg_2_14_0'
ImportError: Mooncake PG was not built against torch==2.14.0.

mooncake/pg.py dispatches to a per-torch-version backend via importlib.import_module("mooncake.pg" + version_suffix), and _build-wheel.yaml was building ep_versions='2.11.0;2.12.0;2.12.1;2.13.0'. Once torch 2.14.0 landed on PyPI, the Run all PG CPU tests with latest PyTorch step started resolving to a torch the wheel has no backend for. That step is gated on ubuntu-22.04 && python 3.12, which is exactly why only that leg failed while ubuntu-24.04, 3.10 stayed green.

It was hitting everyone, not just this branch — same pg_2_14_0 error on dev/oplog-ha-prs/N07 and dev/add-metadata-version.

@UNIDY2002 already fixed it in #3842 (886feb8b, merged 02:53 UTC today), which adds 2.14.0 to ep_versions. The re-run did not pick it up because re-running reuses the merge ref computed when the run was first created, and this branch was still based on 73471dc2.

So I have rebased onto current main (3c841395) rather than re-rolling again, and dropped the empty retrigger commit while I was at it. Diff is unchanged — still +299 across the same 8 files, clang-format --check --changed-lines clean.

Recapping the four attempts, since the picture is now complete:

attempt tree outcome
33443257136 42e436e7 EvictObject + pg elastic — both flakes
33455330017 42e436e7 CleanupStaleHandlesTest — flake
33538917197 42e436e7 pg elastic + pg init — both flakes, also failing on main
re-run of the above 42e436e7 torch 2.14.0 import break — real, repo-wide, fixed by #3842
now rebased on 3c841395 picks up #3842

The three ha/snapshot / mooncake-pg timing flakes are still out there and unrelated to this change; my earlier offer to fix them in separate PRs stands if you think they are worth chasing.

@ykwd

ykwd commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the change — the per-tenant watermark is the right shape for quota pressure that never crosses the pool-wide trigger.

I have two suggestions for this issue, the second of which is out of scope for this PR.

1. Align the multi-tenant path with the single-tenant path.

  • Default tenant_eviction_high_watermark_ratio to 0.9, matching eviction_high_watermark_ratio, instead of leaving it at 0.0 / opt-in.
  • On PutStart / UpsertStart quota shortfall, return TENANT_QUOTA_EXCEEDED immediately and drop the EvictTenantMemoryForQuota + retry loop.

That is the same contract as enable_multi_tenants=false: this put fails, the background evictor builds headroom, the next put can succeed. Keeping a distinct quota error (rather than NO_AVAILABLE_HANDLE) is still useful; the important part is that the write path stops doing synchronous per-object eviction. As it stands, even with this PR, the admission retry remains the hot path for any burst that outruns the 1 Hz pass, so the treadmill this PR is fixing is only half-addressed.

2. Out of scope, but worth discussing: sequential tenant eviction.

EvictTenantsOverWatermark walks over-watermark tenants one by one on the eviction thread and waits for EvictTenantMemoryForQuota to finish before starting the next. With a large working set, evicting tenant A down to (watermark - eviction_ratio) can take long enough that B and C have already run far past their own watermarks.

If a pass finds N tenants over watermark, could we evict them concurrently — e.g. start N threads, one per tenant — instead of serialising them?

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 9, 2026
@gongwei-130

Copy link
Copy Markdown
Collaborator Author

Thanks @ykwd — both done in the head commit, and your instinct on the write path was right for a sharper reason than I had appreciated. Raising one consequence below rather than fixing it here, since it touches how this composes with #3788.

1a. tenant_eviction_high_watermark_ratio now defaults to 0.90, matching eviction_high_watermark_ratio. It stays inert unless enable_multi_tenants is set, and 0.0 remains available as an opt-out. One knock-on: I dropped the set without multi-tenancy startup warning I had added, because with a non-zero default it would fire on every single-tenant master.

1b. PutStart/UpsertStart no longer evict inline. Over-quota returns TENANT_QUOTA_EXCEEDED immediately; the reject metric still fires.

Worth recording why this was more than a tidiness fix. EvictTenantMemoryForQuota walks up to kNumShards — 1024 — taking each shard's write lock and scanning that tenant's whole metadata map per shard, with a second full pass when allow_evict_soft_pinned_objects is on. The retry loop invoked it twice before giving up. So one rejected admission could take ~4k shard write-lock acquisitions and still reject — and the state that triggers it is precisely the state where every other writer is doing the same scan and racing for the same freed extent. That is the treadmill in #3741, and it lived on the write path.

Removing it let me delete the plumbing that only existed to feed it: kMaxTenantQuotaEvictionRetries, the quota_deficit_bytes out-parameter threaded through AllocateAndInsertMetadata, and the deficit_bytes out-parameter on ChargeTenantQuota. EvictTenantMemoryForQuota itself is untouched and still backs the background pass.

One committed behaviour changed, so please look at this specifically. TenantAdmissionEvictsOnlyThatTenantsExpiredObject asserted that a tenant at its ceiling could still be admitted, because admission synchronously evicted its own expired object. That contract is gone by construction. I retargeted the test at the new one as TenantOverQuotaRejectsAdmissionAndBackgroundPassReclaims: the rejected admission leaves the expired object untouched, the pass then reclaims it, the retry is admitted, and a tenant whose data is all hard-pinned keeps rejecting. Also added an EvictTenants() scenario-DSL action and RunTenantEvictForTesting so a test can drive one pass instead of racing the 1 Hz tick.


The consequence I want your and @he-yufeng's call on

Dropping the synchronous fallback opens a window for small-quota tenants, because the headroom this PR creates is a proportion of the quota, not an absolute:

reachable iff  ingest_rate > (1 - watermark) x quota / pass_interval
                           = 0.10 x quota / 1s
tenant quota headroom ingest needed to exhaust it in one pass interval
6451 GiB (the #3741 cluster) 645 GiB 645 GiB/s — not physically reachable
100 GiB 10 GiB 10 GiB/s — marginal on a fast fabric
10 GiB 1 GiB 1 GiB/s — trivially reachable

For the cluster I measured this is unreachable by orders of magnitude, and the production data backs that up: mooncake_tenant_evict_bytes_total ran 25–198 MB/s for 13 hours while charged_bytes stayed frozen at the ceiling. Eviction throughput was never the constraint — the tenant was pinned at 1.0 because deficit-only eviction never builds headroom. That is what this PR fixes. But a 10 GiB tenant can punch through its 1 GiB of headroom and then hard-reject until the next tick.

The cheap fix is to arm the pass instead of evicting inline: an atomic flag set on quota shortfall, OR'd into the 1 Hz gate in EvictionThreadFunc, so the pass runs on the next 10 ms tick rather than up to a second later. The write path still does zero eviction, which is your point; the window closes from 1 s to 10 ms; and because it arms a tenant-scoped pass it only ever frees that tenant's own data — so it does not reintroduce what @he-yufeng objected to about the global need_mem_eviction_ arming in #3788. It would want a floor on the arming interval so a tenant parked at its ceiling cannot drive the pass every 10 ms.

I have deliberately not put that in this PR, because it largely decides whether #3788's headroom change is still needed, and that is your and @he-yufeng's call rather than mine. Happy to add it here or send it separately.

On sequential tenant eviction (your point 2)

Deferring, and I would rather have a measurement first. In the one production window I have numbers for, the tenant path sustained 25–198 MB/s and was not the bottleneck — the missing piece was headroom, not eviction bandwidth. Fanning out N threads across tenants also means N concurrent walks contending on the same 1024 shard write locks, which is the cost that motivated 1b, so I would want evidence that head-of-line blocking is real before paying it. Happy to take it as a follow-up if you have a workload where a slow tenant demonstrably starves the others.

Verification

Built and tested this time, rather than relying on CI — a container on a 224-core box, BUILD_UNIT_TESTS=ON USE_CUDA=OFF WITH_EP=OFF USE_ETCD=OFF:

  • everything compiles, no new warnings in the changed files
  • full ctest: 176/181
  • every tenant/eviction suite green: master_service_tenant_quota_test 35/35, master_service_evict_scenario_test 22/22, plus master_service_tenant_scenario_test, offload_on_evict_test, tenant_quota_test, tenant_quota_ledger_test, tenant_id_test — 8/8 suites
  • the 5 ctest failures are all mine-the-environment, and I chased each: memory_location_test (numa_move_pages: Operation not permitted), posix_file_test and storage_backend_test (io_uring_queue_init: Operation not permitted) all pass once the container is given --privileged; tcp_transport_test and transfer_metadata_test fail on Unable to find metadata storage plugin etcd, an artefact of my USE_ETCD=OFF config, in a subsystem this diff does not touch.
  • scripts/code_format.sh --check --changed-lines clean with clang-format 20.1.8

Rebased onto current main. #2991 collided with this in master_service.h — additive on both sides, so the resolution is just both declaration blocks; the build and the numbers above are from after the rebase.

@catyans catyans left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for digging into the write-path cost — the ~4k shard-lock acquisitions per rejected admission make the tradeoff much more concrete. LGTM on the current head.

Moving tenant eviction out of PutStart/UpsertStart avoids turning quota pressure into a lock-amplification loop. With the tenant watermark now on by default (and 0.0 retained as an opt-out), the reject → background pass → retry contract is coherent, and the rewritten scenario test pins it directly. CI is green.

On the small-quota window: I like the tenant-scoped wakeup as a follow-up, with coalescing plus a minimum per-tenant interval so repeated rejects cannot drive a 10 ms full-shard scan. Once that exists, I agree #3788 should drop its global need_mem_eviction_ arming; its metrics/config diagnostics can still stand independently. I would not hold this PR for that latency improvement, especially now that the one-second behavior is explicit and tested.

@he-yufeng, does that split also match how you see the two PRs composing?

@Aionw

Aionw commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Thanks for PR and dig into this problem.

There is a currently undergoing PR which change metadata manage struct, after that PR #3822 merged, per-tenant eviction should be much easier to implement, I wonder would you mind delaying this PR a little after that merged.

@stmatengss

Copy link
Copy Markdown
Collaborator

Thanks for PR and dig into this problem.

There is a currently undergoing PR which change metadata manage struct, after that PR #3822 merged, per-tenant eviction should be much easier to implement, I wonder would you mind delaying this PR a little after that merged.

Consider merging this PR first. I think this PR is simpler.

…instead of rejecting

Once multi-tenancy partitions the pool, the background evictor stops protecting
most tenants. `EvictionThreadFunc` gates on a pool-global used_ratio:

    double used_ratio = segment_manager_.GetMemoryUsage().used_ratio();
    if (used_ratio > eviction_high_watermark_ratio_ || ...)

`BuildEffectiveQuotaAssignmentsImpl` apportions
`effective = capacity * requested / sum(requested)`, so effective quotas sum to
exactly capacity and at most ONE tenant's share can exceed
eviction_high_watermark_ratio. Every other tenant reaches its own ceiling while
the pool is still under the pool-wide watermark, so BatchEvict never runs on its
behalf. The quota path cannot arm it either -- need_mem_eviction_ is only set
next to inc_put_start_alloc_failures(), and a quota rejection returns before
allocation is attempted, so put_start_alloc_failures stays 0 the whole time.

What is left is the synchronous fallback in the admission retry loop, which
calls EvictTenantMemoryForQuota(tenant, quota_deficit_bytes) -- exactly the
shortfall of the object being admitted, no headroom -- retries
kMaxTenantQuotaEvictionRetries (2) times and then returns
TENANT_QUOTA_EXCEEDED. Under concurrency every writer redoes that work and races
for the same freed extent.

Measured on a 7168 GiB two-tenant cluster running 0.3.13, with a 90/10 split
against the default 0.90 watermark (the two limits landed 11 MB apart):

  - 13 hours with 5-13% of batch put items rejected, 12,456 rejects total
  - mooncake_tenant_quota_charged_bytes frozen at the ceiling for the duration
  - mooncake_tenant_evict_bytes_total running 25-198 MB/s the whole time: the
    tenant path was working, it just could not get below the ceiling
  - master_attempted_evictions_total unchanged: BatchEvict never fired
  - master_put_start_alloc_failures_total 0
  - mooncake_tenant_quota_over_quota 0 -- a tenant pinned AT its ceiling is not
    over it, so that signal never moves

Raising that tenant's share above the watermark moved the same cluster to
90-105 put items/s at a 0.005% reject rate. Twenty times the write pressure,
two thousand times fewer rejections, same day, same hardware. But that fix only
works for one tenant, which is why this needs a mechanism rather than a runbook.

This adds `tenant_eviction_high_watermark_ratio`: a background pass that evicts
any tenant over its own watermark down to (watermark - eviction_ratio) of its
effective quota, giving the tenant path the same create-headroom-ahead-of-demand
behaviour the pool path already has. It reuses EvictTenantMemoryForQuota
unchanged, so lease, hard-pin and soft-pin handling are exactly as before.

Defaults to 0.0, i.e. disabled and no behaviour change for anyone. 0.9 mirrors
the pool default and is what we run; happy to flip the default if maintainers
prefer. The pass is throttled to 1 Hz because the eviction thread wakes every
10 ms and this walks every tenant and locks every quota shard.

Tests cover: off by default, tenants under their watermark untouched, eviction
down to the target ratio, and a tenant over its watermark not causing eviction
in a tenant that is under its own. The contention behaviour that produces the
5-13% loss needs concurrent writers and was measured in production rather than
unit-tested.

Not built locally -- the toolchain here lacks boost, glog, gflags and
yalantinglibs, so CI is the first real compile.

Refs kvcache-ai#3741
The tenant watermark pass runs at 1 Hz and emits a trigger plus a done
line for every tenant still over its watermark, so sustained quota
pressure in a multi-tenant deployment turns it into a log flood. Drop
both to VLOG(1); the pool-level [EVICT-TRIGGER]/[EVICT-DONE] pair stays
at INFO because it is a single line gated on a pool-wide threshold.

Also rewrap the test-file comment that split need_mem_eviction_ across a
line break, which made the symbol ungreppable.
…e path

Addresses @ykwd's review: align the multi-tenant admission path with the
single-tenant one.
1. tenant_eviction_high_watermark_ratio now defaults to 0.90, matching
   eviction_high_watermark_ratio, instead of 0.0 / opt-in. It stays inert
   unless enable_multi_tenants is set, and 0.0 remains available as an
   opt-out. The startup warning for "set without multi-tenancy" is dropped:
   with a non-zero default it would fire on every single-tenant master.
2. PutStart/UpsertStart no longer evict inline. A tenant over its quota is
   rejected with TENANT_QUOTA_EXCEEDED straight away, which is the same
   contract enable_multi_tenants=false already has: the write fails, the
   background evictor builds headroom, the next write succeeds.
The inline path was expensive in exactly the state that triggered it. Each
EvictTenantMemoryForQuota call walks up to kNumShards (1024) shards, taking
the shard write lock and scanning that tenant's whole metadata map per shard,
and runs a second full pass when soft-pinned objects are evictable. The retry
loop invoked it twice before giving up, so one rejected admission could take
~4k shard write locks and still reject -- while every other writer at the
same ceiling did the same scan and raced for the same freed extent.
With the watermark on by default, EvictTenantsOverWatermark keeps each tenant
at (watermark - eviction_ratio) and is the only thing that reclaims for a
tenant, so the deficit-driven inline eviction has nothing left to do.
Removes the now-unreachable plumbing: kMaxTenantQuotaEvictionRetries, the
quota_deficit_bytes out-parameter through AllocateAndInsertMetadata, and the
deficit_bytes out-parameter on ChargeTenantQuota. EvictTenantMemoryForQuota
itself is unchanged and still drives the background pass.
Tests:
- TenantEvictionWatermarkDefaultsToOn: the pass runs with no explicit setting
- TenantEvictionWatermarkOfZeroDisablesThePass: 0.0 still opts out
- TenantAdmissionEvictsOnlyThatTenantsExpiredObject is retargeted at the new
  contract as TenantOverQuotaRejectsAdmissionAndBackgroundPassReclaims: the
  rejected admission leaves the expired object alone, the pass then reclaims
  it, the retry is admitted, and a tenant whose data is all hard-pinned keeps
  rejecting.
- Adds an EvictTenants() scenario-DSL action and RunTenantEvictForTesting so
  a test can drive one pass instead of racing the 1 Hz tick.
@gongwei-130
gongwei-130 force-pushed the feat/tenant-eviction-watermark branch from 7643281 to e3d77d1 Compare September 13, 2026 16:29
@Aionw
Aionw merged commit 662cd44 into kvcache-ai:main Sep 14, 2026
23 checks passed
@gongwei-130
gongwei-130 deleted the feat/tenant-eviction-watermark branch September 14, 2026 04:29
CAICAIIs added a commit to CAICAIIs/Mooncake that referenced this pull request Sep 15, 2026
Resolve the conflicts introduced by three upstream Store changes:

- kvcache-ai#3805 (per-tenant eviction watermark): the write path no longer retries
  inline eviction, so ChargeTenantQuota and AllocateAndInsertMetadata drop
  the quota-deficit out-parameter and PutStart/UpsertStart call admit()
  once. EvictTenantsOverWatermark and its eviction-thread tick land on the
  tenant-first catalog unchanged.
- kvcache-ai#4103 (shrink metadata maps after sweeps): the shard-level
  kStaleHandleCleanupBatchSize/shard loop does not apply, but the leak it
  fixes does. ShrinkBucketsIfSparse moves to common/shrink_buckets.h and is
  applied to each tenant's object route after ClearStaleHandles sweeps and
  after BatchEvict, using the evicted_tenants list that was collected but
  never consumed.
- kvcache-ai#4052 (oplog writer metrics): only the metric call in master_service.cpp.

The tenant-first model keeps its deliberate divergence: empty tenants stay
in the directory (an Empty()+Remove() race would strand a just-inserted
object in an unreachable catalog), so the upstream per-sweep tenant erase is
not carried over.
zchuango added a commit to LinQuickDev/Mooncake that referenced this pull request Sep 28, 2026
* [TransferEngine] Keep RDMA owner CQ polled during active handshake (#3814)

* fix: drain RDMA CQ during active handshake

* Fix comments

* polling for initial step

* [TENT] Fix DeviceSelector inflight quota accounting (#3511)

* [TENT] Fix DeviceSelector inflight quota accounting

* Update mooncake-transfer-engine/tent/tests/quota_accounting_test.cpp

Co-authored-by: Stary <staryxchen@tencent.com>

---------

Co-authored-by: jipengtian <jipengtian@xiaohongshu.com>
Co-authored-by: Stary <staryxchen@tencent.com>

* [PG] Fix CUDA enqueue-stream serialization (#3798)

* [PG] Add support for torch==2.14.0 (#3842)

* [Bugfix][TE] Use the only ADXL endpoint when dest has one engine (#3835)

Embedded/standalone TEs stamp buffer device_id with the logical NPU id
but publish a single endpoint at index 0, which spammed fallback
warnings. Multi-endpoint dest routing is unchanged.

Co-authored-by: youyx <youxiao@huawei.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [Bugfix][TENT] Keep RailMonitor state across COW topology refreshes (#3832)

Segment metadata is copy-on-write, so buffer/CUDA-graph register
publishes a new Topology* even when the rail map is unchanged. Treat
same NIC/memory layout as a pin refresh instead of loadDefault(), and
log the config banner only on first load so PD/e2e is not flooded.

Signed-off-by: staryxchen <staryxchen@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT]: Deduplicate TentMetrics singleton via shared tent_metrics library (#3797)

* fix-tent-metrics-duplicate-log

* delete test

* link Rust TENT

* Fix tent_metrics RPATH and drop redundant Rust link paths

* Restore tent_metrics Rust link search path

---------

Co-authored-by: ruanzhao <ruanzhao@kingsoft.com>

* [TENT] Route HP TCP lanes across configured rails (#3834)

* feat(tent): route HP TCP lanes across rails

* fix(tent): validate HP TCP rail listener

* [Store] Preserve replica IDs during standby restore (#3811)

Co-authored-by: Yuchen Kou <kouyuchen@approaching.ai>

* [Store] Enable fenced batch OpLog writer (#3810)

* [Store] Enable fenced batch OpLog writer

* [Store] Preserve direct OpLog startup while fencing HA serving

---------

Co-authored-by: Yuchen Kou <kouyuchen@approaching.ai>

* [Store] Structure ReplicaSelectionConfig environment settings (#3840)

Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Align OffsetAllocatorBackendConfig with config layout (#3823)

Co-authored-by: Aoi <aione.moe.dev@gmail.com>
Signed-off-by: Schatten <czhengt@qq.com>

* fix(te): free wr_depth_list_ of zero-QP endpoints in ~RdmaEndPoint (#3846)

construct() allocates wr_depth_list_ even when num_qp_list is 0, but the
destructor's abnormal-shutdown fallback only ran deconstructLocked() for
a non-empty qp_list_, leaking the array for constructed zero-QP
endpoints. Key the fallback on wr_depth_list_ too; deconstructLocked()
already handles the empty-qp_list_ case and nulls the pointer.

The nightly-coverage ASan build has failed rdma_endpoint_state_test at
exit on exactly this since the zero-QP lifecycle tests landed.

Fixes #3839.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* test: migrate placement, replication, and task scenarios to the DSL (#3702)

Move the client-visible placement/allocation, copy/move/replica-clear,
task RPC, graceful-unmount, and BatchQueryIp coverage from
master_service_test.cpp into four scenario suites on the MasterScenario
DSL, consuming the vocabulary from #3510 without extending it. Lease
and grace-timer sleeps become deterministic ExpireAt and Eventually.

Drain jobs, task payload wire format, private worker/refcount/metric
probes, and eviction-pressure tests stay as focused direct tests; a
trimmed MoveEndReleasesSourceRefcountWhenTargetGone direct test keeps
the private refcount half of the legacy MoveEnd case.

Implements #3573. Stacked on #3656.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* [Store] Complete KV event publication (#3818)

* [Store] Complete KV event publication

Finish the master-side KV cache event publisher so external cache-aware
indexers can consume a correct availability stream.

* Carry replica availability per medium instead of collapsing an object's
  metadata into a single medium string, so a tier is never dropped from the
  stream and a partial eviction retracts only the tier that went away.
* Announce evictions that leave the object alive, and pick a single publisher
  per removal so a retraction is not emitted twice.
* Add a tenant-wide clear event, tied to the real outcome of a removal.
* Stamp the configured model context on every event instead of nil.
* Add publisher and master-service test coverage, and document the wire format
  and the subscriber contract.

Co-Authored-By: lolo-pop <lolo-pop.rong@gmail.com>

* [Bugfix]fix removeall potential concurrency conflict

---------

Co-authored-by: j00628475 <jiangzexu144@huawei.com>
Co-authored-by: lolo-pop <lolo-pop.rong@gmail.com>

* [Store] Introduce placement index and replica allocator (#3704)

* [Store] Introduce stateful region resource drivers

* [Store] Introduce placement index and replica allocator

* [Store] Refine placement index and policy abstractions

* [Store] Preserve CXL-only preferred allocation

* [Store] Make preferred target filtering explicit

* [Store] Partition placement groups by target kind

* [Store] Make placement group kind explicit

* [Store] Self-heal dangling LOCAL_DISK replicas in Client::Put (#3711)

* [Store] Self-heal dangling LOCAL_DISK replicas in Client::Put

RemoveAll wipes client SSD files unconditionally while master keeps
lease-protected metadata, leaving a completed LOCAL_DISK entry whose
backing file is gone (issue #3709). Reads then fail, and every later Put
for the key hit OBJECT_ALREADY_EXISTS, which Client::Put converted into
a silent success, so the engine believed data was stored when nothing
was ever written.

When PutStart reports OBJECT_ALREADY_EXISTS and every completed replica
is a LOCAL_DISK replica owned by this client, verify the file with the
local storage backend; when it is gone, evict the dangling replica
through the existing EvictDiskReplica RPC and retry PutStart once. If
any completed replica lives elsewhere (memory, remote disk, DFS) the key
is still readable, so the old idempotent path is kept unchanged.


Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* [P2P-Store] Unregister buffers on GetReplica failure paths (#3783)

* [P2P-Store] Unregister buffers on GetReplica failure paths

doGetReplica left earlier registrations behind when a later Add or a
transfer failed, and GetReplica leaked all of them when metadata
recheck or the final update failed.

Signed-off-by: chlins <chlins.zhang@gmail.com>

* Address review: wait for in-flight transfers before rollback, undo per-round refcounts

Signed-off-by: chlins <chlins.zhang@gmail.com>

---------

Signed-off-by: chlins <chlins.zhang@gmail.com>

* [Bugfix] Fail SSD eviction tests on read errors (#3738)

* test: fail SSD eviction checks on read errors

Keep cleanup and diagnostics, then fail single and batched readback tests when data is empty or corrupted.

Assisted-by: Claude
Signed-off-by: Miguel Garcia <miguelgarciaroman8@gmail.com>

* test: isolate SSD eviction keys across cases

Use a distinct key prefix per test so asynchronous cleanup cannot leak stale SSD replicas into the next case.

Assisted-by: Claude
Signed-off-by: Miguel Garcia <miguelgarciaroman8@gmail.com>

* test: fail concurrent SSD reads on errors

Assert the aggregated concurrent read failure count after cleanup so empty or corrupted reads cannot leave the stress test green.

Assisted-by: Claude
Signed-off-by: Miguel Garcia <miguelgarciaroman8@gmail.com>

---------

Signed-off-by: Miguel Garcia <miguelgarciaroman8@gmail.com>

* [P2P-Store] Release batch IDs on all performTransfer exit paths (#3770)

* [P2P-Store] Release batch IDs on all performTransfer exit paths

performTransfer leaked the allocated batch ID on five paths: the
location==nil break, openSegment/submitTransfer/getTransferStatus
errors, and context cancellation.

Signed-off-by: chlins <chlins.zhang@gmail.com>

* Drain in-flight task on cancellation before freeing batch ID

freeBatchID returns BatchBusy while a task is pending, so the
cancellation path must reach a terminal state first. Extract a
batchTransport interface and add cancellation regression tests.

Signed-off-by: chlins <chlins.zhang@gmail.com>

---------

Signed-off-by: chlins <chlins.zhang@gmail.com>

* [Build] conductor: build skeleton and common layer (#3825)

* [Build] conductor: build skeleton and common layer

First slice of the Mooncake Conductor upstreaming series. Adds the
component skeleton so the later slices are pure additions:

- mooncake-conductor/CMakeLists.txt building the conductor_cpp_core
  static library, with a standalone-build guard so the component also
  configures on its own (outside the top-level GLOBAL_CONFIG build).
- common layer: ServiceConfig / HashProfileConfig data model
  (common/types.h) and the environment-variable and log-level helpers
  (common/utils.{h,cpp}).
- tests/CMakeLists.txt with the conductor_test gtest target, covering
  the env helpers and the JSON uint64 round-trip contract that the
  service HTTP responses rely on.
- WITH_CONDUCTOR option (default OFF) wired into the top-level build,
  enabled in the CI unit-test configure so conductor_test is picked up
  by the existing unified CTest run, and documented in build.md.

No conductor binary is produced yet; src/main.cpp arrives with the KV
event manager slice.

Co-Authored-By: Misak2333 <167268798+Misak2333@users.noreply.github.com>

* [Build] conductor: use the top-level googletest targets when present

* [Build] conductor: nest common namespace under mooncake

Address review feedback on #3825: use mooncake::conductor::common instead
of a top-level conductor namespace, matching the convention used across the
tree (mooncake::ha, mooncake::tent::ub, mooncake::offset_allocator).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [Build] conductor: reuse mooncake-common parsing helpers

---------

Co-authored-by: rongchenghao <r00875518@china.huawei.com>
Co-authored-by: Misak2333 <167268798+Misak2333@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* [TENT] Add native UB endpoint bootstrap and READ/WRITE data path (#3796)

* [TENT] Add native UB endpoint bootstrap and READ/WRITE data path

* [TENT] Prefer UB bonding devices when device_filter is empty

Avoid initializing underlying udmac* contexts alongside bonding_dev_0
by default, which can bypass UBAGG and cause cross-node LOC_ACCESS_ERR.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT] Port UB bonding runtime patches into urma_adapter

Bring over the local working-tree fixes needed for bonding_dev_0:
SET_BONDING_MODE, CTP priority/multi_path jetty config, and token_id
segment registration. Also wire topology discover_ub from ub/enable.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT] Unify UB bonding detection and harden segment teardown

Reuse device_selection helpers in urma_adapter and install path so heuristics cannot drift, and always free token_id when unregister fails.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT] Fix clang-format for UB transport files

Apply clang-format-20 to changed lines in ub_transport.cpp and
urma_adapter.cpp so CI code format check passes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [CI/Build] Ignore gflags exit code in UB tebench smoke test

tebench --help exits 1 under gflags; tolerate that so the UB benchmark
CLI smoke step checks help text instead of failing early under set -e.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [CI/Build] Restore UB tebench smoke step after main sync

Re-apply the ub-mock CLI help smoke test that was dropped while
resolving the merge onto the latest LinQuick main.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT][UB] Address PR #3796 review: reclaim provider-lost inflight token, decouple Topology from UB enable

- scanTimeouts(): after a successful quiesce fence, explicitly remove and
  release a still-present timed-out token before scheduling its retry. A
  provider-lost completion previously left the token in inflight_, so
  releaseInflight() never ran and endpoint outstanding/quota stayed charged
  until stop(), which could exhaust capacity and stall later transfers.
- Topology::discover(): drop the transport-policy discover_ub flag and keep
  discovery transport-agnostic (always attempt UB when the platform supports
  it); UbTransport activation remains gated by transports/ub/enable in the
  transport loader.
- Add regression test ProviderLostCompletionIsReclaimedAfterSuccessfulFence
  with a fake-adapter switch that makes quiesce succeed without returning the
  token, asserting inflight/quota/outstanding accounting return to zero and
  subsequent transfers still make progress.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT][UB] Fix clang-format in ub_native_data_path_test

Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT][UB] Cap slices per task and harden endpoint cleanup against incomplete teardown

- Add transports/ub/max_slices_per_task (default 64k) and reject requests
  whose slice count exceeds it in submitTransferTasks, bounding per-task
  memory/scheduling footprint.
- EndpointStore: treat retire() as incomplete when the endpoint does not
  reach kDestroyed, quarantine it instead of replacing it, and retry
  getOrCreate() so a same-key endpoint is not reused while cleanup is still
  in progress.
- Tests: fake adapter gains reset-failure injection; new
  EndpointStoreDoesNotReplaceEndpointWhenCleanupCannotComplete plus slice-cap
  rejection coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT][UB] Fix clang-format in tent_backend

* [TENT][UB] Remove ineffective tebench --help smoke step from CI

The step only grepped static gflags help strings and never exercised
the TENT/UB path; ub-mock build and CTest already cover real coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Le1zyCatt <2490162471@qq.com>
Co-authored-by: Connor-Matthew <60215777+Connor-Matthew@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Bump google.golang.org/grpc in /mooncake-p2p-store/src/p2pstore (#3827)

Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.79.3 to 1.83.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.79.3...v1.83.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.83.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* [Bugfix][TENT] Return an error for unregistered application RPC IDs (#3862)

* [TENT] Slice large HP TCP reads across rails (#3861)

* [TENT] Slice large HP TCP reads across rails

* [TENT] Simplify HP TCP read slicing state

* [TENT] Cancel sibling HP TCP slices on first error

* [TENT] Keep sibling cancellation fix minimal

* [TENT] Test sliced failure cancellation and retry

* [TENT] Make sliced cancellation test deterministic

* [CI] Separate concurrent SSD write and read phases (#3870)

* [Reshard] Add Store-backed model-weight snapshots (#3772)

This PR adds Store-backed model-weight snapshots for heterogeneous resharding.

WeightStore persists a complete, immutable StoredWeightManifest only after
all planned payload fragments have been uploaded and verified. The manifest is
address-free and retains the logical tensor/fragment layout required to plan a
later restore into a different target topology. Runtime GPU addresses,
allocation bounds, leases, and allocation guards stay in ephemeral runtime
bindings.

The Store path provides:

generation-consistent source replica selection and upload planning;
grouped payload and metadata records through Mooncake Store;
commit/abort decisions, payload completeness checks, and recovery-safe
registration cleanup;
retry-safe manifest publication after a commit decision;
range-based restore with get_into_ranges into validated target bindings;
an explicit WeightStoreWriter lifecycle:
begin_weight_snapshot(...) -> write_tensor(...) -> commit().

* [TransferEngine] Clear pending notify when freeing batch (#3891)

* fix(te): clear pending notify when freeing batch

* fix(te): narrow pending notify lock scope

---------

Co-authored-by: guorongjie <guorongjie@bytedance.com>

* [Bugfix][TENT] Predict deadline MLU from a metered wire rate and the bytes ahead (#3780)

* [Bugfix][TENT] Return every slice's charge and lane count to the counter that holds it

* [Bugfix][TENT] Meter the NIC's transmit rate and predict the admission drop from it and the bytes ahead

* [TENT] Score each arbitration slot against the bytes actually posted ahead of it

---------

Co-authored-by: maxlisongsong <maxlisongsong@didiglobal.com>

* [Store] Support Dummy external buffer registration (#3721)

* test: migrate storage-tier RPC scenarios to the DSL (#3847)

Move the client-visible DFS, root_fs_dir dual-replica, and local-disk
offload coverage from master_service_test.cpp, master_service_ssd_test.cpp
and offload_on_evict_test.cpp into three scenario suites, adding the
local-disk vocabulary proposed on the issue (MountLocalDisk,
UnmountLocalDisk, ReportSsdCapacity, OffloadHeartbeat, CompleteOffload,
EvictDiskReplica) as thin wrappers over exported RPCs, each covered by
failure-reporting contract tests.

Metric-singleton probes, the PushOffloadingQueue private hook,
eviction-pressure and concurrency tests, the perf comparison, and the
USE_NOF cases stay as focused direct tests.

Closes #3574.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* [CI/Build] Add Kylin support to dependencies installer (#3906)

Co-authored-by: LiChangXin-KylinSoft <vovalcx@gmail.com>

* [CI/Build] Upload pre-release wheels to GitHub Releases (#3908)

* [Store] Expose explicit soft pin action and TTL in C ABI and Go/Rust bindings (#3881)

* [Store] Expose explicit soft pin action and TTL in C ABI and Go/Rust bindings

Complete the client-API migration started by the fixed soft pin
lifecycle: the C ABI boolean with_soft_pin becomes an explicit
mooncake_soft_pin_action plus an optional request-level TTL, and the
Go and Rust wrappers expose SoftPinAction/soft_pin_ttl_ms accordingly.
This lets non-C++ clients enable a soft pin with a custom TTL and
disable an active one instead of only toggling the master default.

Unknown actions and invalid action/TTL combinations are rejected
(wrapper-level in Go/Rust, master-side for raw C callers).

* test(store/go): allow overriding integration test endpoints via env

Respect MC_METADATA_SERVER and MOONCAKE_MASTER so the Go integration
tests can run against a master on non-default ports; defaults are
unchanged.

* style(store/go): rename SoftPinTTLMS to SoftPinTTLMs

Match the existing Go millisecond-suffix convention (timeoutMs in
mooncake-common).

* fix(store): reject out-of-range soft pin actions at the C ABI boundary

The C soft_pin_action field is a plain int while the C++ enum is
uint8_t-backed, so a direct static_cast wrapped large values (e.g.
257) into valid actions instead of letting the master reject them.
Validate against the enumerator range and fail with -1 like other
argument errors.

* [TENT] Disable Nagle on HP TCP client sockets (#3883)

* [TENT] Pin dispatching_bytes_ to the eligible bytes in Dispatching (#3910)

Co-authored-by: maxlisongsong <maxlisongsong@didiglobal.com>

* [TENT] Drain RDMA CQ and dest GPU before tearing down MRs (#3899)

* [TENT] Drain RDMA CQ and dest GPU before tearing down MRs

Quiesce stops new submits, waits for inflight to hit zero, then
cudaDeviceSynchronize on topology CUDA devices so GPUDirect writes are
visible before deconstruct unregisters memory.

Signed-off-by: staryxchen <staryxchen@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT] Move dest-GPU sync onto Platform during RDMA quiesce

Keep vendor CUDA/HIP calls out of RDMA transport. synchronizeDevices()
lives on Platform; CUDA and ROCM implement it, other platforms no-op.

Signed-off-by: staryxchen <staryxchen@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Signed-off-by: staryxchen <staryxchen@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [Store] Add bounded standby promotion handoff (#3841)

Co-authored-by: Yuchen Kou <kouyuchen@approaching.ai>

* [Store] Structure TransferSubmitterConfig environment settings (#3850)

Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure LocalFileSnapshotConfig environment settings (#3872)

Co-authored-by: Aoi <aione.moe.dev@gmail.com>
Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure RpcTimeoutConfig environment settings (#3871)

Co-authored-by: Aoi <aione.moe.dev@gmail.com>
Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure FilePerKeyConfig environment settings (#3853)

Signed-off-by: Schatten <czhengt@qq.com>

* [Feature] Add per-file storage mode to storage_benchmark_v1 (#3454)

* [Store] Stabilize HA under OpLog pressure (#3860)

Signed-off-by: Zhen Zhang <zhenzhang@roblox.com>

* [CI/Build] Run current weight snapshot API tests in nightly (#3930)

Signed-off-by: Miguel Garcia <miguelgarciaroman8@gmail.com>

* [Bugfix][TransferEngine] Prevent peer failures from latching RDMA contexts inactive (#3559)

* [Bugfix][TransferEngine] Harden the RDMA context breaker

Incident (2026-07-15): one mooncake store replica restarted during an mlx
fault. Five prefill pods accumulated 32 consecutive all-rails-failed submit
batches, every batch targeting that single store, and tripped the
context-level circuit breaker from #2155. The local RdmaContext became
inactive even though its port was still up. Since inactive contexts stop
posting work, CQ success could not clear the breaker, and PORT_ACTIVE never
arrived to start physical recovery. Every later PUT failed synchronously with
AddressNotRegistered until the processes restarted.

Make the context breaker distinguish peer failures from local RNIC failure:

1. Require an all-rails-failed submit streak to span at least
   MC_CONTEXT_FAILURE_MIN_PEERS distinct peer servers before it may trip
   (default 2; 1 restores single-peer submit tripping). Direct local WC
   failures remain strong local evidence and preserve their existing
   single-peer threshold behavior from #3387.

2. Auto-reactivate a breaker-disabled context after
   MC_CONTEXT_PAUSE_TTL_MS (default 5000; 0 keeps the latch-until-port-recovery
   behavior). Reactivation is half-open: the streak resets, so a genuinely
   unhealthy RNIC re-trips after another threshold of failures. Fatal async
   events clear the breaker and own the inactive state, preventing the TTL
   from resurrecting an event-disabled context.

3. Replace the atomic counter with ContextHealthTracker, a mutex/atomic,
   clock-injected state machine that tracks the streak, peer set, trip time,
   and tripped state together. Tracker callbacks serialize breaker state with
   RdmaContext::set_active(), preventing a concurrent trip and recovery reset
   from leaving the context inactive with no armed recovery.

Preserve the delayed physical recovery introduced by #2959: PORT_ACTIVE still
starts the 30-second GID recovery probe, clears any breaker trip so its shorter
TTL cannot bypass that delay, and activates the context only after the probe
succeeds.

Add strict parsing for both settings plus hardware-free tests for single- and
multi-peer thresholds, half-open recovery, fatal-event override, transition
races, concurrent access, and configuration boundaries and malformed values.

Signed-off-by: Dao Le <daole@inferact.ai>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* [CI] Rerun flaky UB mock test

* ci: rerun checks

* ci: rerun checks

* fix: simplify RDMA context breaker

* fix: preserve RDMA recovery ownership across GID probes

---------

Signed-off-by: Dao Le <daole@inferact.ai>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* [Store] Fail-stop supervisor on OpLog writer terminal failure (#3923)

When the batch OpLog writer reaches a terminal failure, the HA supervisor now fail-stops serving: mutation admission is closed through admin availability, the RPC delegate and leader label are removed, runtime state becomes standby, and the RPC server is stopped. Terminal notifications are delivered outside writer locks and duplicate notifications are ignored.

Related: Oplog HA PR-F02 design.

Co-authored-by: Yuchen Kou <kouyuchen@approaching.ai>

* [CI/Build] Simplify CI orchestration and share smoke services (#3913)

Simplify CI orchestration and share service lifecycle code across smoke suites. A failed path selector previously skipped the builds but could still leave CI Gate green; the gate now requires selector success and checks each group against its expected success/skipped result.

* [TENT] Add configuration lifecycle foundation (#3836)

* [TENT] Add configuration lifecycle foundation

Refs #3833

* [TENT] Fix configuration lifecycle formatting

* [TENT] Classify HP-TCP configuration lifecycle

* [TENT] Audit lifecycle inventory against source keys

* [TENT] Reject mixed-lifecycle subtree reads

* [TENT] Simplify configuration lifecycle classification

* [TENT] Remove redundant lifecycle from field specs

Store only path and match mode in the runtime allowlist. Return runtime-candidate explicitly for a matching entry and keep the bootstrap-only fallback. Update the inventory test to verify actual classification.

Refs #3833

* [TENT] Reject noncanonical lifecycle view paths

Legacy Config skips empty slash-delimited segments, so raw lifecycle checks can authorize aliases of runtime fields or mixed subtrees. Reject noncanonical paths in the shared view read guard and cover scalar, metrics subtree, and root aliases without changing legacy Config lookup behavior.

Signed-off-by: bugkeep <1921817430@qq.com>

---------

Signed-off-by: bugkeep <1921817430@qq.com>
Co-authored-by: Liqian Lin <jacklin78911@gmail.com>

* [EP] Add NCCL Device API backend for ElasticBuffer (#3449)

* [EP] Add NCCL Device API backend for ElasticBuffer

* [EP] Optimize NCCL rail transport performance

* [EP] Support NCCL 4x4 rail topology

* [EP] Apply clang-format to NCCL backend

* [EP] Make NCCL the default ElasticBuffer backend

* [EP] Remove redundant NCCL template disambiguator

* [EP] Preserve IBGDA hybrid dispatch batching

* [EP] Clarify elastic transport comments

* [EP] Support NCCL 2.30 and 2.31 device layouts

* [EP] Agree on ElasticBuffer transport across ranks

* [EP] Keep NCCL DeviceTransport opt-in

* [TE] Add metadata versioning (#3837)

* [TE] Add metadata versioning

* Fix Copliot issues

* Update local cache when finding latest version

* Fix comments

* [Bugfix][TENT] Serialize lanes sharing a queue pair on its slice queue (#3924)

Co-authored-by: maxlisongsong <maxlisongsong@didiglobal.com>

* [TENT] Fix HP TCP coalescing limits and short READ stalls (#3931)

* fix(tent): bound HP TCP request coalescing by transfer limit

* fix(tent): avoid delayed HP TCP reads and verify rail payloads

* fix(tent): close HP TCP merge and benchmark review gaps

* test(tent): address HP TCP synchronization and CI review

Replace the test-local TSan suppression with the existing worker barrier after WRITE ACKs. Keep the config test's file port occupied by its HTTP server to avoid random-port false failures. Present the existing rail measurements as a table.

* [TENT] Say why the drop and arbitration MLU read different bytes_ahead (#3907)

Co-authored-by: maxlisongsong <maxlisongsong@didiglobal.com>

* [Store] Migrate eviction and concurrency scenarios to the MasterScenario DSL (#3927)

Co-authored-by: Aionw <aione.moe.dev@gmail.com>
Signed-off-by: Rishabh Sinha <rsinha17@terpmail.umd.edu>

* [Store] Split utility code by function (#3587)

* [Store] Route tensor _from writes through multi-buffers (#3722)


---------

Co-authored-by: Teng Ma <stmatengss@gmail.com>

* [Fix] TENT gid lid refresh (#3920)

* [Bugfix][TENT] Refresh RDMA addresses after network changes

Refresh a TENT RDMA context's LID, GID, and GID index on GID_CHANGE,
LID_CHANGE, and port recovery events, then publish the updated device address
to segment metadata before the NIC becomes selectable again.

Keep the context paused during refresh, reject non-active ports, and retire all
endpoints built with the previous address generation before re-enabling the
NIC. Each endpoint pins one immutable local address snapshot so bootstrap,
data QPs, and notification QPs cannot mix generations during a concurrent
refresh.

Add fake-verbs coverage for GID and LID changes, wrong-port events, query
failures, down ports, initially omitted NIC metadata, port recovery, and
endpoint address-generation pinning.

* [Bugfix][TENT] Validate paused state before address refresh

Make RdmaContext::pause report success when it transitions an enabled context
or observes an already-paused context, and reject uninitialized or disabled
states. Gate activateContext address probing and metadata publication on a
confirmed DEVICE_PAUSED state so teardown races cannot publish an address that
can never be resumed.

Add regression coverage for the pause contract and for skipping GID/metadata
refresh when the context cannot enter the paused state.

---------

Co-authored-by: TTThanos <yaozhong.lyz@alibaba-inc.com>

* [TENT] Spin with pause before yielding in TicketLock (#3925)

Co-authored-by: maxlisongsong <maxlisongsong@didiglobal.com>

* [TE] Keep fabric mem Store-only; drop GRC fabric_memory enablement (#3955)

* [TE] Keep fabric mem Store-only; drop GRC fabric_memory enablement

* [TENT] Pin failover policy at transfer submission (#3934)

* [TENT] Pin failover policy at transfer submission

* [TENT] Initialize failover snapshots and share policy defaults

Build the initial snapshot before fallible setup and reject null test publications without replacing the active policy. Share named defaults across snapshots, logical task policies and config parsing, and extend the policy-pinning regression to cover null rejection.

* [TENT] Skip idle GPUs when synchronizing dest CUDA contexts (#3945)

* [TENT] Skip idle GPUs when synchronizing dest CUDA contexts

Do not cudaSetDevice topology cards that have no primary context, and
do not cudaGetDevice when this thread has no current context. Tone
enumerates all visible GPUs without CUDA_VISIBLE_DEVICES; creating
those contexts wastes VRAM on ranks that only use a subset.

Signed-off-by: staryxchen <staryxchen@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT] Initialize the CUDA driver once in dest-GPU sync

Call cuInit through std::call_once so topology walks do not re-enter
driver setup. Drop environment-specific comments.

Signed-off-by: staryxchen <staryxchen@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Signed-off-by: staryxchen <staryxchen@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [Store] Validate DummyClient registration of external pinned buffers (#3712)


---------

Co-authored-by: Teng Ma <stmatengss@gmail.com>

* [Bugfix][TransferEngine] Export the requested range for dma-buf registration under CUDA VMM (fixes #2511) (#3538)

* Create a new branch for this commit

Create a new branch for this commit

* Add length parameter to exportDmabuf function

* Update exportDmabuf call to include length parameter

* Update exportDmabuf calls to include buffer size

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Update rdma_context.cpp

style: satisfy clang-format (collapse single-statement if)

---------

Co-authored-by: Feng Ren <alogfans@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* [Py] Fix batch_id leak on timeout and submit failure paths (#2350)

* [Integration] Fail Python init when allocator support is unavailable (#2404)

* fix: fail Python allocator init safely

* Mark initMemoryAllocator static; it is only used inside this TU

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

---------

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* TENT: deliver self-targeted notifications in-process (#3464)

* TENT: add the in-process queue for self-targeted notifications

Hold notifications whose target is LOCAL_SEGMENT_ID in the engine itself. The queue is drained by receiveNotification() in the next commit; no behavior change on its own.

* TENT: deliver self-targeted notifications in-process

The data plane short-circuits transfers whose target is LOCAL_SEGMENT_ID to the local path, but notifications had no such branch: sendNotification() handed every target to the first transport that supports notifications. Over RDMA that reaches the local pseudo-endpoint, which never performs the bootstrap handshake and so never owns a notification QP - the send fails ("Notification QP not connected", rdma_transport.cpp -> endpoint.cpp) and a receiver polling for the notification waits forever.

Observed end to end through the NIXL Mooncake plugin: the first intra-agent transfer carrying a notification hangs its receiver until timeout. The combination went unnoticed because the intra-agent DRAM unit test upstream is commented out and the VRAM variant only compiles with CUDA on a GPU machine.

Deliver self-targeted notifications in-process instead: queue them in the engine on send, and drain that queue in receiveNotification() after polling the transport. This mirrors the data plane's local short-circuit, works for every transport combination, and needs no wire at all - including builds where no transport supports notifications, where a queued self-notification is now delivered rather than rejected.

* TENT: cover self-targeted notification delivery with a unit test

local_notification_test pins the contract without needing a NIC or a metadata server: in-order exactly-once delivery of self-targeted notifications, resolution of the engine's own advertised name (in p2p mode the normalized "ip:port", not the configured local_segment_name) and of the empty name to LOCAL_SEGMENT_ID, and delivery with every transport disabled.

* TENT: address review on self-targeted notification delivery

- receiveNotification() clears the output list on entry. Callers reuse one
  vector across polls, so appending to leftovers redelivered whatever the
  caller had already handled.
- A poll that delivered something reports success. Notifications appended to
  the list have been consumed from their queue, so the caller has to see
  them; a persistent transport error still surfaces on the next poll, which
  comes back empty. The local queue is drained regardless of the transport
  poll's outcome, since in-process delivery does not depend on it.
- local_notification_test gains the case the fix is actually about: an
  intra-agent transfer carrying a notification, i.e. submitTransfer(...,
  notifi) -> maybeFireSubmitHooks() -> sendNotification(LOCAL_SEGMENT_ID).
  It routes notifications through a stand-in transport that advertises
  support and fails to send, which is what the RDMA local pseudo-endpoint
  does. Without that stand-in a real transport answers on loopback and the
  notification round-trips even without the fix, so the test would not pin
  the regression; verified to fail with the local short-circuit removed and
  pass with it.
- makeConfig disables the remaining transports (sunrise_link, mpcomm, tpu,
  ub) so the no-notification-transport case is genuinely transport-free, and
  both delivery tests now poll twice through the same vector.

tent suite: 37/37 passed.

* TENT: notify each submit-hook target exactly once

A submit hook is only marked fired once every target has taken delivery,
and it reruns from every later poll while any of them still fails. The
loop restarted from the first target each time, so a target that had
already been notified was notified again on each of those polls.

Self-targeted delivery is what makes that reachable. Before it, the local
leg failed alongside the remote one and nothing was delivered at all; now
it always succeeds, so a batch that notifies both the local segment and a
peer that stays unreachable re-queues the local copy on every poll -
unbounded growth in the local queue, and one duplicate "transfer
complete" per poll for the receiver. Drop each target from the hook as it
takes delivery instead, so a rerun only retries the ones still failing.

local_notification_test covers it with two loopback engines: a two-target
notified transfer whose peer leg cannot be notified, polled while it
fails and again after it recovers. The notification must arrive exactly
once across both phases; without this change it arrives seven times.

Also from the same review pass:

- Document that receiveNotification() clears the caller's vector on
  entry. That was introduced by the previous commit and is the opposite
  of the classic engine's getNotifies(), which appends, so it is worth
  stating rather than leaving for a caller to discover.
- Pin enable_progress_worker and enable_runtime_queue off in the test
  config. The progress worker fires submit hooks from its own thread,
  which races the stand-in transport's counter that these tests read from
  the main thread, and the runtime queue turns it on implicitly.

tent suite: 32/32 passed.

* TENT: attempt every submit-hook target on every pass

Dropping a target as it took delivery still stopped the loop at the first
send failure. hook.targets is an unordered_set, so every target behind the
failing one went unattempted, and because the order is stable across polls
it stayed that way. A self-targeted notification cannot fail, so an
unrelated peer being down would hold one back and block its receiver until
that peer recovered - the liveness of in-process delivery depended on
iteration order.

Continue past failures instead: erase each target that takes delivery, keep
the rest for the next pass, and mark the hook fired once none are left.

SelfNotificationIsNotHeldBackByAnUnreachablePeer covers it with two
unreachable peers and a local target. The delivery assertion alone would
only fail when the order happens to put a peer first, so the test also
counts send attempts: every pass must attempt both failing peers. Against a
version that stops at the first failure that count is 4 over 4 passes
instead of 8, which does not depend on the order.

* Re-trigger CI: tent-ci (ub-mock) hit a known race in RuntimeQueueDispatch.ProgressWorkerRefillsWindowFromTransportNotify

---------

Co-authored-by: Frank <xiaodouzi6661@gmail.com>

* [Store] Introduce SegmentPool catalog and lifecycle (#3705)

Part of #3363; architectural work tracked by #3360. The placement foundation from #3704 is already in main. This PR adds the next layer: a SegmentPool that coordinates mounted-region metadata, allocator resources, placement eligibility, and lifecycle transactions under one lock. It also simplifies the placement interfaces introduced by that foundation.

For example, preparing an unmount removes a region from allocation immediately while retaining the driver resource for cleanup. An immediate transaction can commit or roll back; a graceful transaction keeps existing replicas readable until finalization. Catalog state, driver activation, and placement membership change together under the Pool write lock.

* [Store] Structure NvmeKvConnectorConfig environment settings (#3948)

Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure NoFRegisterConfig environment settings (#3949)

Co-authored-by: Aoi <aione.moe.dev@gmail.com>
Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure MmapArenaConfig environment settings (#3956)

Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure ClientNumaConfig environment settings (#3966)

Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure CxlSegmentConfig environment settings (#3967)

Co-authored-by: Aoi <aione.moe.dev@gmail.com>
Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Wire opt-in batch OpLog snapshots into standby runtime (#3950)

Co-authored-by: Yuchen Kou <kouyuchen@approaching.ai>

* [Store] Add recoverable client liveness and asynchronous offboarding (#2991)

* feat(store): add client liveness state and compatibility config

* feat(store): gate client-owned resources by liveness

* feat(store): retry terminal client offboarding

* feat(store): rebuild liveness across HA restore

* refactor(store): remove client liveness leftovers

* refactor(store): separate allocator registration lifecycle

* fix(store): preserve multi-segment client resources

* fix(store): preserve client liveness invariants

* test(store): align liveness and OpLog scenarios

---------

Co-authored-by: zhuyuening <zhuyn25@mails.tsinghua.edu.cn>

* [Store] Sweep stale handles in bounded write-lock batches (#3593)

Signed-off-by: pjdurden <prajjwalchittori1@gmail.com>

* [TransferEngine] Add POSIX SHM transport for same-host DRAM copies (#3918)

Classic TE still hairpinned same-host DRAM through RDMA/TCP loopback.
Add a first-class ShmTransport (HIP-style) so shm-backed buffers can
memcpy locally, with MC_FORCE_SHM=1 and optional RDMA/TCP coexistence
when ENABLE_MULTI_PROTOCOL is on. Pin relocate-cache mappings across
memcpy so prune/cap cannot munmap an in-flight copy.

RFC: https://github.com/kvcache-ai/Mooncake/issues/3912

Signed-off-by: staryxchen <staryxchen@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [TransferEngine] Prefer THP for shared-segment mmap without a HugeTLB pool (#3952)

POSIX shm mapped first and only hinted THP afterward, so large HostRegister
MRs often stayed on 4KiB pages. Unnamed memfd plus a 2MiB-aligned VA and
MADV_HUGEPAGE before fault lets TP ranks share the same THP-backed pages.

Co-authored-by: youyx <default>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [EP] Support NCCL ElasticBuffer recovery after rank replacement (#3963)

* feat(ep): rebuild NCCL state after rank recovery

* fix(ep): allow reserved PG capacity during NCCL recovery

Validate the buffer's logical ranks independently of reserved PG mask capacity. Exchange expected world sizes before membership collectives so actual resizing is rejected consistently, and add unit and GPU recovery regressions.

* fix(ep): address NCCL recovery review feedback

Recognize the mooncake-cpu backend and use CPU metadata tensors for NCCL bootstrap. Call the always-registered NCCL capability binding directly. Move recovery integration tests into the EP suite while reusing the PG worker harness, add CPU-PG recovery coverage, and document the test command.

* [TENT] Export EGM host memory over multi-node NVLink (opt-in) (#3978)

On Grace-Blackwell NVLink domains GPUs can address peer nodes' host DRAM
through Extended GPU Memory: host memory created with
cuMemCreate(CU_MEM_LOCATION_TYPE_HOST_NUMA) exports as a fabric handle
like device memory. The mnnvl transport only exported device buffers, so
CPU-resident data always crossed the NIC.

Add transports/mnnvl/egm (env MC_MNNVL_EGM, default off). When enabled:
- allocateLocalMemory("cpu:<numa>") returns an EGM allocation mapped for
  every local GPU and the CPU, and the engine's default host allocation
  goes through the mnnvl transport;
- addMemoryBuffer exports host buffers that are EGM allocations, other
  host memory keeps the cudaHostRegister path;
- relocateSharedMemoryAddress imports any exported peer buffer, not only
  cuda:* locations;
- the transport advertises dram_to_dram and gpu_to_dram.

The engine's mnnvl staging policy keys on whether the peer actually
exported the host buffer, so peers registering plain host memory keep
the staging path they had before this flag existed.

The copy stream for host buffers now runs on a GPU attached to the
buffer's NUMA node instead of parsing "cpu:N" as CUDA device N.

Handle lifetime: addMemoryBuffer drops the reference taken by
cuMemRetainAllocationHandle once the buffer is exported (host and device
paths), and transport-owned VMM allocations release the creator handle
after mapping and remember the granularity-rounded extent, so
unregister + freeLocalMemory actually return the memory.


Claude-Session: https://claude.ai/code/session_01USLEbvBiLAhKWPQrvVAMC9

Signed-off-by: JinYan Su <751080330@qq.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

* [Store] Release GIL during client setup (#3947)

Release the Python GIL while setup_real and setup_internal perform native initialization so other Python threads can continue running during long memory registration.

Signed-off-by: leichao.lc <leichao.lc@antgroup.com>
Co-authored-by: leichao.lc <leichao.lc@antgroup.com>

* [Reshard] Add KV cache reshard planning (#3564)

* feat: Add kv reshard

* fix(reshard): repair KV merge integration

* fix

* Phase1

* put lowering to mooncake

* move lowering

* feat(reshard): add runtime-to-runtime KV cache transfer execution

- Harden snapshot, placement, and logical plan validation
- Add operation-scoped resolved bindings and bounded transfer lowering
- Add TE batch execution and writer/target completion contracts
- Preserve existing SGLang PD planning interfaces
- Add contract and native CPU/GPU TCP/RDMA transfer tests

* fix(reshard): tighten KV cache snapshot and memory validation

- Reject mismatched binding snapshots while preserving legacy PD calls
- Validate derived transfer byte sizes against uint64 bounds
- Scope address checks by runtime instance, worker, and device
- Reject conflicting backing allocations while allowing shared storage
- Consolidate boundary tests and remove redundant assertions

Signed-off-by: huanglong <huanglong@linux.alibaba.com>

* fix(reshard): quarantine KV executor on registration failures

Signed-off-by: huanglong <huanglong@linux.alibaba.com>

---------

Signed-off-by: huanglong <huanglong@linux.alibaba.com>

* [CI/Build] Add self-hosted MI350X ROCm external PD tier (#3338)

Co-authored-by: Aionw <aione.moe.dev@gmail.com>

* [Bugfix] Preserve calling thread CPU affinity across SPDK env init (#3904)

* Preserve calling thread CPU affinity across SPDK env init

rte_eal_init registers the calling thread as DPDK's main lcore and
restricts its CPU affinity to the EAL core mask (default "0x1"). Linux
threads inherit the creator's affinity mask, so without a restore every
thread spawned after setup (e.g. sglang's prefetch/backup workers) would
be pinned to a single core, sharply degrading KV read/write throughput.
Save and restore the caller's affinity around spdk_env_init so default
behavior is unchanged.

Signed-off-by: tan changzhi <544463199@qq.com>

Co-Authored-By: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* [Store] Gate affinity save/restore to Linux in SPDK env init

pthread_getaffinity_np/pthread_setaffinity_np and cpu_set_t are
glibc-specific; without the guards the affinity fix would break
non-Linux USE_NOF builds. Linux behavior is unchanged; other
platforms keep the pre-fix behavior.

Co-Authored-By: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

---------

Co-authored-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* [Store] Add DFS read/write metrics and diagnostic logging (#3855)

Co-authored-by: sunbaoshan <sunbaoshan@baidu.com>
Co-authored-by: root <root@gajl-bbc-onlinec-com-1398657.gajl.baidu.com>

* [Store] Support online DFS shard capacity expansion (#3984)

* [Store] Support online DFS shard capacity expansion

* [Store] Isolate lazy DFS shard initialization

* [P2PStore] Fix GetReplica metadata recheck retries (#3921)

* [Store] Fail KV benchmarks when a worker raises (#3938)

Propagate lane failures after joining all workers so the CLI cannot report
success for an incomplete phase or close buffers still used by another lane.

Signed-off-by: Zhe Li <2843409461@qq.com>
Co-authored-by: OpenAI Codex <codex@openai.com>

* [TE] Report buffered JSONL write failures in tebench (#3992)

Co-authored-by: jipengtian <jipengtian@xiaohongshu.com>

* [Bugfix][TransferEngine] Invalidate stale remote segments in syncSegmentCache (#3072)

* [Bugfix][TransferEngine] Invalidate stale remote segments in syncSegmentCache

syncSegmentCache logged "segment X is now invalid" when a cached remote
segment's backend fetch failed but never erased the stale descriptor, so
peers kept routing transfers to dead/unmounted peers until the caching node
restarted. The existing erase sites were local-only paths and did not cover a
remote segment that vanished from the backend.

Track a per-segment consecutive fetch-failure streak (guarded by
segment_lock_) in the apply phase. After kStaleSegmentFailureThreshold (=2)
consecutive failures, erase the entry from both segment_name_to_id_map_ and
segment_id_to_desc_map_, mirroring removeSegmentDesc. A successful fetch
resets the streak, so a single transient backend blip (curl timeout / etcd
hiccup, which the plugin get() collapses with "not found" into false) does
not purge the cache and strand in-flight transfers.

Adds a hardware-free unit test (fake MetadataStoragePlugin) that is
red-on-master (stale entry never invalidated) and green-on-branch.

* style: clang-format-20 the stale remote-segment invalidation test

* [TransferEngine] Distinguish NOT_FOUND from UNAVAILABLE in metadata plugin

MetadataStoragePlugin::get() collapsed a missing key with a transient
backend error into a single false, so two consecutive backend timeouts
looked identical to an authoritative removal and could purge the entire
live cache during a metadata-service outage.

Add GetResult enum (kFound/kNotFound/kUnavailable) and getWithStatus()
virtual with a default that delegates to get() (preserving behavior for
plugins that don't distinguish). Override in RedisStoragePlugin (nil
reply -> kNotFound, command error -> kUnavailable) and HTTPStoragePlugin
(HTTP 404 -> kNotFound, curl/5xx -> kUnavailable).

syncSegmentCache now advances the invalidation streak only for kNotFound;
transient failures (kUnavailable) leave the cached entry untouched and
are retried next cycle.

Add BackendOutageDoesNotInvalidateCache regression: 5 consecutive
transient failures must not invalidate the cached entry.

* fix(format): align getSegmentDesc declaration with clang-format-20 style

clang-format-20 fills the first line up to the 80-column limit instead
of breaking immediately after the opening paren.

Signed-off-by: supermario_leo <leo.stack@outlook.com>

* [TransferEngine] Fail the default getWithStatus() mapping closed

The default getWithStatus() mapped every get() failure to kNotFound, so
a plugin that cannot distinguish absence from an outage (both etcd
implementations do not override it) would let syncSegmentCache() evict
a still-live cache entry during an etcd outage -- reintroducing the
exact availability bug this PR fixes.

Flip the default to fail closed: an ambiguous false is kUnavailable,
and only plugins that can prove an authoritative absence may report
kNotFound. Both etcd plugins now override getWithStatus(): the legacy
SyncClient treats an OK range response with an empty payload as
kNotFound, and the wrapper build treats ret == 0 with no payload as
kNotFound; RPC errors, timeouts and malformed payloads are
kUnavailable.

Add DefaultPluginMappingFailsClosed regression exercising the inherited
default path (a plugin without an override), which stays red until the
fail-closed mapping lands.

Signed-off-by: supermario_leo <leo.stack@outlook.com>

---------

Signed-off-by: supermario_leo <leo.stack@outlook.com>

* [CI/Build] Fix build-musa: map cudaDevAttrMultiProcessorCount (#3999)

* [Store] Complete N08 post-launch snapshot validation (#3991)

Co-authored-by: Yuchen Kou <kouyuchen@approaching.ai>

* [Bugfix][Store] Cover HotStandbyService behaviors with deterministic fakes (#3687)

Co-authored-by: YDMY_Kirito <hantenglong@kylinos.cn>
Co-authored-by: Cursor <cursoragent@cursor.com>

* [TENT] Rotate HP TCP lanes independently per peer (#3987)

* [Store] Avoid blocking HA reattachment on master connection retries (#3743)

* [P2P-Store] Back off transfer status polling after a spin budget (#3851)

Signed-off-by: chlins <chlins.zhang@gmail.com>

* [TENT] Retire idle HP TCP client connections (#3988)

* [Conductor] conductor: prefix index types and hash strategy (#3879)

Co-authored-by: Misak2333 <167268798+Misak2333@users.noreply.github.com>

* [Store] Optimize CRC-64 checksum calculation with slice-by-8 (#3922)

* [Bugfix][Store] Check read leases after grouped batch gets (#3996)

* [Docs] Clarify Store parallel tensor API selection and limits (#3959)

* [Docs] Clarify Store parallel tensor API selection and limits

* [Docs] Fix parallel tensor API table format and spell check

Replace torch.arange with torch.linspace so typos CI accepts the example,
use a standard padded pipe table, and switch the planner cross-link to a
Sphinx {ref} target.

Co-authored-by: Teng Ma <stmatengss@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Teng Ma <stmatengss@users.noreply.github.com>

* [Store] Fan verified bytes out to duplicate keys in batch gets (#3929)

The destination slice maps are keyed by string, so a repeated key
collapses: both indices transfer into the last op's buffer, the
per-index checksum validates against that one filled buffer, and the
first op comes back SUCCESS with its own never-written (recycled,
stale) memory (#3876). The same collapse exists in the
batch_get_buffer / batch_get_into / batch_get_into_multi_buffers
memory paths, the DISK/DFS temp-buffer paths, and both LOCAL_DISK
offload paths.

Transfer each unique key once (first occurrence as primary), then fan
the verified bytes out to each duplicate's own destination locally.
The dedup plan and the result propagation now live in one shared
layer (batch_read_fanout.h: PlanDuplicateKeys, FanOutDuplicates), so
every read path behaves identically: duplicates of a failed primary
inherit the primary's error instead of a pre-set success, and a size
mismatch between same-key destinations fails the duplicate loudly
everywhere.

The fan-out copy is device-aware end to end: RuntimeAccelerator gains
CopyAuto (host/device on either side, memcpy fallback) and
CopySlicesMaybeDevice walks scatter-gather lists whose chunking may
differ, so MEMORY/LOCAL_DISK duplicates with GPU-resident caller
buffers no longer go through a plain memcpy. New unit tests pin the
plan, the slice walker (including mismatched chunking and
underflow), the fan-out contract, and CopyAuto's direction choice;
the existing integration test still covers the end-to-end behavior.

Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>

* [Bugfix][TE] Fix Ascend LocalCopyEngine stream/context for multi-device async copy (#4026)

* [Bugfix][TE] Fix Ascend LocalCopyEngine stream/context for multi-device async copy

* docs: update README AgentX news (#4011)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stmatengss <11641725+stmatengss@users.noreply.github.com>

* [TENT] fix: TCP transport implicitly creates CUDA contexts (#3973)

* [TENT] fix: TCP transport implicitly creates CUDA contexts

* Re-trigger CI (build-wheel-rocm 3.12 flake)

* [TransferEngine] Enable MACA IBGDA using the shared mlx5 transport (#3997)

* [Store] Align BucketBackendConfig with config layout (#3957)

Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure NvmeKvIoConcurrencyConfig environment settings (#4012)

Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure HugepageConfig environment settings (#3983)

Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure ClientAutoDiscoveryConfig environment settings (#3900)

Co-authored-by: Aoi <aione.moe.dev@gmail.com>
Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure SpdkControllerConfig environment settings (#3982)

Co-authored-by: Aoi <aione.moe.dev@gmail.com>
Signed-off-by: Schatten <czhengt@qq.com>

* [Store] Structure RpcProtocolConfig environment settings (#3976)

Co-authored-by: Aoi <aione.moe.dev@gmail.com>
Signed-off-by: Schatten <czhengt@qq.com>

* [Doc] Backport vLLM-style notes for large changes (#4032)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stmatengss <11641725+stmatengss@users.noreply.github.com>

* [Conductor] Add PR classification and automatic labeling (#4010)

* [TENT] Revoke registrations removed by failed batch rollback (#4002)

* [TENT] Revoke registrations removed by failed batch rollback

Hand rollback-to-zero descriptors from SegmentTracker to the engine after
metadata publication, then finish transport cleanup before metadata sync
and return. Preserve the original registration error and normal unregister
behavior.

Add tracker and real-engine regressions for cleanup ownership, failures,
metrics, lease draining, replacement survival and stale HP TCP writes.

* [TENT] Remove unused rollback test callback

* [TENT] Remove added rollback regression fixtures

* [TENT] Reuse callback handoff for failed registration rollback

* [TENT] Fix standard TCP completion, permissions and task accounting (#4001)

* [TENT] fix TCP task lifetime after terminal publication

* [TENT] fail TCP tasks when worker operations throw

* [TENT] enforce registered memory permissions in TCP RPC handlers

* [TENT] preserve original byte counts for merged public tasks

* [TENT] evaluate batch notifications per submit interval

* [TENT] bound standard TCP RPC payloads and request merging

* [TENT] keep TCP merge route preflight free of invalidation

* [TENT] add focused TCP correctness regressions

* [Store] Support RealClient CUDA tensor reads (#3723)


---------

Co-authored-by: Teng Ma <stmatengss@gmail.com>

* [TENT] Refresh TCP endpoints without replaying uncertain writes (#4006)

* [TENT] Refresh TCP endpoints without replaying uncertain writes

* [TENT] Remove added TCP endpoint refresh regression tests

* [TENT] Clarify TCP transfer failure log

* [TENT] Refresh HP TCP endpoints after pre-send connection failures (#4007)

* [TENT] Refresh HP TCP endpoints after pre-send failures

* [TENT] Remove added HP TCP endpoint refresh regression test

* [TENT] Limit HP TCP pre-send refresh to endpoint errors

* TENT: derive notification QP attributes from endpoint params (#4064)

The notify QP hard-coded the verbs tutorial values for its path and retry
attributes while the data QPs of the same endpoint take them from
EndPointParams (setupOneQP), so the knobs under transports/rdma/endpoint
never reached the notify path. Read them from the same params, so both
QPs of an endpoint run on one budget and one configuration:

  attribute        before   after (default)
  path_mtu         4096     path_mtu (4096, clamped to the port MTU)
  min_rnr_timer    0x12     min_rnr_timer (12: 0.64 ms, was 5.12 ms)
  timeout          0x12     send_timeout (14: 67 ms/attempt, was 1.07 s)
  retry_cnt        7        send_retry_count (7)
  rnr_retry        7        7, pinned (see below)
  max_*_rd_atomic  1        1 (the notify QP only carries SEND/RECV)

The EndPointParams defaults themselves are unchanged. Nominally this
brings the time to report a dead peer path on the notify QP from
4.096us * 2^18 * 8 ~= 8.6 s down to the ~0.54 s budget the data QPs
already have; on a BlueField-3 integrated ConnectX-7 (VF) the same
change measured 14.9 s -> 3.6 s, against 3.7 s for the data QPs with the
same parameters.

path_mtu following params is a side effect worth having: the notify QP
hard-coded IBV_MTU_4096 and drifted from the data QPs on a fabric that
configures a smaller transports/rdma/endpoint/path_mtu. min_rnr_timer
moving from 0x12 (5.12 ms) to the default 12 (0.64 ms) shortens the RNR
retry interval: a burst of 2000 standalone notifications against the 256
posted recv slots went from p50 15.3 ms to 1.6 ms. rnr_retry stays
pinned at 7 (unbounded) and does not read send_rnr_count: RNR NAKs are
ordinary flow control on the notify QP, the endpoint's only SEND/RECV
QP, and must never retire it together with its data QPs. The
address-vector fields (hop_limit, flow_label, src_path_bits, PSNs) are
left as they were, and the path-class completion handling from #3475 is
untouched.

Co-authored-by: xiaodouzi666 <xiaodouzi6661@gmail.com>

* [CI/Build] Enable KV events in wheels with ZeroMQ dependencies (#4060)



---------

Signed-off-by: Ishan Dhanani <ishandhanani@gmail.com>
Co-authored-by: Ishan Dhanani <ishandhanani@gmail.com>

* [Bugfix] Fix GIL misuse in TENT Python guard exits (#4051)

* [Bugfix] Fix GIL misuse in TENT Python guard exits

* [TENT] Cover normal and exceptional Python guard exits

* [Store] Support DeepSeek-V4.1-Flash Engram with local and Store-backed byte tables (#4065)

* [Store] Support multi-layer byte Engram tables with caller-owned output

Replace the float32-specific Engram layout with a required positive row_bytes field and retain fixed per-layer, per-head keys. One EngramStore manages a map of layer layouts over a shared Store client; reads, writes and removal select a layer explicitly.

Expose lookup_into as the only read interface, writing directly into caller-owned, pre-registered uint8 output without allocating or registering output internally. Pass replication configuration through populate, validate row sizes and buffer layouts, and update integration tests, the benchmark, and API documentation.

Validation: Store binding build, 17 integration tests, small benchmark smoke, scoped pre-commit and documentation HTML build passed.

---------

Signed-off-by: Teng Ma <stmatengss@gmail.com>

* [CI/Build] Retry transient PUT failures in SSD eviction test (#3875)

* [Bugfix][TENT] Plan RDMA slices from the block size: no empty slices, and fold short tails (#3981)

* [Bugfix][TENT] Take the slice count from the block size so no slice is empty

* [Bugfix][TENT] Fold a short slice tail into the slice before it, not into the block size

* [Bugfix][TENT] Charge the folded slice tail so allocate and release balance

---------

Co-authored-by: maxlisongsong <maxlisongsong@didiglobal.com>

* [TENT] Drain standard TCP workers during quiesce (#4005)

* [TENT] Drain standard TCP workers before batch teardown

* [TENT] Remove added TCP teardown regression tests

* [TENT] Synchronize TCP admission with quiesce

* [Store] Add a per-tenant eviction watermark so quota pressure evicts instead of rejecting (#3805)

* [Store] Structure RedisConnectionConfig environment settings (#4036)

Signed-off-by: Schatten <czhengt@qq.com>

* [TENT] Add CUDA coverage for TCP data-path round trips (#4082)

* [Bugfix][TENT] Keep engines alive for Python allocation guards (#4087)

* [TransferEngine] Enrich Ascend init and register-mem error logs with context (#4084)

* [TransferEngine] Enrich Ascend init and register-mem error logs with context

Include endpoint index/counts and register addr/length so engine mismatches and ADXL register failures are easier to diagnose.

Co-authored-by: Cursor <cursoragent@cursor.com>

* [TransferEngine] Apply clang-format to Ascend error log lines

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>

* perf(store): avoid redundant query result copies (#4085)

Signed-off-by: waizuichougou <2082431897@qq.com>

* [TENT] Add Intel XPU platform to the Transfer Engine (#4030)

Add an XpuPlatform to the TENT runtime so Mooncake can stage transfers
through Intel XPU (GPU) device memory. XpuPlatform extends CpuPlatform:
host DRAM allocation, NUMA probing, and host<->host copies are inherited
unchanged, and only the XPU-device-aware operations are overridden.

Implementation is a native, direct-link SYCL build: the platform sources
include <sycl/sycl.hpp> and link libsycl directly via a private
XpuSyclBackend (device enumeration with GPU-preferred/any-device
fallback, sycl::malloc_device USM allocation, an interior-pointer
registry so chunked staging addresses classify correctly, and
VRAM<->host memcpy). Because SYCL requires the Intel DPC++ compiler,
USE_XPU=ON now requires USE_TENT=ON and fails fast unless the build is
configured with icpx (CXX=icpx); -fsycl is applied to platform_xpu and
propagated to everything that links it.

- runtime: MTYPE_XPU memory type + USE_XPU loader branch
- platform: XpuPlatform (probe/allocate/free/copy/getMemoryType/
  getLocation) backed by XpuSyclBackend
- build: USE_XPU option/gate in common.cmake; platform_xpu with -fsycl
- test: tent_xpu_platform_test drives the real backend (alloc -> H2D ->
  D2H -> byte-equality -> free, host/interior pointer classification),
  skipping when no SYCL device is present; runs against the OpenCL CPU
  device on GPU-less hosts
- docker/xpu.Dockerfile: build image based on intel/pytorch:xpu with the
  oneAPI DPC++ compiler added, so it both builds and runs the platform

Test on Intel B60 platform.

* [Bugfix][TENT] Fix Python send_probe under MC_USE_TENT (#4081)

* [Bugfix][TENT]…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation run-ci Store

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants