perf(durable-streams-rust): batch memory-mode meta sidecar flushes into a periodic sweep#4692
Open
balegas wants to merge 5 commits into
Open
perf(durable-streams-rust): batch memory-mode meta sidecar flushes into a periodic sweep#4692balegas wants to merge 5 commits into
balegas wants to merge 5 commits into
Conversation
…to a periodic sweep Memory mode burned ~5x wal-mode CPU at identical low load (#4691): every append fell back to a per-stream debounced flush — a tokio timer task + 100 ms sleep + spawn_blocking running a full sidecar rewrite, per stream per 100 ms. At 150 streams x 10 append/s that is ~1,500 sidecar writes/s plus timer churn. Give memory mode the same batched treatment wal mode got in #4675: appends (and TTL read touches) now only queue the stream via Store::mark_meta_dirty (a CAS-deduped dirty set); a single store-level sweeper flushes every dirty sidecar in one pass per second. The sweep skips hard-deleted streams so a pending flush can no longer resurrect an unlinked sidecar. Producer/access sidecar state is a documented non-durable lagging flush; the lag bound moves from 100 ms to the 1 s sweep cadence. Flush-on-close/delete/shutdown durable paths unchanged. Fixes #4691 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SmhCex16r9JUPxHunf7Roy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SmhCex16r9JUPxHunf7Roy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SmhCex16r9JUPxHunf7Roy
❌ 1 Tests Failed:
View the top 1 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
…-sweep Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SmhCex16r9JUPxHunf7Roy
Durable-streams PRs carry a changeset for the release notes; the auto-approve check treated it as out of scope and skipped approval. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SmhCex16r9JUPxHunf7Roy
samwillis
approved these changes
Jul 7, 2026
icehaunter
approved these changes
Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4691.
Problem
In
--durability memory, every append fell back toStreamState::schedule_meta_flush: a per-streamtokio::spawn+ 100 ms sleep +spawn_blockingrunning a full.metasidecar rewrite (JSON serialize +File::create+rename, plus parent-dir inode contention). Whenever the per-stream inter-append gap is ≥ the 100 ms debounce (any rate ≤ ~10 ops/s/stream — the common case at high stream cardinality), every stream pays a timer task plus a full sidecar rewrite every 100 ms. Wal mode stopped paying this in #4675 (the checkpoint batches sidecar writes), so memory mode — supposedly the cheaper path — burned ~5x wal's CPU at identical load.Fix
Give memory mode the same batched treatment (
packages/durable-streams-rust):Store::mark_meta_dirty(st)— appends (memory branch) and TTL read touches now only CASmeta_dirtyand, on the false→true edge, push the stream into a store-levelmeta_sweepset (deduped: at most one entry per stream per cycle). No timer task, no per-append write.Store::sweep_meta_once()— drains the set and writes every still-dirty sidecar in onespawn_blockingpass. It skips hard-deleted streams (Arcidentity check against the streams map), so a pending flush can no longer resurrect an unlinked sidecar — a race the old debounce had.spawn_meta_sweeper(main.rs) — a single 1 s ticker drives the sweep in both durability modes (wal appends still flush via the checkpoint; only TTL read touches use the sweeper there).StreamState::schedule_meta_flushis deleted. Durable flush paths (close/delete/shutdownwrite_meta_sync(st, true)call sites) are unchanged.The sidecar's producer/access state is documented as a non-durable, lagging flush; the lag bound moves from 100 ms to the 1 s sweep cadence — exactly the trade wal mode made in #4675.
Validation (local ds-bench, kind, server capped at 2 CPU)
ds-bench
sustainedsuite (10 append/s per stream, 256 B payloads, 90 s window, 1 fleet pod), baseline image = main @ 640509c, fix image = this branch:Memory-mode CPU drops ~4–6x at fixed load and lands below wal at every stream count — the issue's expected outcome (memory does strictly less work per append). Throughput held at the offered rate in every cell (
stable=True, zero errors); wal cells are unchanged within noise (its append path doesn't touch this code — only TTL read touches moved to the sweeper).bench-latency (in-repo host harness,
--quick, after the fix): memory-mode write ack mean 0.29–0.51 ms across 1–64 KiB payloads — within the ~0.2–0.5 ms band the issue expects; wal unchanged (2.5–3.3 ms, macOS F_FULLFSYNC dominated).Tests
store::meta_sweep_tests::mark_dedupes_and_sweep_flushes— CAS dedupe, sweep persists pending producer state, clears the flag, second sweep is a no-op.store::meta_sweep_tests::sweep_skips_hard_deleted_stream— a sweep after a hard delete does not resurrect the sidecar.handlers::memory_mode_tests::memory_append_defers_sidecar_to_store_sweep— a memory-mode append no longer flushes the sidecar within the old 100 ms debounce window; the batched sweep is what persists it.cargo test(105) and the protocol conformance suite (326 tests) pass; clippy clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01SmhCex16r9JUPxHunf7Roy