perf(shard): lock-free cross-shard read recovery — idle+batch-gated reply spin + dead-flag cleanup#177
Conversation
… §1+§2
Size the shardslice RISK-ACCEPTED follow-up ("lock-free cross-shard read
acceleration") into a new-major milestone and co-specify its first task
through Specify + Scenarios, with a pre-freeze spike that reshaped the
approach before any contract was frozen.
Milestone v2-performance (bundle, user-confirmed intake): cross-shard read
acceleration (safe / memory-preserving) + FT.SEARCH off-event-loop + WAL
group commit. Low-RSS is promoted to a first-class milestone constraint
(the reason RCU/snapshot reads are Out). Records a measurement exception:
single-client cross-shard latency is NOT reliably measurable on the
OrbStack VM (host->guest vCPU starvation) -> GCloud bare-metal.
Task xshard-read-fastpath §1 (rules) + §2 (scenarios): adaptive idle-gated
reply-side spin + cross-connection read coalescing + hard-remove of the
now-dead --cross-shard-fast-path flag/metric/histograms (zero production
callers since the wave-e2 cutover).
Spin-viability spike (scripts/spike-xshard-read.sh), findings recorded in §1:
- fixed spin=4096 cut s4 c100 GET throughput -48% (materializes
shard_starvation) -> the spin MUST be idle-gated, not a fixed budget.
- INFO spsc_notify_wakes ratio 1.000 over ~150k cross-shard ops -> the
eventfd wake fires for every op; the c1 -85% regression is ARCHITECTURAL
cross-thread wake+reschedule cost, NOT a lost-wake/tick-floor bug.
- the OrbStack VM is an invalid instrument for this metric under host vCPU
contention (load floored ~1.5, shards parked); the throwaway
MOON_XSHARD_SPIN prototype in handler_monoio was reverted.
Phase: scenarios. The §3 contract freeze is gated on a GCloud bare-metal
M0 baseline (the VM number is meaningless for this metric).
author: Tin Dang
Close the v1 ADD loop: consolidate the milestone's confirmed competency deltas into the durable foundation and record the milestone retrospective. - PROJECT.md → foundation-version 1; record the v1 CLOSE decision + the cross-shard-read RISK-ACCEPTED follow-up; add the (now shape-enforced) shared-nothing write-path lock-invariant restoration to §Domain. - CONVENTIONS.md: encode the behavior-preserving-perf red-suite split (runtime-red + compile-red API file + green pins) and the §3 freeze flag-line requirement. - hotpath-lock-quickwins TASK §7: mark the three deltas folded. - v1-shared-nothing MILESTONE: exit criteria annotated MET. - RETRO.md: milestone retrospective. author: Tin Dang
Advance xshard-read-fastpath scenarios -> contract and draft the §3 frozen-shape, holding it at DRAFT pending the GCloud bare-metal M0 baseline + the one human bundle approval. The contract fixes the INTERNAL mechanism only — no wire change (the 197-test consistency suite stays the byte-identical wire contract): - C1 idle-gate: thread-local `Cell<u32> XSHARD_INFLIGHT` sibling to the per-shard SHARD cell (single-threaded shard => no atomic, no lock), `xshard_may_spin()` gating on `<= XSHARD_SPIN_GATE`, RAII `XshardWaitGuard`. This is the mechanism that lets c1 spin while c100 parks — the resolution of the spike's starvation tension. - C2 reply-side adaptive poll wired into the EXISTING wait sites (monoio handler_monoio Empty branch, tokio handler_sharded reply await); poll is synchronous (no borrow across .await) and falls through to today's park path unchanged when the gate is false. - C3 CoalescedReadBatch: cross-connection read coalescing to one owner shard, per-result reply routing; groups ACROSS connections only so per-connection read-your-writes order is preserved. - C4 exact hard-remove surface for the dead --cross-shard-fast-path flag + moon_cross_shard_lock_contention_total metric + stale RwLock docs. Contracted responses cover all six §1 rejects. Two contract-level lowest-confidence flags surfaced for the freeze: (1) idle-gate counts cross-shard waiters, not all runnable work (mixed-workload starvation risk); (2) C3 per-result routing is the most novel surface (consistency risk, 197-suite is the oracle). Phase: contract. NOT advancing to tests/build until FROZEN. author: Tin Dang
Record the quiesced-VM RELATIVE baseline that anchors the "halve the gap" target, correct the baseline commit (the waiver's label was wrong), and add the two measurement harnesses. COMMIT CORRECTION: the shardslice waiver called the pre-regression baseline `eb5d664 (v1 HEAD)`. Archaeology disproved it — eb5d664 is the ORPHANED shared-nothing feature tip (the SLOW side, in no branch, not an ancestor of main). The real pre-regression "before" is v0.3.0 tag `3e376a1` (still has the cross-thread lock read fast-path). All eb5d664 refs flipped to 3e376a1. M0 ANCHOR (2026-06-14, monoio, fresh-boot clean VM, pinned cores 0-3 / bench 4-5, fresh-server-per-rep, best-of-5): - s4-c1-GET 37580 (3e376a1) -> 22252 (HEAD a497602) = -40.8% <- the gap to halve - s1 LOCAL c1-GET ~40k on BOTH -> regression is cross-shard-specific (anchor valid) - s4-c100-GET guard ~202k; s4-c1-SET 564->21720 (v0.3.0 WRITES were the slow side; shared-nothing improved writes 38x -> confirms reads-only scope) - M1 target: recover >= half the 15.3k-RPS gap -> c1-GET best-of-N >= ~30k - Live regression is -40.8%, NOT the historical -85% (current main recovered part post-shardslice via PR #173). INSTRUMENT: GCloud bare-metal was blocked (no open billing account) so the anchor is a same-instrument RELATIVE ratio on the quiesced VM (overhead cancels pre/post; absolute us untrusted). A leaked `moon-<hash>-monoio` busy-poller that survived `pkill -f "moon --port"` floored c1 at 1-6k for hours; the clean number came only after a Mac reboot. Hygiene rule recorded in the M0 RECORD + auto-memory. - scripts/baseline-xshard-quiesced.sh: best-of-N pinned baseline harness (deep-settle, quiesce-gate, c1-cool/c100-last ordering, hard checkout guard). - scripts/probe-xshard.sh: local-vs-xshard discriminator (separates contamination from real cross-shard cost). Phase still: contract (DRAFT). Freeze remains gated on the one human bundle approval; M0 anchor (the other gate) is now satisfied. author: Tin Dang
Human bundle approval given ("Freeze as-is", Tin Dang 2026-06-14) over the
§1–§4 bundle, lowest-confidence flag surfaced first (the ~30k "halve" target
sits at the mechanism's one-wake-removed ceiling — little margin). Both freeze
gates now satisfied: M0 anchor established (clean-VM best-of-5, c1-GET
37580→22252 = −40.8%) + the one human approval.
The frozen internal shape (no wire change): C1 idle-gate (thread-local
Cell<u32> XSHARD_INFLIGHT + xshard_may_spin + XshardWaitGuard), C2 reply-side
adaptive poll wired into existing monoio/tokio wait sites, C3 CoalescedReadBatch
cross-connection coalescing, C4 hard-remove the dead --cross-shard-fast-path
surface. M1 target locked: c1-GET best-of-N ≥ ~30k (halve the 15.3k gap).
Phase: contract -> tests. Changing any C1–C4 clause or the M1 line is now a
change request back to SPECIFY.
author: Tin Dang
…p shape Failing-first suite for the frozen §3 contract (behavior-preserving-perf split: compile-red API pin + runtime-red gate logic + cleanup shape-grep + evidence-gated oracles). Proven red for the right reason (symbol presence/absence verified): - tests/xshard_fastpath_api.rs — COMPILE-RED (imports xshard_may_spin / XshardWaitGuard / XSHARD_SPIN_GATE / CoalescedReadBatch, all absent until §5): idle_gate_allows_spin_when_alone (xrf1), idle_gate_blocks_spin_above_gate (xrf1 / reject shard_starvation — the RAII anti-starvation invariant), idle_gate_boundary_is_inclusive (the <= gate bound), coalesced_read_batch_type_exists (xrf2 — routing correctness deferred to the consistency oracle per contract C3). - tests/xshard_cleanup_shape.rs — RUNTIME-RED now (6 dead symbols still in src/), green after M3: dead_fastpath_surface_removed (xrf3 + reject removed_flag_referenced). §4 plan records the evidence-gated Musts (xrf1 perf ≥~30k, c100 guard ~202k, xrf2 P1 recovery, RYW 197/197, RSS) against the M0 anchor + the existing shape green-pins (reject lock_reintroduced / borrow_across_await). Phase: tests. On feat/xshard-read-fastpath (compile-red must not red-break main CI). author: Tin Dang
…path surface xshard-read-fastpath C4+C1+C2 (staged increment; C3 cross-connection coalescing deferred behind the M1 checkpoint). Recovers the default-config cross-shard read regression introduced when shardslice-migration (PR #175) deleted the cross-thread RwLock read fast-path (RISK-ACCEPTED, waiver expires 2026-08-01). C4 — remove the dead pre-shardslice fast-path surface so the new mechanism is the only cross-shard read path: - delete CrossShardFastPath config enum, --cross-shard-fast-path arg, and cross_shard_fast_path_enabled() - delete the DISPATCH_CROSS_READ_FASTPATH metric, its 3 recorders + getter, the INFO field, and the always-0 cross_read_fast_dispatches stat (pinned by tests/xshard_cleanup_shape.rs — source grep, zero dead symbols) C1 — idle-gate primitive (src/shard/slice.rs): - XSHARD_INFLIGHT thread-local Cell<u32> (single-threaded shard: no atomic, no lock); XshardWaitGuard RAII inc-on-enter / dec-on-drop spanning the whole reply-wait (a parked waiter IS in-flight); xshard_may_spin() is true when in-flight reply-waiters <= XSHARD_SPIN_GATE (=2) - CoalescedReadBatch type defined now (src/shard/dispatch.rs); its producer (origin accumulation) + consumer (owner drain) land in C3 (pinned by tests/xshard_fastpath_api.rs) C2 — reply-side adaptive spin (both runtimes): - ResponseSlot::try_take() — non-blocking FILLED take, registers no waker - handler_sharded (tokio) + handler_monoio: when the shard is near-idle (gate open) busy-poll the reply for XSHARD_SPIN_BUDGET (=4096) iters to skip the reply-side cross-thread wake; on miss fall through to the EXISTING park path UNCHANGED. Gate closed => byte-identical to before (the idle gate is what prevents the unconditional-spin c100 -48% collapse measured earlier) Mechanism removes one of the two cross-thread wakes per cross-shard read round-trip. Compiles + clippy-clean on monoio (default) and tokio,jemalloc; xshard_fastpath_api + xshard_cleanup_shape green. M1 relative-anchor checkpoint measured separately on the quiesced VM. author: Tin Dang
Tests phase exit gate met: §4 red suite (xshard_fastpath_api + xshard_cleanup_shape) authored and committed; §3 contract FROZEN @ v1. Marker + state.json advance specify->...->build for the now-running build phase (C4+C1+C2 landed in 2bfc5bc; C3 staged behind M1). author: Tin Dang
…ncrement
Verify-phase fixups for xshard-read-fastpath C2 (no behavior change):
1. unsafe/SAFETY: C2's ResponseSlot::try_take() had added a THIRD inline
`unsafe { (*data.get()).take() }`, tripping §1 Must M4 "zero new unsafe"
+ CLAUDE.md "no new unsafe without approval". Consolidate the single
UnsafeCell take into one private take_if_filled() helper that BOTH
poll_take() and try_take() route through. poll_take/try_take now carry
ZERO inline unsafe; response_slot drops 4 -> 2 unsafe blocks (net -2).
Logic is byte-identical (same Acquire-check / take / Release-store-EMPTY);
10 ResponseSlot unit tests + the loom model (which already exercises a
try_take interleaving) stay green. audit-unsafe: 218/218 SAFETY coverage.
2. fmt: 2bfc5bc was committed clippy+test-clean but WITHOUT cargo fmt --check.
Apply rustfmt to config.rs (stray blank line from the C4 deletion),
handler_monoio (reflow the C2 spin block), and the two frozen red-suite
tests (import ordering + a one-line assert) -- pure whitespace, no
assertion or frozen contract touched.
M1 was measured on 2bfc5bc's binary; this change is perf-neutral (no logic
change), so the recorded c1-GET recovery stands.
author: Tin Dang
…un list Add COMMITS (space-sep list, default = baseline + current) so baseline / no-mechanism / with-mechanism are measured in ONE quiescence window -- the only trustworthy comparison (cross-run absolute RPS is invalid per the milestone VM-instrument exception). The anchor block now reports each non-baseline commit's regression vs the 1.0 reference, plus a C2 RECOVERY block (mech vs no-mech, same-run) isolating the mechanism's effect with VM variance cancelled. Used for the xshard-read-fastpath M1 checkpoint: same-run c1-GET 21468 (a497602 no-mech) -> 25336 (2bfc5bc with-mech) = +18.0% recovery. author: Tin Dang
…dead bench script
C4 (xshard-read-fastpath) deleted the CrossShardFastPath enum and the
cross_shard_fast_path field from ServerConfig, but ~15 integration tests
constructed full ServerConfig literals naming that field, and
scripts/bench-cross-shard-fastpath.sh drove the removed --cross-shard-fast-path
flag. The xshard_cleanup_shape grep-pin only scans src/, and the earlier verify
runs were scoped to specific test binaries, so the full `cargo test` breakage
went unseen until the dual-runtime CI-parity pass surfaced it.
- Drop the `cross_shard_fast_path: CrossShardFastPath::Auto,` field line from
every ServerConfig literal in tests/ (mechanical; no assertion changed).
- Drop CrossShardFastPath from the 3 `use moon::config::{...}` imports.
- git rm scripts/bench-cross-shard-fastpath.sh (it benched the removed flag).
- CHANGELOG: record the BREAKING --cross-shard-fast-path removal + the
idle-gated reply-spin perf recovery.
Completes M3 cleanup repo-wide (not just src/). The tokio CI-parity suite now
compiles cleanly past these literals.
author: Tin Dang
…16 -27%) Verify caught a real regression the c100-P1 guard didn't cover: at pipeline depth the synchronous reply-side spin SERIALIZES cross-shard reads behind per-read thread-blocks and starves throughput -- best-of-7 quiesced s4-P16 GET -26.7% with-mech vs the s1-P16 noise control flat (+2.8%). This materializes the §3 contract flag #1: the XSHARD_INFLIGHT gate alone cannot see the busy thread, because a synchronous spin never yields, so co-located waiters never register as in-flight for each other. Mitigation (flag #1 reserved): add a batch-depth gate. The spin engages ONLY when the connection's current batch carries a single cross-shard command (XSHARD_SPIN_MAX_BATCH_REMOTE=1) -- the pure c1 case -- AND the shard is near-idle. Both reply-wait sites (monoio oneshot drain, tokio ResponseSlotPool) now call one xshard_should_spin(batch_remote), so the two regimes the spin must avoid -- pipelined fan-out (batch gate) and concurrent waiters (inflight gate) -- are enforced in one place. Effect by regime: c1-P1 batch=1 -> spins (+18% preserved); s4-P16 batch~=12 -> parks (byte-identical to no-mech); c100-P1 batch=1 but inflight>gate -> parks (unchanged). Unit-pinned: batch_gate_blocks_spin_for_pipelined_batch + batch_gate_and_inflight_gate_compose. Re-measurement recorded in §6. author: Tin Dang
remeasure-xshard-fix.sh validates the batch-depth gate (xshard-read-fastpath verify): measures no-mech (a497602) / old-mech (2bfc5bc, P16-regressed) / new-mech (7048e8a, fixed) in one quiescence window, best-of-5, core-pinned, across 4 cells — s4-c1-GET (the win), s4-c100-GET (starvation guard), s4-P16-GET (the regression), s1-P16-CTRL (noise floor). Same-run RELATIVE ratios only (VM absolute RPS is untrusted per the v2-performance milestone exception). The s1 control cell isolates real mechanism deltas from VM drift. Reuses the $HOME/moon-baseline file:// clone for builds and caches binaries in $HOME/moon-baseline-bins. author: Tin Dang
… + fixed, gate PASS-pending Advance phase build->verify and write the §6 verify record for the xshard-read-fastpath task (v2-performance milestone). Key finding (verify earned its keep): the with-mech build (2bfc5bc) regressed s4-P16 throughput -27.5% — a synchronous reply spin serializes a pipelined cross-shard fan-out, and the INFLIGHT-only gate is blind to it (the spin never yields). This is the exact §3 contract flag-#1 risk, materialized. Fixed by a batch-depth gate (commit 7048e8a, XSHARD_SPIN_MAX_BATCH_REMOTE=1) so only a singleton cross-shard read spins; any pipeline parks. 3-way same-run re-measure (no-mech / old-mech / new-mech, best-of-5, quiesced): - s4-c1-GET +22.9% vs no-mech (the win, preserved) - s4-P16-GET -27.5% -> +5.2% vs no-mech (regression cleared) - s4-c100-GET +10.3% (no starvation regression) - s1-P16-CTRL -1.4% (flat control => the P16 delta is real, not VM drift) Post-fix dual-runtime re-confirm on 7048e8a: cross-shard surface 23/23 monoio + 23/23 tokio (shardslice_live = liveness oracle, cross_shard_consistency_red = correctness oracle, both green). Gate: PASS (RECOMMENDED) — pending human sign-off (conservative autonomy + concurrency/architecture surface; not auto-gated). M2/C3 deferred via the human-approved v2 change request. No security finding. Adds the pipeline-regime and rejected-risk-flag competency deltas to §7. author: Tin Dang
…erion Record the verify gate PASS for xshard-read-fastpath: phase done, milestone task checkbox + exit criterion ticked. The cross-shard c1-GET exit criterion is MET on the contract's RELATIVE form (same-run +18-23% recovery → regression -19.4% <= ~20% vs 3e376a1). The absolute ">=30k" sub-line is RETIRED, not met (~25k): the whole-VM baseline drifted 37580->31437->20243 across three clean runs, so absolute RPS is not a stable instrument — documented in the §7 OBSERVE delta, recorded transparently rather than silently checked. v2-performance stays ACTIVE: two bottleneck tasks remain (ft-search-off-eventloop, wal-group-commit) plus the deferred xshard-read-coalescing follow-up. author: Tin Dang
The monoio handler's C2 batch-depth-gate binding exceeded the 100-col limit (longer `oneshot_futures` + `|(_, meta, _)|` closure). Wrap the initializer onto its own line — pure whitespace, no semantic change. Restores `cargo fmt --check` clean (the only red stage in the local CI-parity matrix; clippy x2 + both full test suites were already green, 0 failed). author: Tin Dang
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
📝 WalkthroughWalkthroughImplements an idle-gated adaptive busy-spin for cross-shard single-key read replies on both the monoio and sharded handlers, using a new thread-local in-flight counter and RAII Changesxshard-read-fastpath: idle-gated busy-spin + dead flag cleanup
Sequence Diagram(s)sequenceDiagram
participant Client
participant Handler as monoio / sharded Handler
participant Gate as xshard_should_spin
participant Slot as ResponseSlot / reply_rx
participant ShardWorker as Target Shard Worker
Client->>Handler: cross-shard GET request
Handler->>ShardWorker: dispatch read (SPSC channel)
Handler->>Handler: XshardWaitGuard::new() → XSHARD_INFLIGHT++
Handler->>Gate: xshard_should_spin(batch_remote_total)
alt inflight ≤ SPIN_GATE AND batch_remote = 1
loop ≤ XSHARD_SPIN_BUDGET iterations
Handler->>Slot: try_take() / try_recv()
ShardWorker-->>Slot: write response
Slot-->>Handler: Some(frames)
Handler-->>Client: reply (early return)
end
else gate closed or deep pipeline
Handler->>Slot: await future_for / race2 chunked wait
Slot-->>Handler: frames
Handler-->>Client: reply
end
Handler->>Handler: XshardWaitGuard::drop() → XSHARD_INFLIGHT--
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
scripts/probe-xshard.sh (1)
27-30: 💤 Low valueConsider making commit hashes configurable.
The hardcoded commit hashes
3e376a1anda497602reduce flexibility. For a reusable diagnostic, consider accepting them as environment variables with these as defaults:+BASELINE_COMMIT="${BASELINE_COMMIT:-3e376a1}" +CURRENT_COMMIT="${CURRENT_COMMIT:-a497602}" for r in 1 2 3; do - probe "$BINS/moon-3e376a1-monoio" 1 "3e376a1 (v0.3.0 fast) LOCAL" - probe "$BINS/moon-3e376a1-monoio" 4 "3e376a1 (v0.3.0 fast) XSHARD" - probe "$BINS/moon-a497602-monoio" 1 "a497602 (HEAD slow) LOCAL" - probe "$BINS/moon-a497602-monoio" 4 "a497602 (HEAD slow) XSHARD" + probe "$BINS/moon-${BASELINE_COMMIT}-monoio" 1 "${BASELINE_COMMIT} (v0.3.0 fast) LOCAL" + probe "$BINS/moon-${BASELINE_COMMIT}-monoio" 4 "${BASELINE_COMMIT} (v0.3.0 fast) XSHARD" + probe "$BINS/moon-${CURRENT_COMMIT}-monoio" 1 "${CURRENT_COMMIT} (HEAD slow) LOCAL" + probe "$BINS/moon-${CURRENT_COMMIT}-monoio" 4 "${CURRENT_COMMIT} (HEAD slow) XSHARD"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/probe-xshard.sh` around lines 27 - 30, Replace the hardcoded commit hashes 3e376a1 and a497602 in the probe function calls with environment variables. Define environment variables at the top of the script (for example, COMMIT_HASH_1 and COMMIT_HASH_2) with default values set to the current hardcoded hashes, then substitute these variables into the probe calls. This allows the script to be reused with different commit hashes without requiring edits to the script itself.scripts/remeasure-xshard-fix.sh (1)
24-29: 💤 Low valueConsider cleaning up temporary directories.
The
measurefunction creates a temp directory on line 24 but doesn't remove it after killing the process on line 28. While the OS will eventually clean/tmp, explicitly removing it would be cleaner:vals+=("$(bench "$4" "$5" "$6")") kill "$pid" 2>/dev/null; wait "$pid" 2>/dev/null + rm -rf "$dir" done🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/remeasure-xshard-fix.sh` around lines 24 - 29, The measure function creates a temporary directory stored in the dir variable but does not clean it up after the process is killed. After the kill and wait commands on line 28, add an explicit cleanup command to remove the temporary directory stored in the dir variable to ensure proper resource management and avoid accumulating temporary files in /tmp.scripts/baseline-xshard-quiesced.sh (1)
146-147: 💤 Low valueMedian calculation handles both odd and even sample counts correctly.
The ternary expression correctly computes the median: for odd N it takes the middle element
a[(NR+1)/2], and for even N it averages the two middle elements(a[NR/2]+a[NR/2+1])/2. Note thatremeasure-xshard-fix.shuses a simpler rounding approach instead of averaging; consider aligning them for consistency.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/baseline-xshard-quiesced.sh` around lines 146 - 147, The median calculation in the med variable using the awk ternary expression is logically correct, but for consistency with the simpler rounding approach used in remeasure-xshard-fix.sh, update the med variable calculation to match that file's median computation method instead of using the averaging approach for even-sized sample counts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/spike-xshard-read.sh`:
- Line 30: Remove the `--disk-offload disable` flag from the Redis server
startup command in the script to prevent unrecognized option errors when
measuring against older commits that don't support this flag. The flag appears
in the command string alongside other flags like `--appendonly no` and should be
deleted entirely from the line to ensure compatibility across different binary
versions being benchmarked.
In `@src/server/conn/handler_sharded/mod.rs`:
- Around line 1649-1660: The busy-spin logic controlled by
xshard_should_spin(batch_remote_total) is currently applied to all remote
operations, including writes, but it should only apply to remote reads. Add an
additional condition to the if statement that gates the spin loop to ensure it
only executes for read operations, not write operations. Since meta already
carries aof_bytes information, check that aof_bytes is zero or empty (indicating
a read-only operation with no AOF overhead) before entering the spin loop. This
ensures the fast path optimization remains limited to singleton remote reads as
originally intended, preventing unnecessary CPU usage on the slower write/AOF
path.
In `@src/shard/slice.rs`:
- Around line 283-315: The XshardWaitGuard struct must be made thread-affine to
prevent it from migrating across threads when held across an await point on a
multithreaded runtime. Replace the `_priv: ()` field with `_priv:
PhantomData<Rc<()>>` to add a !Send marker. This enforces that the guard cannot
move between threads, ensuring the increment and decrement operations on the
thread-local XSHARD_INFLIGHT counter happen on the same thread and preventing
counter corruption as documented in the struct comments.
---
Nitpick comments:
In `@scripts/baseline-xshard-quiesced.sh`:
- Around line 146-147: The median calculation in the med variable using the awk
ternary expression is logically correct, but for consistency with the simpler
rounding approach used in remeasure-xshard-fix.sh, update the med variable
calculation to match that file's median computation method instead of using the
averaging approach for even-sized sample counts.
In `@scripts/probe-xshard.sh`:
- Around line 27-30: Replace the hardcoded commit hashes 3e376a1 and a497602 in
the probe function calls with environment variables. Define environment
variables at the top of the script (for example, COMMIT_HASH_1 and
COMMIT_HASH_2) with default values set to the current hardcoded hashes, then
substitute these variables into the probe calls. This allows the script to be
reused with different commit hashes without requiring edits to the script
itself.
In `@scripts/remeasure-xshard-fix.sh`:
- Around line 24-29: The measure function creates a temporary directory stored
in the dir variable but does not clean it up after the process is killed. After
the kill and wait commands on line 28, add an explicit cleanup command to remove
the temporary directory stored in the dir variable to ensure proper resource
management and avoid accumulating temporary files in /tmp.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f2627f49-9063-465a-9828-5d01b2477c8c
📒 Files selected for processing (39)
.add/CONVENTIONS.md.add/PROJECT.md.add/milestones/v1-shared-nothing/MILESTONE.md.add/milestones/v1-shared-nothing/RETRO.md.add/milestones/v2-performance/MILESTONE.md.add/state.json.add/tasks/hotpath-lock-quickwins/TASK.md.add/tasks/xshard-read-fastpath/TASK.mdCHANGELOG.mdscripts/baseline-xshard-quiesced.shscripts/bench-cross-shard-fastpath.shscripts/probe-xshard.shscripts/remeasure-xshard-fix.shscripts/spike-xshard-read.shsrc/admin/metrics_setup.rssrc/command/connection.rssrc/config.rssrc/server/conn/handler_monoio/mod.rssrc/server/conn/handler_sharded/mod.rssrc/server/response_slot.rssrc/shard/dispatch.rssrc/shard/slice.rstests/ft_search_multi_shard_as_of.rstests/ft_search_temporal_parity.rstests/hybrid_filter_backward_compat.rstests/hybrid_filter_multishard.rstests/hybrid_filter_tag.rstests/integration.rstests/kill_snapshot.rstests/lunaris_hybrid_ft_search.rstests/mq_integration.rstests/pipeline_auto_index.rstests/replication_test.rstests/txn_ft_search_snapshot.rstests/txn_kv_wiring.rstests/vacuum_commands.rstests/workspace_integration.rstests/xshard_cleanup_shape.rstests/xshard_fastpath_api.rs
💤 Files with no reviewable changes (16)
- scripts/bench-cross-shard-fastpath.sh
- tests/replication_test.rs
- src/command/connection.rs
- tests/hybrid_filter_tag.rs
- tests/mq_integration.rs
- tests/txn_kv_wiring.rs
- tests/hybrid_filter_multishard.rs
- tests/ft_search_temporal_parity.rs
- tests/lunaris_hybrid_ft_search.rs
- tests/txn_ft_search_snapshot.rs
- tests/hybrid_filter_backward_compat.rs
- tests/workspace_integration.rs
- tests/ft_search_multi_shard_as_of.rs
- tests/pipeline_auto_index.rs
- src/admin/metrics_setup.rs
- src/config.rs
| cleanup | ||
| local dir; dir="$(mktemp -d /tmp/moon-spike.XXXXXX)" # fresh VM-local dir — dodges diskfull + CWD-reload trap | ||
| "$BINARY" --port "$PORT" --shards "$SHARDS" --dir "$dir" \ | ||
| --appendonly no --disk-offload disable --admin-port 0 >/dev/null 2>&1 & |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if --disk-offload is recognized by the binary
"$BINARY" --help 2>&1 | grep -q 'disk-offload' || echo "WARN: --disk-offload not supported by this binary"Repository: pilotspace/moon
Length of output: 109
Remove --disk-offload disable flag or expect unrecognized option errors when measuring older commits.
The --disk-offload flag is not supported by the binary being measured. While acceptable for this focused spike, measuring commits from before this feature was introduced will require removing this flag to avoid errors.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/spike-xshard-read.sh` at line 30, Remove the `--disk-offload disable`
flag from the Redis server startup command in the script to prevent unrecognized
option errors when measuring against older commits that don't support this flag.
The flag appears in the command string alongside other flags like `--appendonly
no` and should be deleted entirely from the line to ensure compatibility across
different binary versions being benchmarked.
| if crate::shard::slice::xshard_should_spin(batch_remote_total) { | ||
| for _ in 0..crate::shard::slice::XSHARD_SPIN_BUDGET { | ||
| if let Some(r) = response_pool.slot_for(target).try_take() { | ||
| spun = Some(r); | ||
| break; | ||
| } | ||
| core::hint::spin_loop(); | ||
| } | ||
| } | ||
| match spun { | ||
| Some(r) => r, | ||
| None => response_pool.future_for(target).await, |
There was a problem hiding this comment.
Gate the busy-spin to remote reads only.
This condition currently spins for any singleton cross-shard batch, including writes, because it only checks batch_remote_total. meta already carries aof_bytes, so a lone remote SET/write will still busy-spin on the executor thread before falling back to future_for(...).await, which widens this fast path beyond the PR’s “singleton read” contract and risks burning CPU on the slower write/AOF path.
Suggested fix
- if crate::shard::slice::xshard_should_spin(batch_remote_total) {
+ let is_single_remote_read =
+ matches!(meta.as_slice(), [(_, None, _)]);
+ if is_single_remote_read
+ && crate::shard::slice::xshard_should_spin(batch_remote_total)
+ {
for _ in 0..crate::shard::slice::XSHARD_SPIN_BUDGET {
if let Some(r) = response_pool.slot_for(target).try_take() {
spun = Some(r);
break;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if crate::shard::slice::xshard_should_spin(batch_remote_total) { | |
| for _ in 0..crate::shard::slice::XSHARD_SPIN_BUDGET { | |
| if let Some(r) = response_pool.slot_for(target).try_take() { | |
| spun = Some(r); | |
| break; | |
| } | |
| core::hint::spin_loop(); | |
| } | |
| } | |
| match spun { | |
| Some(r) => r, | |
| None => response_pool.future_for(target).await, | |
| let is_single_remote_read = | |
| matches!(meta.as_slice(), [(_, None, _)]); | |
| if is_single_remote_read | |
| && crate::shard::slice::xshard_should_spin(batch_remote_total) | |
| { | |
| for _ in 0..crate::shard::slice::XSHARD_SPIN_BUDGET { | |
| if let Some(r) = response_pool.slot_for(target).try_take() { | |
| spun = Some(r); | |
| break; | |
| } | |
| core::hint::spin_loop(); | |
| } | |
| } | |
| match spun { | |
| Some(r) => r, | |
| None => response_pool.future_for(target).await, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/server/conn/handler_sharded/mod.rs` around lines 1649 - 1660, The
busy-spin logic controlled by xshard_should_spin(batch_remote_total) is
currently applied to all remote operations, including writes, but it should only
apply to remote reads. Add an additional condition to the if statement that
gates the spin loop to ensure it only executes for read operations, not write
operations. Since meta already carries aof_bytes information, check that
aof_bytes is zero or empty (indicating a read-only operation with no AOF
overhead) before entering the spin loop. This ensures the fast path optimization
remains limited to singleton remote reads as originally intended, preventing
unnecessary CPU usage on the slower write/AOF path.
| /// RAII guard that marks the current connection as an in-flight cross-shard | ||
| /// reply-waiter for its lifetime: increments `XSHARD_INFLIGHT` on construction, | ||
| /// decrements on drop. Wrap every cross-shard reply-wait in one so the idle gate | ||
| /// reflects reality even across early returns / cancellation. `!Send` in spirit — | ||
| /// the thread-local it guards is per-thread and the guard must not cross threads. | ||
| #[must_use = "the guard must be held for the duration of the reply-wait; dropping it early ends the in-flight window"] | ||
| pub struct XshardWaitGuard { | ||
| // Private field: construct only via `new()`, and a `PhantomData<Rc<()>>`-free | ||
| // marker is unnecessary because the type holds no data that could be moved | ||
| // across threads; callers keep it on the shard thread's stack. | ||
| _priv: (), | ||
| } | ||
|
|
||
| impl XshardWaitGuard { | ||
| /// Enter a cross-shard reply-wait: increment this thread's in-flight count. | ||
| #[inline] | ||
| pub fn new() -> Self { | ||
| XSHARD_INFLIGHT.with(|c| c.set(c.get().saturating_add(1))); | ||
| Self { _priv: () } | ||
| } | ||
| } | ||
|
|
||
| impl Default for XshardWaitGuard { | ||
| #[inline] | ||
| fn default() -> Self { | ||
| Self::new() | ||
| } | ||
| } | ||
|
|
||
| impl Drop for XshardWaitGuard { | ||
| #[inline] | ||
| fn drop(&mut self) { | ||
| XSHARD_INFLIGHT.with(|c| c.set(c.get().saturating_sub(1))); |
There was a problem hiding this comment.
Make XshardWaitGuard thread-affine with a !Send marker.
XshardWaitGuard currently implements Send because it only holds (), allowing the guard to migrate across threads if held across an .await on a multithreaded runtime. When the guard drops on a different thread than where it was created, the decrement will operate on a different thread-local XSHARD_INFLIGHT counter than the increment, corrupting both counters and skewing xshard_may_spin() decisions. Adding PhantomData<Rc<()>> enforces thread-affinity by making the type !Send, matching the documented invariant.
Suggested fix
pub struct XshardWaitGuard {
- // Private field: construct only via `new()`, and a `PhantomData<Rc<()>>`-free
- // marker is unnecessary because the type holds no data that could be moved
- // across threads; callers keep it on the shard thread's stack.
- _priv: (),
+ // Keep the guard thread-affine: the thread-local counter must be decremented
+ // on the same thread that incremented it.
+ _not_send: PhantomData<Rc<()>>,
}
impl XshardWaitGuard {
/// Enter a cross-shard reply-wait: increment this thread's in-flight count.
#[inline]
pub fn new() -> Self {
XSHARD_INFLIGHT.with(|c| c.set(c.get().saturating_add(1)));
- Self { _priv: () }
+ Self {
+ _not_send: PhantomData,
+ }
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// RAII guard that marks the current connection as an in-flight cross-shard | |
| /// reply-waiter for its lifetime: increments `XSHARD_INFLIGHT` on construction, | |
| /// decrements on drop. Wrap every cross-shard reply-wait in one so the idle gate | |
| /// reflects reality even across early returns / cancellation. `!Send` in spirit — | |
| /// the thread-local it guards is per-thread and the guard must not cross threads. | |
| #[must_use = "the guard must be held for the duration of the reply-wait; dropping it early ends the in-flight window"] | |
| pub struct XshardWaitGuard { | |
| // Private field: construct only via `new()`, and a `PhantomData<Rc<()>>`-free | |
| // marker is unnecessary because the type holds no data that could be moved | |
| // across threads; callers keep it on the shard thread's stack. | |
| _priv: (), | |
| } | |
| impl XshardWaitGuard { | |
| /// Enter a cross-shard reply-wait: increment this thread's in-flight count. | |
| #[inline] | |
| pub fn new() -> Self { | |
| XSHARD_INFLIGHT.with(|c| c.set(c.get().saturating_add(1))); | |
| Self { _priv: () } | |
| } | |
| } | |
| impl Default for XshardWaitGuard { | |
| #[inline] | |
| fn default() -> Self { | |
| Self::new() | |
| } | |
| } | |
| impl Drop for XshardWaitGuard { | |
| #[inline] | |
| fn drop(&mut self) { | |
| XSHARD_INFLIGHT.with(|c| c.set(c.get().saturating_sub(1))); | |
| /// RAII guard that marks the current connection as an in-flight cross-shard | |
| /// reply-waiter for its lifetime: increments `XSHARD_INFLIGHT` on construction, | |
| /// decrements on drop. Wrap every cross-shard reply-wait in one so the idle gate | |
| /// reflects reality even across early returns / cancellation. `!Send` in spirit — | |
| /// the thread-local it guards is per-thread and the guard must not cross threads. | |
| #[must_use = "the guard must be held for the duration of the reply-wait; dropping it early ends the in-flight window"] | |
| pub struct XshardWaitGuard { | |
| // Keep the guard thread-affine: the thread-local counter must be decremented | |
| // on the same thread that incremented it. | |
| _not_send: PhantomData<Rc<()>>, | |
| } | |
| impl XshardWaitGuard { | |
| /// Enter a cross-shard reply-wait: increment this thread's in-flight count. | |
| #[inline] | |
| pub fn new() -> Self { | |
| XSHARD_INFLIGHT.with(|c| c.set(c.get().saturating_add(1))); | |
| Self { | |
| _not_send: PhantomData, | |
| } | |
| } | |
| } | |
| impl Default for XshardWaitGuard { | |
| #[inline] | |
| fn default() -> Self { | |
| Self::new() | |
| } | |
| } | |
| impl Drop for XshardWaitGuard { | |
| #[inline] | |
| fn drop(&mut self) { | |
| XSHARD_INFLIGHT.with(|c| c.set(c.get().saturating_sub(1))); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/shard/slice.rs` around lines 283 - 315, The XshardWaitGuard struct must
be made thread-affine to prevent it from migrating across threads when held
across an await point on a multithreaded runtime. Replace the `_priv: ()` field
with `_priv: PhantomData<Rc<()>>` to add a !Send marker. This enforces that the
guard cannot move between threads, ensuring the increment and decrement
operations on the thread-local XSHARD_INFLIGHT counter happen on the same thread
and preventing counter corruption as documented in the struct comments.
The cross-shard idle-gated reply-spin entry under [Unreleased] was the only v2-performance changelog entry without its originating PR number, while the sibling WAL group-commit (#178) and FT.SEARCH off-event-loop (#179) entries both carry one. Add the "(PR #177)" tag so all three v2 stack entries are consistently attributable. Documentation-only; no code, test, or behavior change. author: Tin Dang Co-authored-by: Tin Dang <tin.dang@trustifytechnology.com>
Summary
Recovers the cross-shard single-key read latency that
shardslice-migration(#175) gave up, without re-introducing a cross-thread lock or growing per-key memory — discharging the cross-shard-read half of that PR's RISK-ACCEPTED waiver (expires 2026-08-01).A cross-shard GET pays two mandatory cross-thread wakes per round-trip (origin→owner request, owner→origin reply). This PR removes the reply-side wake with an idle-gated, batch-depth-gated reply-side spin: when the requesting shard has no other in-flight cross-shard waiters and the batch contains a single cross-shard read, the connection polls its reply slot instead of parking + being woken across threads. The second (origin→owner) wake is irreducible without RCU/snapshot reads, which were declined this milestone to protect moon's low-RSS differentiator — so this is the documented single-connection floor for the safe approach.
Also hard-removes the orphaned
--cross-shard-fast-pathflag and its dead metric/histogram/functions (zero production callers since the shardslice cutover).What changed
src/shard/slice.rs, both connection handlers,src/server/response_slot.rs)Cell<u32>inflight gate (xshard_may_spin) — single-threaded shard ⇒ no atomic/lock.XshardWaitGuardRAII spans the reply-wait.xshard_should_spin(batch_remote),XSHARD_SPIN_MAX_BATCH_REMOTE=1): only a singleton cross-shard read spins; any pipelined fan-out parks (prevents serializing the pipeline).ResponseSlottake consolidated into onetake_if_filled()helper (net −2 unsafe blocks).--cross-shard-fast-pathflag,CrossShardFastPathenum,cross_shard_fast_path_enabled,moon_cross_shard_lock_contention_total,moon_dispatch_cross_read_fastpath_latency_us, therecord_dispatch_cross_read_fastpath_*fns, and the hardcodedcross_read_fast_dispatches.clapnow rejects--cross-shard-fast-path. See CHANGELOG.Evidence
3-way same-run, best-of-5, quiesced + core-pinned OrbStack VM (absolute VM RPS is untrusted per the milestone's documented instrument exception — relative same-run ratio only):
vs the pre-regression tag 3e376a1, the with-mech c1-GET regression is −19.4% ≤ ~20% (the contract's relative target). The absolute "≥30k" line was retired (whole-VM baseline itself drifted 37580→31437→20243 across clean runs) — the durable signal is the same-run +18–23% recovery.
shardslice_live= real cross-shard reads,cross_shard_consistency_red= data correctness).scripts/test-consistency.sh197/197 @ shards=1/4/12 unchanged.audit-unsafe.sh218/218 (net −2 unsafe),audit-unwrap.sh0/0, zero new cross-thread lock (shardslice_shape.rs5/5), RSS flat (49456→48992 kB HWM under 4-shard-r 1M).clippy -D warnings×2 featuresets +fmt --checkclean.Verify caught + fixed (in-phase)
The first with-mech build regressed pipelined throughput −27.5% on s4-P16 — a synchronous spin serializes a pipelined cross-shard fan-out (exactly the contract's pre-named flag-#1 risk). Fixed by the batch-depth gate so only singleton reads spin; re-measured to +5.2% (table above).
Scope / deferral
Cross-connection read coalescing (C3, the multi-client P1 case) is deferred to follow-up task
xshard-read-coalescing(human-approved v2 change request). The dead-code typeCoalescedReadBatchis retained as the seam.Breaking change
--cross-shard-fast-pathis removed; passing it now errors. It has been a no-op since the shardslice cutover. Documented in CHANGELOG under[Unreleased] › Removed (BREAKING).🤖 ADD task
xshard-read-fastpath· milestone v2-performance · gate PASS 2026-06-14Summary by CodeRabbit
Release Notes
Breaking Changes
--cross-shard-fast-pathcommand-line flag.Performance Improvements
Documentation