Skip to content

storage: Use chunked round-robin exchange for persist sink - #35263

Closed
antiguru wants to merge 1 commit into
MaterializeInc:mainfrom
antiguru:mv_sink
Closed

antiguru wants to merge 1 commit into
MaterializeInc:mainfrom
antiguru:mv_sink

Conversation

@antiguru

@antiguru antiguru commented Mar 1, 2026 •

Copy link
Copy Markdown
Member

Replace Pipeline with a chunked round-robin exchange on the desired_input in write_batches.
With many workers, Pipeline causes each worker to write its own small batch per timestamp, increasing persist overhead.
The chunked exchange concentrates updates onto fewer workers, producing fewer but larger batches.

The ChunkedRoundRobinDistributor uses a single ContainerBuilder and ships chunk_size full containers to one target before rotating to the next.
The seed determines a fixed initial target per source, and on every flush the target resets to it so that all workers make consistent routing decisions regardless of flush count.

Controlled by a new storage_persist_sink_chunk_size dyncfg (default 1).
Set to 0 to fall back to Pipeline.

Changes

  • src/timely-util/src/pact.rs — ChunkedRoundRobinDistributor implementing timely's Distributor<C> trait, plus chunked_exchange convenience constructor.
  • src/storage-types/src/dyncfgs.rs — New STORAGE_PERSIST_SINK_CHUNK_SIZE dyncfg.
  • src/storage/src/render/persist_sink.rs — Wire up the exchange in write_batches, with Pipeline fallback when chunk size is 0.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

Thanks for opening this PR! Here are a few tips to help make the review process smooth for everyone.

PR title guidelines

  • Use imperative mood: "Fix X" not "Fixed X" or "Fixes X"
  • Be specific: "Fix panic in catalog sync when controller restarts" not "Fix bug" or "Update catalog code"
  • Prefix with area if helpful: compute: , storage: , adapter: , sql:

Pre-merge checklist

  • The PR title is descriptive and will make sense in the git log.
  • This PR has adequate test coverage / QA involvement has been duly considered. (trigger-ci for additional test/nightly runs)
  • If this PR includes major user-facing behavior changes, I have pinged the relevant PM to schedule a changelog post.
  • This PR has an associated up-to-date design doc, is a design doc (template), or is sufficiently small to not require a design.
  • If this PR evolves an existing $T ⇔ Proto$T mapping (possibly in a backwards-incompatible way), then it is tagged with a T-proto label.
  • If this PR will require changes to cloud orchestration or tests, there is a companion cloud PR to account for those changes that is tagged with the release-blocker label (example).

Comment thread src/timely-util/src/pact.rs Resolved
Comment thread src/timely-util/src/pact.rs Resolved
Comment thread src/storage/src/render/persist_sink.rs Resolved
@antiguru
antiguru marked this pull request as ready for review March 1, 2026 20:02
@antiguru
antiguru requested review from a team as code owners March 1, 2026 20:02
@antiguru
antiguru requested review from bkirwi and teskje March 1, 2026 20:03
Replace `Pipeline` with a chunked round-robin exchange on the
`desired_input` in `write_batches`. With many workers, `Pipeline`
causes each worker to write its own small batch per timestamp,
increasing persist overhead. The chunked exchange concentrates
updates onto fewer workers, producing fewer but larger batches.

The new `ChunkedRoundRobinDistributor` in `mz-timely-util` sends
`chunk_size` full containers to one target before rotating to the
next. Different timestamps start at different targets via a rotating
seed to avoid permanent skew.

Controlled by a new `storage_persist_sink_chunk_size` dyncfg
(default 1). Set to 0 to fall back to `Pipeline`.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@antiguru
antiguru marked this pull request as draft March 2, 2026 09:33
@antiguru

antiguru commented Mar 2, 2026

Copy link
Copy Markdown
Member Author

I looked into whether we could do the same in compute, but it's much harder. In compute, we have an operator that accepts data from a dataflow, and (negated) data from persist, and emits corrections according to some batch description. The batches are supposed to bring the persist input to the batche's upper, at which point the operator can advance the since, and potentially consolidate most of its data.

The data hash-partitions its inputs to ensure that records that would cancel are at the same worker. This also means that the batches are similarly hash-partitioned, although there is no requirement that they are. We'd like to take the batch and re-shard it to different workers.

This is difficult because the contents of the batch are somewhat independent of the frontiers. Each operator knows the persist and desired frontiers, and the batches it's supposed to write. It can write a batch when it brings the persist frontier to the desired frontier, and it knows exactly that it has seen all data from the two inputs. Extracting this into a separate operator is tricky because we need to maintain the same behavior, but lose context if we're not careful.

Specifically, we need to indicate the end of updates without releasing a capability, which is at odds with how we usually indicate progress to downstream operators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants