compute: import published indexes as a shared arrangement - #38390
compute: import published indexes as a shared arrangement#38390antiguru wants to merge 3 commits into
Conversation
cf8f1f6 to
5c374d3
Compare
5c374d3 to
1bce309
Compare
1bce309 to
35cb94e
Compare
f92f260 to
aa7c8eb
Compare
aa7c8eb to
db731c1
Compare
db731c1 to
7f75767
Compare
7f75767 to
ecc45ca
Compare
ecc45ca to
00d66cc
Compare
00d66cc to
c4afb21
Compare
c4afb21 to
5bb8e79
Compare
QA LLM Review1. MEDIUM -- Delta join's
|
5bb8e79 to
6807118
Compare
|
All three fixed in 6807118. The delta join's Posted by Claude Code. |
6807118 to
fb79274
Compare
fb79274 to
8a82082
Compare
QA LLM Review1. MEDIUM --
|
| // The nine `(stream flavor) x (lookup flavor)` combinations differ only in the two trace | ||
| // types handed to the generic `differential_join_inner` and the two arrangement values | ||
| // consumed. This local macro spells one combination. The `SharedTrace` rows exist so an | ||
| // interactive-runtime join over imported indexes type-checks. At runtime a dataflow's | ||
| // arrangements are all one runtime's flavor, so the mixed rows never fire, but exhaustive | ||
| // matching requires them. | ||
| macro_rules! join { | ||
| ($stream:expr, $stream_tr:ty, $lookup:expr, $lookup_tr:ty, $errs1:expr) => {{ | ||
| let (oks, errs2) = self | ||
| .differential_join_inner::<$stream_tr, $lookup_tr>($stream, $lookup, closure); | ||
| errors.push($errs1.as_collection(|k, _v| k.clone())); | ||
| errors.extend(errs2); | ||
| oks | ||
| }}; | ||
| } |
There was a problem hiding this comment.
Macro is fine; but could the also be a vcall instead?
There was a problem hiding this comment.
Not without a branch in the per-record loop. TraceReader and Cursor carry GATs (Key<'a>, Val<'a>, Cursor<'a>), so neither is dyn-compatible. The alternative is an enum over the three trace types plus an enum over their cursors, with a match in every key, val, step_key, and so on, which is a dispatch per cursor operation inside the join's inner loop. The macro costs monomorphisation only, three new combinations of the nine. Keeping it.
Posted by Claude Code.
| /// NOTE: the `TraceFrontier` bound on the imported handles is derived from the dataflow's | ||
| /// `as_of`, not from its `until`, so this variant serves single-time dataflows only. Nothing | ||
| /// checks that at the type level. |
There was a problem hiding this comment.
I don't follow the reasoning: The bound should always be derived from the as-of, as we're interested in the data in (as_of, until[, right? What implies that this can only serve single-time dataflows?
There was a problem hiding this comment.
You are right, and the reasoning was wrong. The maintenance import passes (as_of, until) to import_frontier_core and follows the upper when until is empty. Nothing about the shared import needs a different bound, and the single-time restriction lives in the router, which admits only is_single_time() dataflows. So snapshot_until equalled self.until for every dataflow that reached this import, and the hardcoding bought nothing while forbidding a long-lived dependent later.
import_index_shared now passes self.until. The NOTE on the variant, the "serves single-time dataflows only" sentence, and the try_step_forward special case for Timestamp::MAX are gone. is_single_time() at MAX yields an empty until, so the import follows the trace to its terminal frontier there, the same as the maintenance import.
Posted by Claude Code.
| /// permuted as the plan expects, so a `Get` of `idx.on_id` and the joins and reduces below it | ||
| /// consume an arrangement rather than re-deriving one. The import is a snapshot at `as_of`, so | ||
| /// it serves single-time dataflows only. | ||
| fn import_index_shared<'outer>( |
There was a problem hiding this comment.
We have fn import_index_shared, and fn import_shared_index, which can be misread. Do we have more than one use of the import_shared_index free function? It's fine to keep if this is the cleanest, and the free function isn't pub.
There was a problem hiding this comment.
One production caller (import_index_shared) and the render tests. It is not pub. Renamed the free function to import_published_index, which is the registry's vocabulary and no longer reads as a permutation of the method's name.
Posted by Claude Code.
| Some(ArrangementFlavor::SharedTrace(..)) => { | ||
| // Only the interactive runtime produces `SharedTrace`, and only for imports it reads | ||
| // from the sharing registry. Its exports are transient query outputs, which are | ||
| // freshly rendered `Local` arrangements (a join/reduce output), never a direct | ||
| // re-export of an imported shared arrangement. The maintenance runtime's imports are | ||
| // `Local`/`Trace`. So an export can never observe a `SharedTrace` input. | ||
| unreachable!( | ||
| "interactive runtime does not re-export an imported shared arrangement" | ||
| ); | ||
| } |
There was a problem hiding this comment.
I think this encodes an unwritten invariant: A peek against a re-export dataflow would be totally valid right now, but now wouldn't be anymore. I do not think the optimizer guarantees that it doesn't do no-op re-exports. What'd be the cost of re-exporting a shared trace?
There was a problem hiding this comment.
Agreed that nothing guaranteed it. Both unreachable! arms, here and in export_index_iterative, are now the shared-arrangement analogue of the Trace arm: alias_shared_reexport registers idx_id as an alias of gid's publication point through publish_alias, so a peek on idx_id reads the imported arrangement. Only import_published_index creates slots and a dataflow routed here imports no transient id, so no reader can have created idx_id's slot first, and the alias always registers, which is asserted.
The one thing the Trace arm has that this cannot is a TraceBundle to install, and report_frontiers reads the write frontier from it. The alias has no trace of its own, so report_frontiers falls back to the published point's upper through a new ArrangementSharingRegistry::published_upper, which is the same frontier the maintenance re-export reports. Everything else already worked: TraceManager::allow_compaction is a no-op on a missing id, Aliases::note defers to the target while it lives, and drop_collection removes the alias.
Cost of the re-export itself is a map insert. A protocol invariant would have to mirror what ArrangeBy does over the imported Get, and any simpler rule over-rejects a filter followed by a re-arrange under an import's key, so I did not pursue it.
#38393 gains two_runtime_reexport_interactive.spec, a query dataflow whose export is the arrangement it imports, which passes locally along with the four existing two-runtime specs.
Posted by Claude Code.
8a82082 to
99777c2
Compare
99777c2 to
ae38969
Compare
ae38969 to
de38369
Compare
de38369 to
afa26ed
Compare
`ArrangementFlavor` gains a `SharedTrace` variant carrying arrangements backed by `TraceFrontier<SharedTraceHandle>`, and `import_index_shared` constructs it on the interactive runtime instead of reading the local `TraceManager`, which holds nothing there. Because the shared handle shares the `RowRow`/`Err` batch and cursor types of a maintenance `TraceAgent`, the variant flows through the same generic bodies as `Trace`, so a downstream `Get` receives a real arrangement with its key and permutation intact rather than a collection the plan has to re-derive. Joins, delta joins, thresholds, and hydration logging gain the corresponding arm. The import is a static snapshot at `as_of`, bounded one step past it rather than by `self.until`. Interactive work is single-time, so the capability must drop once the shared trace seals past `as_of` for the one-shot result to complete, and an unbounded `until` never gets there. An `as_of` at `Timestamp::MAX` yields the empty bound, which is the right reading of "the final state". The read hold is the returned `Arranged`'s own trace rather than a separate token, so consumers can downgrade it as their frontiers advance. A token nobody downgrades would be a floor under every hold that is downgraded, because the publisher forwards the meet. The two export paths reject `SharedTrace` as unreachable. Only the interactive runtime produces the variant, and its exports are freshly rendered query outputs, never a re-export of an import. Both `import_index_shared` and the variant are reachable only when a runtime holds the `Interactive` role, which nothing constructs yet. The two `sharing` tests that build this variant return here, along with the `consolidate_capture` helper they share. `render`'s tests move out of line to `render/tests.rs`, per the convention in `src/compute/AGENTS.md`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…xports The shared import synthesised its own bound one step past `as_of` instead of passing the dataflow's `until`, and documented the result as serving single-time dataflows only. The maintenance import passes `(as_of, until)` and follows the trace when `until` is empty, and nothing about the shared import needs a different bound. The router already sends only single-time dataflows to the interactive runtime, so the two bounds were equal for every dataflow that reached the import, and the narrowing bought nothing. The variant's single-time note and the `Timestamp::MAX` special case go with it. An export whose arrangement is an imported shared arrangement was `unreachable!`. Nothing in the optimizer promises that a peek dataflow never re-exports its import under the export key, and a panic on either runtime aborts the process. Both export paths now alias the export's id to the imported publication point, the shared analogue of the `Trace` arm. The alias has no `TraceBundle`, so `report_frontiers` reads the frontier through the registry. `import_shared_index` is renamed `import_published_index` so it no longer reads as a permutation of the `import_index_shared` method that calls it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDm7opomJLxbNUEP3r9BLk
The refusal report named the controller's last compaction frontier, which the registry no longer records. The published `since` already says how far the trace compacted, so the report keeps `since`, `as_of`, and the standing hold. Tests compact through the writer's own agents where they used to note the controller's frontier on the registry, and keep the agents alive for as long as they read. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDm7opomJLxbNUEP3r9BLk
afa26ed to
511f795
Compare
Fifth of eight PRs splitting #37770. Stacks on #38389. Tracked by CPU-215.
ArrangementFlavorgains aSharedTracevariant carrying arrangements backed byTraceFrontier<SharedTraceHandle>, andimport_index_sharedconstructs it on the interactive runtime instead of reading the localTraceManager, which holds nothing there. The shared handle shares theRowRow/Errbatch and cursor types of a maintenanceTraceAgent, so the variant flows through the same generic bodies asTraceand a downstreamGetreceives a real arrangement with its key and permutation intact, rather than a collection the plan re-derives. Joins, delta joins, thresholds, and hydration logging gain the corresponding arm.The import is a static snapshot at
as_of, bounded one step past it rather than byself.until: interactive work is single-time, so the capability must drop once the shared trace seals pastas_offor the one-shot result to complete, and an unboundeduntilnever gets there.The read hold is the returned
Arranged's own trace rather than a separate token, so consumers can downgrade it as their frontiers advance. A token nobody downgrades would be a floor under every hold that is downgraded, since the publisher forwards the meet.The variant and its constructor land together because a never-constructed variant of an enum inside the private
rendermodule tripsdead_code, and CI lints with-D warnings. Reachable only when a runtime holds theInteractiverole, which nothing constructs yet.