compute: a shared-trace primitive for cross-thread arrangement reads - #38386
compute: a shared-trace primitive for cross-thread arrangement reads#38386antiguru wants to merge 2 commits into
Conversation
fda6a81 to
ebd007b
Compare
ebd007b to
870f336
Compare
An arrangement is normally readable only from the timely worker that maintains it, because its batches are `Rc`-backed and its trace handle is neither `Send` nor `Sync`. This adds a publication point that carries `Arc`-backed batches together with the trace's `since` and `upper`, so a reader on any thread can mint a `Send` handle for the same arrangement and import it as a snapshot at a chosen `as_of`. Nothing in the crate calls it yet, so the module is inert: it compiles, its unit tests exercise publish, import, seal, and compaction holdback, and no rendered dataflow reaches it. A publication point is differential's `TraceBox` for readers that are not agents of the trace. It accumulates their holds in a `MutableAntichain` per axis and each handle adjusts that accumulation as a delta, the way a `TraceAgent` does, which costs the times that changed rather than a walk over every hold. Two special cases go with it: an empty request contributes nothing instead of having to be filtered out, and there is no zero-holds case to fall back from. The standing hold and the publisher's own hold at the chain coverage are ordinary holds in those accumulations, so a shared arrangement compacting no faster than the slowest runtime's command stream follows from a registered hold rather than from an invariant asserted after the fact. The controller's own frontier stays out of the accumulation. It is another agent's hold on the same trace, so it belongs to the meet the trace already computes, which is what the publisher publishes as `since`. The concrete `SharedOks*`/`SharedErrs*` type aliases live here rather than alongside the registry that will consume them. They name a shared-trace handle over `RowRowSpine` and `ErrSpine` and mention no registry type, so this is where they belong. `Published::diagnostics`, `note_writer_logical`, and `note_standing_hold` are `pub` like the rest of the type's accessors. Scoping them to the crate would make them unreachable for dead-code analysis while the only callers are the tests. Tests are out of line in `shared_trace/tests.rs`, per the convention in `src/compute/AGENTS.md`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
870f336 to
b508d89
Compare
…nt sharing (MaterializeInc#38396) Replaces MaterializeInc#37881, whose head branch lives on a fork and so cannot be the base of a stacked PR in this repository. Same commits, same tree, on an upstream branch instead. This is the root of the stack MaterializeInc#38386 through MaterializeInc#38393, which splits MaterializeInc#37770. ### Motivation Cross-runtime arrangement sharing (the two-runtime read-isolation work, MaterializeInc#37770) needs batches readable from a thread other than the one maintaining the trace. Differential's default spines reference-count batches with `Rc`, which is worker-local. ### Description Introduce `mz_row_spine::ArcBatch`, a local newtype around `Arc<B>` that carries differential's batch traits (the orphan rule forbids the blanket impl on a bare `Arc<B>`), and switch the production spines and their builders — `RowRowSpine`, `RowValSpine`, `RowSpine`, `ValRowSpine`, `ColValSpine`, `ColKeySpine` — from `Rc`/`RcBuilder` to `ArcBatch`/`ArcBuilder`. An `Arc`-backed batch whose contents are `Send + Sync` can be read across threads, which `Rc` cannot do. Only the batch handle becomes atomic; the batch contents are unchanged, so the cost is a marginally more expensive refcount. Also adds generic `ArcOrdVal`/`ArcOrdKeySpine` aliases for callers outside `mz_compute`, adapts batch-size logging (`log_arrangement_size_inner`) to reach through the newtype to the inner `Arc`, and switches the storage sink trace to the `Arc`-backed spine. Builds against released differential-dataflow 0.25 with no fork or `[patch.crates-io]`. ### Verification `cargo check --workspace` passes with no `Cargo.lock` churn. `relations.slt`'s golden is rewritten because the spine type name appears in operator names. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
QA LLM Review1. MEDIUM -- a live import permanently pins the published arrangement's physical compaction
The read hold DetailsMeasured on a Registration id 2 is the Cost is unbounded rather than constant: retractions in stranded Suggested fix, verified against the same probe (chain folds to 5, exactly matching the control, and the hold tracks to if let Some(hold) = hold.as_mut() {
hold.set_logical_compaction(acknowledged.borrow());
+ hold.set_physical_compaction(acknowledged.borrow());
}
|
A handle carries two physical frontiers, and they are not interchangeable. The one it reports through `get_physical_compaction` is seeded at the published `since`, because a reported frontier may never lead the chain coverage. The one it holds is seeded at that coverage, because a merge spanning the coverage destroys the boundary the reader was seeded with. `Clone` and the setter both wrote the reported frontier into the hold, which silently lowers it. Since the accumulation is a meet, one such registration is a floor under every other hold, so the published spine stops merging: batches pile up in `Spine::pending`, one per seal, for as long as that registration lives. The cost is unbounded rather than constant, since retractions in stranded batches never consolidate and every `cursor_through` builds a `CursorList` over all of them. An import's read hold hit this on both counts. It is a clone, so it registered at `since`, and it advanced only on the logical axis, so nothing ever raised it. Measured against an unimported control over 40 seals: 39 batches against 5. Keep the two frontiers in separate fields, have `Clone` inherit the hold, have the setter join into both, and advance the import's hold on both axes. `acknowledged` is the right value for the physical axis too: it is exactly the frontier below which that import will never cut again. Reported by the QA LLM review on #38386. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Confirmed and fixed in 285af14. The measurement reproduces exactly: 39 batches against an unimported control's 5, over 40 seals. Both of the report's points were real, and the second one is the root cause rather than a separate cleanup. A handle carries two physical frontiers that are not interchangeable. The one it reports through The import's read hold hit this on both counts: it is a clone, so it registered at The fix keeps the two frontiers in separate fields, has Two regression tests, each verified red without its half of the fix:
The The stack above this PR is rebased and pushed. Full run at the tip: 124 tests, workspace (Posted by Claude Code.) |
First of eight PRs splitting #37770. Stacks on #38396. Tracked by CPU-215.
An arrangement is normally readable only from the timely worker that maintains it, because its batches are
Rc-backed and its trace handle is neitherSendnorSync. This adds a publication point carryingArc-backed batches together with the trace'ssinceandupper, so a reader on any thread can mint aSendhandle for the same arrangement and import it as a snapshot at a chosenas_of.A publication point is differential's
TraceBoxfor readers that are not agents of the trace. It accumulates their holds in aMutableAntichainper axis, and each handle adjusts that accumulation as a delta the way aTraceAgentdoes. The standing hold and the publisher's own hold at the chain coverage are ordinary holds in those accumulations, so a shared arrangement compacting no faster than the slowest runtime's command stream follows from a registered hold rather than from an invariant asserted after the fact. The controller's own frontier stays out, since it is another agent's hold on the same trace and so belongs to the meet the trace already computes, which is what the publisher publishes assince.Inert: nothing in the crate calls it. The module is
pubso that its accessors are reachable for dead-code analysis while the only callers are its own tests.PublishArrangementrequiresTr::Batch: Send + Syncand the tests instantiate it overRowRowSpine, so Arc-backed spines are a compile prerequisite rather than a preference. This PR retargets tomainonce #38396 merges.