Repository navigation
Conversation
The design of record, the evaluation record and the two machine-checked protocol models land separately in MaterializeInc#38239, along with their CI checks. They have no dependency on this code and reviewing 4,700 lines of prose against 7,000 lines of compute is worse for both. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019G29DBfgE8LXpE5jamm2Zi
The design of record for isolating reads from index maintenance inside a compute replica, plus the two machine-checked protocol models and the experiment record behind it. `design.md` opens with the decomposition that shaped everything else: twelve reported symptoms, the mechanisms behind them, and which of sixteen candidate solutions reaches each. Every cell carries an evidence label, and the table deliberately includes mechanisms nothing here addresses, because a decomposition listing only the causes we have answers for is not a decomposition. Two of the widest-blast-radius mechanisms are among the unaddressed. `evaluation.md` records the experiments with their fixtures and venues. Several retired claims the design started with. The offload alone captures the peek-tail win, so the second runtime is not what removes head-of-line blocking between peeks. The offload is measurably worse than doing nothing when a peek queues behind a long operator activation. And the second runtime's case is temporary dataflows and introspection during hydration, not peeks at all. `peek-placement.md` parks the peek-substrate question as orthogonal, with the deciding experiment and its rule registered in advance rather than settled by implementing every candidate and learning afterwards. The models are checked in CI, because a model nobody runs reads as assurance without being any. `protocol-holds/` is TLA+ over the read-hold protocol and runs three configs: the shipped design, plus two that are expected to violate the invariant and whose passing would mean the model can no longer express the defect the design fixes. It earned its place by refuting broadcast-alone before that was implemented. `protocol/` is a Lean development of the command-ordering argument. `check-copyright.sh` gains an exemption for `lean-toolchain`, which is a version pin with nowhere to put a header. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019G29DBfgE8LXpE5jamm2Zi
The design of record, the evaluation record and the two machine-checked protocol models land separately in MaterializeInc#38239, along with their CI checks. They have no dependency on this code and reviewing 4,700 lines of prose against 7,000 lines of compute is worse for both. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019G29DBfgE8LXpE5jamm2Zi
c6e43ec to
bd6bfb6
Compare
The directory held seven documents, three of which covered the read-hold mechanism and two of which were spent scaffolding. It now holds one design document that targets the interactive runtime. The protocol section described the wrong mechanism. It specified capping the compaction frontier at the multiplexer, which the implementation replaced with broadcast compaction and a standing hold per shared collection. That section is rewritten from `broadcast-compaction.md`, including the counterexample that rules out broadcasting without the hold and the liveness coupling the hold introduces. The cap, the synthesized `AcquireHolds` command, and the in-process sequence barrier are recorded as rejected alternatives, with the reason each failed: the cap failed at its retirement point rather than at the cap, since `send` carries no ack. Both model checks are removed, along with their CI jobs and images. The TLA+ spec modelled the shipped mechanism and the Lean model did not: its scope was the controller's read hold and the multiplexer's cap, and both of its counterexamples target behaviours no longer present in any form. The reasoning each contributed survives as prose in the rejected alternatives. Peek placement becomes an orthogonal axis discussed once rather than an argument threaded through the document. The solution matrix keeps its row, because the comparison is what bounds the interactive runtime's claims, but the adjudication between placement candidates, the substrate follow-ups and the routing policy move to the tracking issues. `peek-placement.md` and `peek-offload-stash-plan.md` move to the branch that implements the offload. The experimental evaluation moves to a project document. Evaluation artifacts do not belong in a design, and the document also carried experiment protocol for arms the code no longer reaches the same way. Also corrected: the serving path claimed walking off the worker was abandoned, the arrangement-size doubling is recorded as not reproducing on staging, and the open findings drop the items describing deleted code.
The document described the branch as it stood before the implementation was split into reviewable layers, and three parts of it no longer matched the code. Peek execution shipped separately. The summary and the placement section said peek offloading was parked pending an experiment; it now records that the walk runs in budgeted slices with offload past the budget, that both are dyncfgs, and that the interactive runtime inherits that path. The serving path section replaces the runtime's own pending-peek variant with the unified `IndexPeek` over `IndexTraces`, and the registry section describes how a shared peek parks in `pending_work` until its target's dirty mark. Two open findings were fixed and are documented as mechanism rather than kept as defects. The reader's handle mirrors `TraceAgent`: it joins on set, reports the join, and seeds its physical hold at the chain coverage, so the inert physical hold finding is removed and the compaction section explains why the coverage is the only correct seed. The import keeps no separate hold pinned at `as_of`; the read hold is the returned arrangement's own trace handle, so the frozen-hold section becomes a description of the feedback path. Re-exports are aliases. The registry section replaces the sentence about sharing an `Arc` under a new id with the alias mechanism: `publish_alias`, its refusal when a reader bound first and the import-based fallback that covers it, the target-governed frontiers, and the meet over aliases after the target drops. The discarded-failure finding is removed and the measured cost of the earlier import-based publication is recorded. The multiplexer, configuration and testing sections are updated to the shipped routing predicate, the soft assertion on transient imports, the CI variable system parameter, the four driver specs, the sqllogictest, the feature and parallel benchmark scenarios, and the swap scenarios, with the numbers one nightly run produced. The implementation history lists the stacked pull requests. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VDm7opomJLxbNUEP3r9BLk
The shared import follows the dataflow's `until` instead of synthesising a bound one step past `as_of`, so the section that presented the bound as a call-site narrowing now places the single-time restriction where it lives, in the routing predicate. An export whose arrangement is an imported shared arrangement aliases its publication point instead of hitting an `unreachable!`, which closes that open finding and adds a fifth driver spec. The interactive runtime installs no logging dataflow, so the runtime tells the peer's publications apart by whether it hosts the collection rather than by transience, and the multiplexer's frontier filter is described as a guard on that invariant rather than as a repair for empty copies. The registry's state sits behind one lock. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VDm7opomJLxbNUEP3r9BLk
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VDm7opomJLxbNUEP3r9BLk
The sharing primitive became `SharedSpine`, a `Trace` wrapper that mirrors the spine's chain and frontiers into the publication point inside each mutation. The design records why a spine behind a mutex was rejected, with the measured reader and writer latencies, replaces the sink-based feed and compaction sections, drops the forwarded controller frontier, and marks the arrangement-size doubling as gone with the sink. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VDm7opomJLxbNUEP3r9BLk
Four questions the document left open have numbers now, all taken on one process and one binary with the replica-scoped dyncfg toggled between points, which is what every earlier comparison lacked. Publishing an index costs nothing in reported arrangement size. The doubling the document recorded belonged to the publisher sink operator, which the trace wrapper replaced; a published index now reports the same size and allocations as an unpublished one at 1, 1000, 100000, and 1000000 records. The compaction coupling has a metric. `mz_compute_shared_arrangement_hold_gap_ms` and `mz_compute_shared_arrangement_held_count` report, per worker, how far the readers' holds keep maintenance from applying the frontier its controller asked for, and how many arrangements are held at all. An interactive reader over a published index leaves the merge schedule alone but more than doubles the arrangement-maintenance CPU maintenance spends, which is recorded as a new open finding rather than explained. And the concrete long-lived import today, the coordinator's four introspection subscribes, costs 4.9% of one core on an idle replica and nothing measurable during hydration. That is what moving them off maintenance is worth. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_015tLhSbZdXrTSK2KwSocT59
The finding recorded that interactive reads roughly double maintenance's arrangement-maintenance CPU, and guessed at a per-read mechanism. Sweeping reader concurrency 1, 2 and 4, plus an arm at concurrency 4 against a join a hundred times smaller, holds it flat at 0.164, 0.144, 0.143 and 0.129 CPU-seconds per second against a quiet baseline of 0.068. It falls slightly as concurrency rises, which is what opportunistic `exert` does when the workers get busier. So the charge is not per read, per row, per dataflow install, or per reader. It is a fixed 6 to 10% of one core for having any reader attached, which is about 1% of what the reads cost the process, and it bounds no throughput. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_015tLhSbZdXrTSK2KwSocT59
Bring the implementation sections in line with the stack: placement by DataflowClass set in the controller, a passive multiplexer that broadcasts every command, peer holds in the TraceManager in place of the standing hold, per-worker registries without aliases, the shared import as an IndexTrace, the role components, and the enable_compute_interactive_dataflows flag. Record the saturated staging run, the persist fetch semaphore finding (CPU-298), two open findings from the latest stack review, and the latest nightly numbers. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The publisher registers the peer hold, registry slots are keyed by the incarnation's as_of, peer bundles are padded on reconnect, and the reconciliation spec runs against two runtimes. The pinning paragraph now states what the code does and leaves the layout to measurement. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…ed stack Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_015tLhSbZdXrTSK2KwSocT59
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_015tLhSbZdXrTSK2KwSocT59
…ialization Describe how per-runtime holds derived from the shared command stream, downgraded to the writer over a channel, remove the lock-based admission rules, which places still create holds on the reader thread, and the two physical-hold designs that keep the channel sound. Record the single-time specialization, where every interactive read takes one snapshot after the trace passes its time, as the variant to land first, and the result of the owned-agent spike. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_015tLhSbZdXrTSK2KwSocT59
bosconi
left a comment
There was a problem hiding this comment.
The design needs a few corrections before it merges, mainly the inverted M4 comparison and the unsupported Solo claim; details inline.
- Add a Verification section to the PR description saying the document was checked against the stack, since that is the failure the revision notes describe fixing.
- The revision notes do not mention the new Simplifying the sharing protocol section, the only forward-looking design in the document.
| ### What follows | ||
|
|
||
| Two conclusions bear on this design. The rest of the matrix belongs to the peek program and | ||
| is tracked in the project's peek issues. |
There was a problem hiding this comment.
| the instance's logging configuration arrives it records every log index as a peer at the | ||
| minimum time, and the maintenance runtime publishes them. | ||
|
|
||
| #### The price: compaction is coupled to the rendering runtime's drain rate |
There was a problem hiding this comment.
nit: the body says reading runtime; the heading still says rendering.
| #### The price: compaction is coupled to the rendering runtime's drain rate | |
| #### The price: compaction is coupled to the reading runtime's drain rate |
| `AllowCompaction`, so an interactive runtime that drains promptly leaves the two frontiers | ||
| equal. On an 8-worker replica under continuous churn the gauges read zero with no reader, and | ||
| peaked at one compaction round with 31 arrangements held while 509 interactive joins ran over | ||
| the published index. The lag did not grow with time under that load. |
There was a problem hiding this comment.
Give the hold-gap gauges an actionable threshold, even a provisional one, for when the interactive runtime has stopped draining and what to do then. This is the only detection for the one new price the design names. Also say here that the gauges are added in #38736, which is still a draft.
| publishing `since` before applying it, the peer-hold reservation and its FIFO pairing, and the | ||
| physical hold an import registers at the chain coverage. This section records two designs that | ||
| remove those rules. The first is general and keeps imports live. The second exploits the | ||
| one-shot-read boundary, removes more, and is the one to land first. |
There was a problem hiding this comment.
Landing the specialization first removes ImportQueue, incremental replay and the per-read logical hold (1272-1282), which S14 (235-246), Compaction feedback and Non-goals (1490-1492) present as in place. Either say here that the general design is kept alongside the specialization, or say in those three places that they depend on the general design landing.
| and the writer merges freely. The snapshot log is unnecessary, because each read needs one | ||
| snapshot. | ||
| * **Seeding through placeholders.** A read over a slot that is not yet published waits. It | ||
| does not bind to the point before the trace exists, so attach has nothing to seed. |
There was a problem hiding this comment.
| loaded, on either runtime. It is larger than the tail that runtime placement | ||
| recovers, so for the workload M3's remedy is justified by, this is the dominant | ||
| term. |
There was a problem hiding this comment.
M4's floor of about 900 ms is smaller than the roughly 1400 ms tail that placement recovers (321-323, E7's 2835 to 1456 ms), so it is not the dominant term. The same correction is needed at 320-323, 379-380 and 439-441.
| loaded, on either runtime. It is larger than the tail that runtime placement | |
| recovers, so for the workload M3's remedy is justified by, this is the dominant | |
| term. | |
| loaded, on either runtime. At about 900 ms it is roughly two thirds of the tail | |
| that runtime placement recovers, so for the workload M3's remedy is justified | |
| by, it is a first-order cost next to that benefit. |
| > **I1c.** A shared arrangement compacts only as fast as the slowest runtime's stream | ||
| > position. |
There was a problem hiding this comment.
I1c names a rate, not a relation, so no assertion or test can check it. State it as a predicate, matching the restatement at 1205-1206 and what a_peer_bundle_pins_until_the_importing_runtime_applies asserts.
| > **I1c.** A shared arrangement compacts only as fast as the slowest runtime's stream | |
| > position. | |
| > **I1c.** For every published index `I`, the publishing trace's `since` is at or below the | |
| > reading runtime's peer hold on `I`: the `as_of` of `I`'s `CreateDataflow`, advanced by every | |
| > `AllowCompaction` for `I` that the reading runtime has applied. |
| * `Solo` is the sole runtime of a single-runtime process and is behaviorally identical to a | ||
| deployment from before this work. Its metric and log label is `None`, so a single-runtime | ||
| registration collides with nothing and looks unchanged. |
There was a problem hiding this comment.
typedefs.rs:46-55 wraps every spine in SharedSpine with no role or flag gate, so Solo is not behaviorally identical to a pre-work deployment, and the wrapper's cost is unmeasured. The summary at 20-21 makes the same claim.
| * `Solo` is the sole runtime of a single-runtime process and is behaviorally identical to a | |
| deployment from before this work. Its metric and log label is `None`, so a single-runtime | |
| registration collides with nothing and looks unchanged. | |
| * `Solo` is the sole runtime of a single-runtime process. It renders every class, publishes | |
| nothing and reads nothing from the registry, and its metric and log label is `None`, so a | |
| single-runtime registration collides with nothing and looks unchanged. It is not unchanged | |
| underneath: every spine in `mz_compute::typedefs` is a `SharedSpine` regardless of role, so | |
| `Solo` runs every trace call through the unattached wrapper. |
| only approximate. The wrapper reads both off the spine, and the registry stopped | ||
| forwarding the controller's frontier. |
There was a problem hiding this comment.
1077-1080 says the published since is the frontier the wrapper is about to apply, matching apply_holds. "Reads both off the spine" describes the previous order.
| only approximate. The wrapper reads both off the spine, and the registry stopped | |
| forwarding the controller's frontier. | |
| only approximate. The wrapper publishes the spine's chain and the exact `since` it is about | |
| to apply, and the registry stopped forwarding the controller's frontier. |
| |---|---|---| | ||
| | Point lookup under hydration churn | 3.3 / 28 ms | 186 / 1683 ms | | ||
| | Range count under hydration churn | 27 / 56 ms | 1635 / 4884 ms | | ||
| | Introspection read under hydration churn | 99 / 168 ms | 481 / 519 s | |
There was a problem hiding this comment.
This cell is censored by the load phase, not a measured latency; mark it as a lower bound so nobody derives a speedup ratio from it.
| | Introspection read under hydration churn | 99 / 168 ms | 481 / 519 s | | |
| | Introspection read under hydration churn | 99 / 168 ms | > 481 / > 519 s | |
Motivation
A compute replica maintains indexes and serves reads on the same timely workers, and timely does not preempt a running task. So a read waits behind maintenance work, not because the machine is out of CPU but because the one run loop is busy and cannot be interrupted. The sharpest form is introspection:
mz_introspectiondescribes a replica's own state and cannot be served from any other replica, so it is unavailable exactly when an operator needs it.This is design only. No production code. It is the design of record for the implementation stack, #38386 through #38393, with the benchmarks in #38676 and #38678 and the hold-gap metric in #38736, and it describes what those pull requests ship.
Description
One document,
design.md. It targets the interactive runtime: a second in-process timely runtime that renders temporary dataflows and serves reads off the maintenance runtime's arrangements, zero-copy through a per-process sharing registry.It opens with the decomposition that shaped everything else: twelve reported symptoms, the mechanisms behind them, and which of sixteen candidate solutions reaches each. Every cell carries an evidence label distinguishing measured from argued from read-off-the-code, and the table deliberately includes mechanisms nothing here addresses. Two of the widest-blast-radius ones are among those, which is the point: a decomposition listing only the causes we have answers for is not a decomposition.
Several claims the work started with are retired in it:
Peek placement is therefore treated as an orthogonal axis and discussed once. It needs no second runtime and this design needs none of it. It shipped separately, as budgeted peek slices on the worker with offload past the budget, and #38449 is its design. The interactive runtime inherits that path unchanged.
The protocol section describes what shipped: the compute controller tags each dataflow with a
DataflowClass, the multiplexer forwards every command to both runtimes, and the runtime that does not render a dataflow records its exports as peers, holding each peer index at itsas_ofin theTraceManager. It includes the counterexample that rules out broadcasting without the hold and the liveness coupling the hold introduces. Capping the frontier at the multiplexer, a synthesizedAcquireHoldscommand, an in-process sequence barrier, and routing creates while broadcasting only compaction are recorded as rejected alternatives with the reason each failed.What the current revision changed
The document now describes the reworked stack.
DataflowClasson the dataflow description, a passive broadcasting multiplexer, peers, andenable_compute_interactive_dataflowsto keep peek dataflows off the interactive runtime.IndexTracein theTraceManager, with oneimport_indexfor both provenances, so it honoursSnapshotMode.What moved out
The experimental evaluation is a project document under Interactive read isolation, which also tracks the work as issues. Evaluation artifacts do not belong in a design.
Both model checks are removed with their CI jobs. The TLA+ spec modelled the shipped mechanism; the Lean model's scope was the controller's read hold and the multiplexer's cap, and both of its counterexamples target behaviours no longer present in any form. The reasoning each contributed survives as prose in the rejected alternatives.
🤖 Generated with Claude Code
https://claude.ai/code/session_01VDm7opomJLxbNUEP3r9BLk