Hive Hive
Sign in

fix(kura): group-commit segment fsyncs to complete cold-mesh-bootstrap write throughput

GitHub issue · Closed

Metadata
Source
tuist/tuist #11511
Updated
Jul 5, 2026
Domains
Kura
Details

What

Group-commit the segment durability fsync in the kura store so concurrent artifact writes share a single fsync instead of one fsync per write. This removes the last serialization point on the cold-mesh-bootstrap write path that the parallel bootstrap fetch (#11464) left in place.

Why

Every artifact write (put_blob_artifactappend_reader_to_segment) held the global segment_write_lock across an fsync (sync_data). So all writes — foreground CAS saves and inbound peer replication alike — serialized at one fsync per write, capping write throughput at roughly 1 / fsync_latency, single-threaded, no matter how many writers were in flight.

This is the missing half of the cold-mesh-bootstrap throughput work. #11464 made a fresh node fetch bootstrap artifacts concurrently (buffer_unordered(BOOTSTRAP_ARTIFACT_FETCH_CONCURRENCY), 16 at a time) so it can finish a full bootstrap within KURA_BOOTSTRAP_TIMEOUT_MS instead of streaming serially. But all 16 of those concurrent fetches converge on the same per-write fsync and re-serialize there — the fetch is parallel, the durability is not, so the intended speedup is throttled back down to one-fsync-at-a-time. The same ceiling bites the cold-start replication storm, where many segments replicate into a recovering node at once.

Group-commit lets those N concurrent bootstrap/replication writes share ~1 fsync, so a fresh node finishes bootstrap — and a degraded mesh re-converges — materially faster, and the win grows with how much write concurrency is in flight (i.e. exactly the cold-start regime #11464 targets).

Reads were never the problem; this is purely the write/durability path.

Out of scope: this is unrelated to the Xcode-CAS-vs-public-endpoint benchmark. That slowness was a client-side routing issue (the launchd CAS daemon never reached the PN node), fixed separately in #11501; the warm-cache benchmark is read-mostly and does not exercise this path.

How

  • Appends stay serialized and ordered under segment_write_lock (and still fsync the outgoing segment on rotation) but no longer fsync per write. Each append reserves a monotonic pending_seq.
  • Durability happens after the lock in ensure_segment_durable: the first writer to win a separate fsync_lock performs a single fsync of the active segment and advances a durable_seq watermark to the latest reserved sequence. Writers already <= durable_seq return without syncing.
  • Correctness across rotation and the lock-free fsync race rests on one invariant: a segment is fsynced when it rotates out, so only the active segment can ever hold un-synced bytes. If the active segment rotated between a writer’s append and the group fsync, the rotation already made that writer’s bytes durable; otherwise the group fsync covers them. Either way every append <= durable_seq is durable.

Net effect: N concurrent writes produce ~1 fsync instead of N, and it batches harder under more load.

Validation

  • TDD test concurrent_artifact_writes_batch_segment_fsyncs: slows the fsync via a BeforeSegmentFsync failpoint, fires 16 concurrent writes (matching BOOTSTRAP_ARTIFACT_FETCH_CONCURRENCY) and asserts they batch. It fails on the first commit (16 fsyncs, RED) and passes here (GREEN); its wall-clock drops ~0.92s → ~0.10s.
  • Companion test concurrent_replicated_artifact_applies_batch_segment_fsyncs proves the same batching on the replication-apply path a bootstrapping node actually drives (apply_replicated_artifact_from_bytes → the shared persist_artifact_from_path_with_version), so the parallel bootstrap fetch isn’t silently re-serialized at the fsync.
  • New metric kura_segment_fsyncs_total (group-commit + rotation fsyncs), sampled from the store snapshot. Its rate against kura_artifact_writes_total is the on-node signal for the cold-node gate below — fsyncs should track batches, not writes.
  • All 276 unit tests pass, including the durability/persistence/rotation tests (segment_state_snapshot_survives_reopen, sweep_orphaned_segments_reclaims_crash_window_segment_and_metadata, bytestream_writes_persist_completely_under_concurrency).
  • cargo fmt --check and cargo clippy --all-targets both clean.

Reviewer notes

  • This is the durability-critical write path; the watermark/rotation reasoning above is what to scrutinize. The load-bearing invariant is “rotation fsyncs the outgoing segment.”
  • Not yet load-tested on a real node. Kept as a draft pending a cold-node validation: bring up a fresh node against a populated account (or replay a replication storm into a recovering mesh) and confirm the bootstrap/replication wall-clock drops (existing kura_bootstrap_duration_seconds) while rate(kura_segment_fsyncs_total) stays well below rate(kura_artifact_writes_total) under concurrent inbound writes. The warm-cache CAS benchmark is the wrong instrument here — it’s read-mostly and exercises the separate routing path fixed in #11501.

🤖 Generated with Claude Code

Flights

Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.

New Flights are paused Configure model inference, GitHub, and a sandbox provider to start another Flight. Existing results remain available below.
No Flights yet

Start a Flight and preserve its objective, outcome, and session here.

Comments
F
fortmarek Jun 26, 2026

Production benchmark validation — this fix is what makes the Xcode CAS cache usable on the private network.

The body scoped the benchmark out, correctly given the data at the time: every benchmark run then was CAS→public (the daemon-routing fix #11501 wasn’t in a canary yet), so the PN write path was never exercised. Once #11501 shipped in a canary and the benchmark’s CAS actually reached the PN node, the write storm this PR targets appeared — and this fix eliminates it.

runners-benchmark (mastodon, tuist-macos, TUIST_CACHE_ENDPOINT=<PN node>, 1 hyperfine run):

Config Xcode cache Module + Xcode warm-up saveCASArtifact errors
CAS→PN, node kura@0.10.12 (no fix), canary.4 178–190s 133s 1809 (The request timed out)
CAS→PN, node kura@0.10.13 (this fix), canary.8 90.9s 44.4s 0

Mechanism: on the unfixed node the warm-up build’s concurrent CAS writes serialize behind the per-write fsync and time out, so the PN cache never warms → the timed “warm” build misses → recompiles → ~180s (worse than no-cache, 56s). With group-commit the warm-up writes batch their fsyncs and complete cleanly → cache warms → 91s. #11501 (route CAS to the PN) and this PR (PN writes don’t time out) are a pair: #11501 alone regresses the benchmark; together they deliver it.

Attribution: the saveCASArtifact storm is node-side fsync serialization, so only the 0.10.12→0.10.13 runtime roll (~11:51 UTC) fixes it — the canary.4→.8 CLI bump can’t. The merged runtime (kura@0.10.13 = this PR’s merge 53b41736160) rolled to all canary+prod mesh pods with 0 restarts.