Skip to content

Commit 00d66cc

Browse files
antiguruclaude
andcommitted
compute: import published indexes as a shared arrangement
`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>
1 parent d3a7b3d commit 00d66cc

7 files changed

Lines changed: 1627 additions & 47 deletions

File tree

‎src/compute/src/render.rs‎

Lines changed: 264 additions & 11 deletions
Large diffs are not rendered by default.

‎src/compute/src/render/context.rs‎

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ use crate::extensions::reduce::MzReduce;
5454
use crate::render::columnar::CollectionEdge;
5555
use crate::render::errors::{DataflowErrorSer, ErrorLogger};
5656
use crate::render::{LinearJoinSpec, MaybeBucketByTime, RenderTimestamp};
57+
use crate::shared_trace::{SharedErrsEnter, SharedOksEnter};
5758
use crate::typedefs::{
5859
ErrAgent, ErrBatcher, ErrBuilder, ErrEnter, ErrSpine, RowRowAgent, RowRowEnter, RowRowSpine,
5960
};
@@ -233,6 +234,23 @@ pub enum ArrangementFlavor<'scope, T: RenderTimestamp> {
233234
Arranged<'scope, RowRowEnter<mz_repr::Timestamp, Diff, T>>,
234235
Arranged<'scope, ErrEnter<mz_repr::Timestamp, T>>,
235236
),
237+
/// A maintenance-runtime arrangement imported into the interactive runtime through the
238+
/// shared-trace primitive. Backed by `SharedTraceHandle`, so it is a real arrangement the plan
239+
/// can `Get`, not a re-derived collection. Only the interactive runtime produces this.
240+
///
241+
/// The `GlobalId` mirrors [`Self::Trace`]'s: it names the imported index. The interactive
242+
/// runtime never re-exports maintained indexes, so it is never consumed by an export path.
243+
///
244+
/// NOTE: no compile-time guard prevents an `until`-carrying multi-time dataflow from importing
245+
/// this variant. Unlike [`Self::Trace`], the imported handles carry no `TraceFrontier`/`until`
246+
/// bound, so safety rests on the Interactive=peek-only invariant (peeks are single-time and
247+
/// need no `until`). A future SUBSCRIBE-on-interactive migration must add the
248+
/// `TraceFrontier`/`until` bound here first.
249+
SharedTrace(
250+
GlobalId,
251+
Arranged<'scope, SharedOksEnter<T>>,
252+
Arranged<'scope, SharedErrsEnter<T>>,
253+
),
236254
}
237255

238256
impl<'scope, T: RenderTimestamp> ArrangementFlavor<'scope, T> {
@@ -267,6 +285,10 @@ impl<'scope, T: RenderTimestamp> ArrangementFlavor<'scope, T> {
267285
oks.clone().as_collection(logic),
268286
errs.clone().as_collection(|k, &()| k.clone()),
269287
),
288+
ArrangementFlavor::SharedTrace(_, oks, errs) => (
289+
oks.clone().as_collection(logic),
290+
errs.clone().as_collection(|k, &()| k.clone()),
291+
),
270292
}
271293
}
272294

@@ -350,6 +372,18 @@ impl<'scope, T: RenderTimestamp> ArrangementFlavor<'scope, T> {
350372
let errs = errs.concat(mfp_errs.as_collection());
351373
(oks, errs)
352374
}
375+
ArrangementFlavor::SharedTrace(_, oks, errs) => {
376+
let (oks, mfp_errs) = CollectionBundle::<T>::flat_map_core_fallible::<_, _, DCB, _>(
377+
oks.clone(),
378+
key,
379+
max_demand,
380+
logic,
381+
REFUEL,
382+
);
383+
let errs = errs.clone().as_collection(|k, &()| k.clone());
384+
let errs = errs.concat(mfp_errs.as_collection());
385+
(oks, errs)
386+
}
353387
}
354388
}
355389

@@ -395,6 +429,17 @@ impl<'scope, T: RenderTimestamp> ArrangementFlavor<'scope, T> {
395429
let errs = errs.clone().as_collection(|k, &()| k.clone());
396430
(oks, errs)
397431
}
432+
ArrangementFlavor::SharedTrace(_, oks, errs) => {
433+
let oks = CollectionBundle::<T>::flat_map_core_ok::<_, _, DCB, _>(
434+
oks.clone(),
435+
key,
436+
max_demand,
437+
logic,
438+
REFUEL,
439+
);
440+
let errs = errs.clone().as_collection(|k, &()| k.clone());
441+
(oks, errs)
442+
}
398443
}
399444
}
400445
}
@@ -404,6 +449,7 @@ impl<'scope, T: RenderTimestamp> ArrangementFlavor<'scope, T> {
404449
match self {
405450
ArrangementFlavor::Local(oks, _errs) => oks.stream.scope(),
406451
ArrangementFlavor::Trace(_gid, oks, _errs) => oks.stream.scope(),
452+
ArrangementFlavor::SharedTrace(_gid, oks, _errs) => oks.stream.scope(),
407453
}
408454
}
409455

@@ -419,6 +465,11 @@ impl<'scope, T: RenderTimestamp> ArrangementFlavor<'scope, T> {
419465
oks.clone().enter_region(region),
420466
errs.clone().enter_region(region),
421467
),
468+
ArrangementFlavor::SharedTrace(gid, oks, errs) => ArrangementFlavor::SharedTrace(
469+
*gid,
470+
oks.clone().enter_region(region),
471+
errs.clone().enter_region(region),
472+
),
422473
}
423474
}
424475
}
@@ -435,6 +486,11 @@ impl<'scope, T: RenderTimestamp> ArrangementFlavor<'scope, T> {
435486
oks.clone().leave_region(outer),
436487
errs.clone().leave_region(outer),
437488
),
489+
ArrangementFlavor::SharedTrace(gid, oks, errs) => ArrangementFlavor::SharedTrace(
490+
*gid,
491+
oks.clone().leave_region(outer),
492+
errs.clone().leave_region(outer),
493+
),
438494
}
439495
}
440496
}
@@ -565,8 +621,9 @@ impl<'scope, T: RenderTimestamp> CollectionBundle<'scope, T> {
565621
/// reads is the consumer's choice, and a delta join reads both within one operator, so a
566622
/// binding's definition cannot know which form to collapse.
567623
///
568-
/// NOTE: Leaves imported arrangements (`ArrangementFlavor::Trace`) alone, whose error traces
569-
/// this dataflow cannot rewrite in place. Their errors arrive bounded by the exporting
624+
/// NOTE: Leaves imported arrangements (`ArrangementFlavor::Trace` and
625+
/// `ArrangementFlavor::SharedTrace`) alone, whose error traces this dataflow cannot rewrite in
626+
/// place. Their errors arrive bounded by the exporting
570627
/// dataflow's last level of sharing rather than collapsed to one, since nothing collapses at an
571628
/// export. A global read more than once within one dataflow is not collapsed either, because
572629
/// only local bindings reach this.
@@ -582,7 +639,9 @@ impl<'scope, T: RenderTimestamp> CollectionBundle<'scope, T> {
582639
let name = format!("Distinct errors[{key:?}]");
583640
ArrangementFlavor::Local(oks, distinct_arranged_errs(errs, &name))
584641
}
585-
flavor @ ArrangementFlavor::Trace(..) => flavor,
642+
flavor @ (ArrangementFlavor::Trace(..) | ArrangementFlavor::SharedTrace(..)) => {
643+
flavor
644+
}
586645
};
587646
self.arranged.insert(key, flavor);
588647
}

‎src/compute/src/render/join/delta_join.rs‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use timely::progress::Antichain;
4141
use crate::render::RenderTimestamp;
4242
use crate::render::context::{ArrangementFlavor, CollectionBundle, Context};
4343
use crate::render::errors::DataflowErrorSer;
44+
use crate::shared_trace::SharedOksEnter;
4445
use crate::typedefs::{RowRowAgent, RowRowEnter};
4546

4647
impl<'scope, T: RenderTimestamp> Context<'scope, T> {
@@ -319,6 +320,9 @@ fn bundle_errs<'scope, T: RenderTimestamp>(
319320
ArrangementFlavor::Trace(_id, _oks, errs) => {
320321
errs.clone().as_collection(|k, _v| k.clone())
321322
}
323+
ArrangementFlavor::SharedTrace(_id, _oks, errs) => {
324+
errs.clone().as_collection(|k, _v| k.clone())
325+
}
322326
};
323327
collected.push(errs);
324328
}
@@ -400,6 +404,32 @@ where
400404
};
401405
(oks, errs2)
402406
}
407+
// A maintenance index the interactive runtime imported from the sharing registry. Same
408+
// shape as `Trace`, only the trace type differs (`SharedOksEnter`).
409+
Some(ArrangementFlavor::SharedTrace(_, oks, errs)) => {
410+
let (oks, errs2) = if source_precedes_lookup {
411+
build_halfjoin_trace::<_, SharedOksEnter<_>, _>(
412+
updates,
413+
oks,
414+
prev_key,
415+
prev_thinning,
416+
|t1, t2| t1.le(t2),
417+
closure,
418+
config_set,
419+
)
420+
} else {
421+
build_halfjoin_trace::<_, SharedOksEnter<_>, _>(
422+
updates,
423+
oks,
424+
prev_key,
425+
prev_thinning,
426+
|t1, t2| t1.lt(t2),
427+
closure,
428+
config_set,
429+
)
430+
};
431+
(oks, errs2.concat(errs.as_collection(|k, _v| k.clone())))
432+
}
403433
None => panic!("Arrangement promised by the planner is absent!"),
404434
}
405435
}
@@ -743,6 +773,17 @@ where
743773
initial_closure,
744774
)
745775
}
776+
// A maintenance index the interactive runtime imported from the sharing registry. Same
777+
// shape as `Trace`, only the trace type differs (`SharedOksEnter`).
778+
Some(ArrangementFlavor::SharedTrace(_, oks, errs)) => {
779+
let (oks, errs2) = build_update_stream_trace::<_, SharedOksEnter<_>>(
780+
oks,
781+
as_of,
782+
source_relation,
783+
initial_closure,
784+
);
785+
(oks, errs2.concat(errs.as_collection(|k, _v| k.clone())))
786+
}
746787
None => panic!("Arrangement promised by the planner is absent!"),
747788
}
748789
}

‎src/compute/src/render/join/linear_join.rs‎

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ use crate::render::RenderTimestamp;
4141
use crate::render::context::{ArrangementFlavor, CollectionBundle, Context};
4242
use crate::render::errors::DataflowErrorSer;
4343
use crate::render::join::mz_join_core::mz_join_core;
44+
use crate::shared_trace::SharedOksEnter;
4445
use crate::typedefs::{RowRowAgent, RowRowEnter};
4546
use mz_row_spine::{RowRowBuilder, RowRowColPagedBuilder, RowRowSpine};
4647

@@ -195,6 +196,8 @@ enum JoinedFlavor<'scope, T: RenderTimestamp> {
195196
Local(Arranged<'scope, RowRowAgent<T, Diff>>),
196197
/// An imported arrangement.
197198
Trace(Arranged<'scope, RowRowEnter<mz_repr::Timestamp, Diff, T>>),
199+
/// A shared-trace arrangement imported into the interactive runtime.
200+
SharedTrace(Arranged<'scope, SharedOksEnter<T>>),
198201
}
199202

200203
impl<'scope, T> Context<'scope, T>
@@ -238,6 +241,10 @@ where
238241
errors.push(errs.as_collection(|k, _v| k.clone()).enter_region(inner));
239242
JoinedFlavor::Trace(oks.enter_region(inner))
240243
}
244+
(Some(ArrangementFlavor::SharedTrace(_gid, oks, errs)), None) => {
245+
errors.push(errs.as_collection(|k, _v| k.clone()).enter_region(inner));
246+
JoinedFlavor::SharedTrace(oks.enter_region(inner))
247+
}
241248
(_, initial_closure) => {
242249
// TODO: extract closure from the first stage in the join plan, should it exist.
243250
// TODO: apply that closure in `flat_map_ref` rather than calling `.collection`.
@@ -421,52 +428,57 @@ where
421428
.arrangement(&lookup_key[..])
422429
.expect("Arrangement absent despite explicit construction");
423430

431+
// The nine `(stream flavor) x (lookup flavor)` combinations differ only in the two trace
432+
// types handed to the generic `differential_join_inner` and the two arrangement values
433+
// consumed. This local macro spells one combination. The `SharedTrace` rows exist so an
434+
// interactive-runtime join over imported indexes type-checks. At runtime a dataflow's
435+
// arrangements are all one runtime's flavor, so the mixed rows never fire, but exhaustive
436+
// matching requires them.
437+
macro_rules! join {
438+
($stream:expr, $stream_tr:ty, $lookup:expr, $lookup_tr:ty, $errs1:expr) => {{
439+
let (oks, errs2) = self
440+
.differential_join_inner::<$stream_tr, $lookup_tr>($stream, $lookup, closure);
441+
errors.push($errs1.as_collection(|k, _v| k.clone()));
442+
errors.extend(errs2);
443+
oks
444+
}};
445+
}
446+
424447
match joined {
425448
JoinedFlavor::Collection(_) => {
426449
unreachable!("JoinedFlavor::VecCollection variant avoided at top of method");
427450
}
428451
JoinedFlavor::Local(local) => match arrangement {
429452
ArrangementFlavor::Local(oks, errs1) => {
430-
let (oks, errs2) = self
431-
.differential_join_inner::<RowRowAgent<_, _>, RowRowAgent<_, _>>(
432-
local, oks, closure,
433-
);
434-
435-
errors.push(errs1.as_collection(|k, _v| k.clone()));
436-
errors.extend(errs2);
437-
oks
453+
join!(local, RowRowAgent<_, _>, oks, RowRowAgent<_, _>, errs1)
438454
}
439455
ArrangementFlavor::Trace(_gid, oks, errs1) => {
440-
let (oks, errs2) = self
441-
.differential_join_inner::<RowRowAgent<_, _>, RowRowEnter<_, _, _>>(
442-
local, oks, closure,
443-
);
444-
445-
errors.push(errs1.as_collection(|k, _v| k.clone()));
446-
errors.extend(errs2);
447-
oks
456+
join!(local, RowRowAgent<_, _>, oks, RowRowEnter<_, _, _>, errs1)
457+
}
458+
ArrangementFlavor::SharedTrace(_gid, oks, errs1) => {
459+
join!(local, RowRowAgent<_, _>, oks, SharedOksEnter<_>, errs1)
448460
}
449461
},
450462
JoinedFlavor::Trace(trace) => match arrangement {
451463
ArrangementFlavor::Local(oks, errs1) => {
452-
let (oks, errs2) = self
453-
.differential_join_inner::<RowRowEnter<_, _, _>, RowRowAgent<_, _>>(
454-
trace, oks, closure,
455-
);
456-
457-
errors.push(errs1.as_collection(|k, _v| k.clone()));
458-
errors.extend(errs2);
459-
oks
464+
join!(trace, RowRowEnter<_, _, _>, oks, RowRowAgent<_, _>, errs1)
465+
}
466+
ArrangementFlavor::Trace(_gid, oks, errs1) => {
467+
join!(trace, RowRowEnter<_, _, _>, oks, RowRowEnter<_, _, _>, errs1)
468+
}
469+
ArrangementFlavor::SharedTrace(_gid, oks, errs1) => {
470+
join!(trace, RowRowEnter<_, _, _>, oks, SharedOksEnter<_>, errs1)
471+
}
472+
},
473+
JoinedFlavor::SharedTrace(trace) => match arrangement {
474+
ArrangementFlavor::Local(oks, errs1) => {
475+
join!(trace, SharedOksEnter<_>, oks, RowRowAgent<_, _>, errs1)
460476
}
461477
ArrangementFlavor::Trace(_gid, oks, errs1) => {
462-
let (oks, errs2) = self
463-
.differential_join_inner::<RowRowEnter<_, _, _>, RowRowEnter<_, _, _>>(
464-
trace, oks, closure,
465-
);
466-
467-
errors.push(errs1.as_collection(|k, _v| k.clone()));
468-
errors.extend(errs2);
469-
oks
478+
join!(trace, SharedOksEnter<_>, oks, RowRowEnter<_, _, _>, errs1)
479+
}
480+
ArrangementFlavor::SharedTrace(_gid, oks, errs1) => {
481+
join!(trace, SharedOksEnter<_>, oks, SharedOksEnter<_>, errs1)
470482
}
471483
},
472484
}

0 commit comments

Comments
 (0)