mz_compute/render.rs
1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Renders a plan into a timely/differential dataflow computation.
11//!
12//! ## Error handling
13//!
14//! Timely and differential have no idioms for computations that can error. The
15//! philosophy is, reasonably, to define the semantics of the computation such
16//! that errors are unnecessary: e.g., by using wrap-around semantics for
17//! integer overflow.
18//!
19//! Unfortunately, SQL semantics are not nearly so elegant, and require errors
20//! in myriad cases. The classic example is a division by zero, but invalid
21//! input for casts, overflowing integer operations, and dozens of other
22//! functions need the ability to produce errors ar runtime.
23//!
24//! At the moment, only *scalar* expression evaluation can fail, so only
25//! operators that evaluate scalar expressions can fail. At the time of writing,
26//! that includes map, filter, reduce, and join operators. Constants are a bit
27//! of a special case: they can be either a constant vector of rows *or* a
28//! constant, singular error.
29//!
30//! The approach taken is to build two parallel trees of computation: one for
31//! the rows that have been successfully evaluated (the "oks tree"), and one for
32//! the errors that have been generated (the "errs tree"). For example:
33//!
34//! ```text
35//! oks1 errs1 oks2 errs2
36//! | | | |
37//! | | | |
38//! project | | |
39//! | | | |
40//! | | | |
41//! map | | |
42//! |\ | | |
43//! | \ | | |
44//! | \ | | |
45//! | \ | | |
46//! | \| | |
47//! project + + +
48//! | | / /
49//! | | / /
50//! join ------------+ /
51//! | | /
52//! | | +----------+
53//! | |/
54//! oks errs
55//! ```
56//!
57//! The project operation cannot fail, so errors from errs1 are propagated
58//! directly. Map operators are fallible and so can inject additional errors
59//! into the stream. Join operators combine the errors from each of their
60//! inputs.
61//!
62//! The semantics of the error stream are minimal. From the perspective of SQL,
63//! a dataflow is considered to be in an error state if there is at least one
64//! element in the final errs collection. The error value returned to the user
65//! is selected arbitrarily; SQL only makes provisions to return one error to
66//! the user at a time. There are plans to make the err collection accessible to
67//! end users, so they can see all errors at once.
68//!
69//! To make errors transient, simply ensure that the operator can retract any
70//! produced errors when corrected data arrives. To make errors permanent, write
71//! the operator such that it never retracts the errors it produced. Future work
72//! will likely want to introduce some sort of sort order for errors, so that
73//! permanent errors are returned to the user ahead of transient errors—probably
74//! by introducing a new error type a la:
75//!
76//! ```no_run
77//! # struct EvalError;
78//! # struct SourceError;
79//! enum DataflowError {
80//! Transient(EvalError),
81//! Permanent(SourceError),
82//! }
83//! ```
84//!
85//! If the error stream is empty, the oks stream must be correct. If the error
86//! stream is non-empty, then there are no semantics for the oks stream. This is
87//! sufficient to support SQL in its current form, but is likely to be
88//! unsatisfactory long term. We suspect that we can continue to imbue the oks
89//! stream with semantics if we are very careful in describing what data should
90//! and should not be produced upon encountering an error. Roughly speaking, the
91//! oks stream could represent the correct result of the computation where all
92//! rows that caused an error have been pruned from the stream. There are
93//! strange and confusing questions here around foreign keys, though: what if
94//! the optimizer proves that a particular key must exist in a collection, but
95//! the key gets pruned away because its row participated in a scalar expression
96//! evaluation that errored?
97//!
98//! In the meantime, it is probably wise for operators to keep the oks stream
99//! roughly "as correct as possible" even when errors are present in the errs
100//! stream. This reduces the amount of recomputation that must be performed
101//! if/when the errors are retracted.
102
103use std::any::Any;
104use std::cell::RefCell;
105use std::collections::{BTreeMap, BTreeSet};
106use std::convert::Infallible;
107use std::future::Future;
108use std::pin::Pin;
109use std::rc::{Rc, Weak};
110use std::sync::Arc;
111use std::task::Poll;
112
113use ::columnar::{Columnar as ColumnarData, Index as ColumnarIndex, Push as ColumnarPush};
114use differential_dataflow::dynamic::pointstamp::PointStamp;
115use differential_dataflow::lattice::Lattice;
116use differential_dataflow::operators::arrange::Arranged;
117use differential_dataflow::operators::arrange::ShutdownButton;
118use differential_dataflow::operators::iterate::Variable;
119use differential_dataflow::trace::cursor::{BatchCursor, BatchDiff, BatchKey, BatchVal};
120use differential_dataflow::trace::{Cursor, Navigable, TraceReader};
121use differential_dataflow::{AsCollection, Collection, Data, VecCollection};
122use futures::FutureExt;
123use futures::channel::oneshot;
124use itertools::Itertools;
125use mz_compute_types::dataflows::{IndexDesc, RenderDataflowDescription};
126use mz_compute_types::dyncfgs::{
127 COMPUTE_APPLY_COLUMN_DEMANDS, COMPUTE_LOGICAL_BACKPRESSURE_INFLIGHT_SLACK,
128 COMPUTE_LOGICAL_BACKPRESSURE_MAX_RETAINED_CAPABILITIES, ENABLE_COMPUTE_LOGICAL_BACKPRESSURE,
129 ENABLE_ERROR_DISTINCT, SUBSCRIBE_SNAPSHOT_OPTIMIZATION, TEMPORAL_BUCKETING_SUMMARY,
130};
131use mz_compute_types::plan::render_plan::{
132 self, BindStage, LetBind, LetFreePlan, RecBind, RenderPlan,
133};
134use mz_compute_types::plan::scalar::LirScalarExpr;
135use mz_compute_types::plan::{ArrangementStrategy, LirId};
136use mz_expr::{EvalError, Id, LocalId, permutation_for_arrangement};
137use mz_persist_client::operators::shard_source::{ErrorHandler, SnapshotMode};
138use mz_repr::explain::DummyHumanizer;
139use mz_repr::fixed_length::ExtendDatums;
140use mz_repr::{Datum, DatumVec, Diff, GlobalId, ReprRelationType, Row, RowArena, SharedRow};
141use mz_row_spine::{DatumSeq, RowRowBatcher};
142use mz_storage_operators::persist_source;
143use mz_storage_types::controller::CollectionMetadata;
144use mz_timely_util::columnar::Column;
145use mz_timely_util::columnar::builder::ColumnBuilder;
146use mz_timely_util::operator::StreamExt;
147use mz_timely_util::probe::{Handle as MzProbeHandle, ProbeNotify};
148use mz_timely_util::scope_label::ScopeExt;
149use timely::PartialOrder;
150use timely::container::CapacityContainerBuilder;
151use timely::dataflow::channels::pact::Pipeline;
152use timely::dataflow::operators::core::to_stream::ToStreamBuilder;
153use timely::dataflow::operators::vec::Filter;
154use timely::dataflow::operators::vec::ToStream;
155use timely::dataflow::operators::{CapabilitySet, Operator, Probe, probe};
156use timely::dataflow::{Scope, Stream, StreamVec};
157use timely::order::{Product, TotalOrder};
158use timely::progress::timestamp::Refines;
159use timely::progress::{Antichain, Timestamp};
160use timely::scheduling::ActivateOnDrop;
161use timely::worker::Worker as TimelyWorker;
162
163use crate::arrangement::manager::TraceBundle;
164use crate::compute_state::ComputeState;
165use crate::extensions::arrange::{KeyCollection, MzArrange};
166use crate::extensions::reduce::MzReduce;
167use crate::extensions::temporal_bucket::TemporalBucketing;
168use crate::logging::compute::{
169 ComputeEvent, DataflowGlobal, LirMapping, LirMetadata, LogDataflowErrors, OperatorHydration,
170};
171use crate::render::columnar::{
172 ColCollection, RecTimestamp, columnar_consolidate, columnar_leave_dynamic, columnar_negate,
173 concat_many, flat_map_datums,
174};
175use crate::render::context::{ArrangementFlavor, Context};
176use crate::render::errors::DataflowErrorSer;
177use crate::typedefs::{ErrBatcher, ErrBuilder, ErrSpine, MzTimestamp};
178use mz_timely_util::columnar::consolidate::ConsolidatingColumnBuilder;
179
180pub(crate) mod columnar;
181pub mod context;
182pub(crate) mod errors;
183mod flat_map;
184mod join;
185mod reduce;
186pub mod sinks;
187mod threshold;
188mod top_k;
189
190pub use context::CollectionBundle;
191pub use join::LinearJoinSpec;
192
193/// Guard that presses a differential [`ShutdownButton`] when dropped.
194///
195/// Dropping this guard releases the imported trace's capabilities.
196struct PressOnDrop<T>(ShutdownButton<T>);
197
198impl<T> Drop for PressOnDrop<T> {
199 fn drop(&mut self) {
200 self.0.press();
201 }
202}
203
204/// Assemble the "compute" side of a dataflow, i.e. all but the sources.
205///
206/// This method imports sources from provided assets, and then builds the remaining
207/// dataflow using "compute-local" assets like shared arrangements, and producing
208/// both arrangements and sinks.
209pub fn build_compute_dataflow(
210 timely_worker: &mut TimelyWorker,
211 compute_state: &mut ComputeState,
212 dataflow: RenderDataflowDescription<CollectionMetadata>,
213 start_signal: StartSignal,
214 until: Antichain<mz_repr::Timestamp>,
215 dataflow_expiration: Antichain<mz_repr::Timestamp>,
216) {
217 // Mutually recursive view definitions require special handling.
218 let recursive = dataflow
219 .objects_to_build
220 .iter()
221 .any(|object| object.plan.is_recursive());
222
223 // Determine indexes to export, and their dependencies.
224 let indexes = dataflow
225 .index_exports
226 .iter()
227 .map(|(idx_id, (idx, _typ))| (*idx_id, dataflow.depends_on(idx.on_id), idx.as_lir()))
228 .collect::<Vec<_>>();
229
230 // Determine sinks to export, and their dependencies.
231 let sinks = dataflow
232 .sink_exports
233 .iter()
234 .map(|(sink_id, sink)| (*sink_id, dataflow.depends_on(sink.from), sink.clone()))
235 .collect::<Vec<_>>();
236
237 let worker_logging = timely_worker.logger_for("timely").map(Into::into);
238 let apply_demands = COMPUTE_APPLY_COLUMN_DEMANDS.get(&compute_state.worker_config);
239 let subscribe_snapshot_optimization =
240 SUBSCRIBE_SNAPSHOT_OPTIMIZATION.get(&compute_state.worker_config);
241
242 let name = format!("Dataflow: {}", dataflow.debug_name);
243 let input_name = format!("InputRegion: {}", dataflow.debug_name);
244 let build_name = format!("BuildRegion: {}", dataflow.debug_name);
245
246 timely_worker.dataflow_core(&name, worker_logging, Box::new(()), |_, scope| {
247 let scope = scope.with_label();
248
249 // The scope.clone() occurs to allow import in the region.
250 // We build a region here to establish a pattern of a scope inside the dataflow,
251 // so that other similar uses (e.g. with iterative scopes) do not require weird
252 // alternate type signatures.
253 let mut imported_sources = Vec::new();
254 let mut tokens: BTreeMap<_, Rc<dyn Any>> = BTreeMap::new();
255 let output_probe = MzProbeHandle::default();
256
257 scope.clone().region_named(&input_name, |region| {
258 // Import declared sources into the rendering context.
259 for (source_id, import) in dataflow.source_imports.iter() {
260 region.region_named(&format!("Source({:?})", source_id), |inner| {
261 let mut read_schema = None;
262 let mut mfp = import.desc.arguments.operators.clone().map(|mut ops| {
263 // If enabled, we read from Persist with a `RelationDesc` that
264 // omits uneeded columns.
265 if apply_demands {
266 let demands = ops.demand();
267 let new_desc = import
268 .desc
269 .storage_metadata
270 .relation_desc
271 .apply_demand(&demands);
272 let new_arity = demands.len();
273 let remap: BTreeMap<_, _> = demands
274 .into_iter()
275 .enumerate()
276 .map(|(new, old)| (old, new))
277 .collect();
278 ops.permute_fn(|old_idx| remap[&old_idx], new_arity);
279 read_schema = Some(new_desc);
280 }
281
282 ops
283 });
284
285 let snapshot_mode = if import.with_snapshot || !subscribe_snapshot_optimization
286 {
287 SnapshotMode::Include
288 } else {
289 compute_state.metrics.inc_subscribe_snapshot_optimization();
290 SnapshotMode::Exclude
291 };
292 let suppress_early_progress_as_of = dataflow.as_of.clone();
293
294 // Note: For correctness, we require that sources only emit times advanced by
295 // `dataflow.as_of`. `persist_source` is documented to provide this guarantee.
296 let (mut ok_stream, err_stream, token) = persist_source::persist_source::<
297 DataflowErrorSer,
298 ConsolidatingColumnBuilder<Row, mz_repr::Timestamp, Diff>,
299 _,
300 >(
301 inner,
302 *source_id,
303 Arc::clone(&compute_state.persist_clients),
304 &compute_state.txns_ctx,
305 import.desc.storage_metadata.clone(),
306 read_schema,
307 dataflow.as_of.clone(),
308 snapshot_mode,
309 until.clone(),
310 mfp.as_mut(),
311 compute_state.dataflow_max_inflight_bytes(),
312 start_signal.clone().into_send_future(),
313 ErrorHandler::Halt("compute_import"),
314 );
315
316 // If `mfp` is non-identity, we need to apply what remains.
317 // For the moment, assert that it is either trivial or `None`.
318 assert!(mfp.map(|x| x.is_identity()).unwrap_or(true));
319
320 // To avoid a memory spike during arrangement hydration (database-issues#6368), need to
321 // ensure that the first frontier we report into the dataflow is beyond the
322 // `as_of`.
323 if let Some(as_of) = suppress_early_progress_as_of {
324 ok_stream = suppress_early_progress(ok_stream, as_of);
325 }
326
327 if ENABLE_COMPUTE_LOGICAL_BACKPRESSURE.get(&compute_state.worker_config) {
328 // Apply logical backpressure to the source.
329 let limit = COMPUTE_LOGICAL_BACKPRESSURE_MAX_RETAINED_CAPABILITIES
330 .get(&compute_state.worker_config);
331 let slack = COMPUTE_LOGICAL_BACKPRESSURE_INFLIGHT_SLACK
332 .get(&compute_state.worker_config)
333 .as_millis()
334 .try_into()
335 .expect("must fit");
336
337 let stream = ok_stream.limit_progress(
338 output_probe.clone(),
339 slack,
340 limit,
341 import.upper.clone(),
342 name.clone(),
343 );
344 ok_stream = stream;
345 }
346
347 // Attach a probe reporting the input frontier.
348 let input_probe =
349 compute_state.input_probe_for(*source_id, dataflow.export_ids());
350 ok_stream = ok_stream.probe_with(&input_probe);
351
352 let (oks, errs) = (
353 ok_stream
354 .as_collection()
355 .leave_region(region)
356 .leave_region(scope),
357 err_stream
358 .as_collection()
359 .leave_region(region)
360 .leave_region(scope),
361 );
362
363 imported_sources.push((mz_expr::Id::Global(*source_id), (oks, errs)));
364
365 // Associate returned tokens with the source identifier.
366 tokens.insert(*source_id, Rc::new(token));
367 });
368 }
369 });
370
371 // If there exists a recursive expression, we'll need to use a non-region scope,
372 // in order to support additional timestamp coordinates for iteration.
373 if recursive {
374 scope.clone().iterative::<PointStamp<u64>, _, _>(|region| {
375 let mut context = Context::for_dataflow_in(
376 &dataflow,
377 region.clone(),
378 compute_state,
379 until,
380 dataflow_expiration,
381 );
382
383 for (id, (oks, errs)) in imported_sources.into_iter() {
384 let bundle = crate::render::CollectionBundle::from_edge(
385 oks.enter(region),
386 errs.enter(region),
387 );
388 // Associate collection bundle with the source identifier.
389 context.insert_id(id, bundle);
390 }
391
392 // Import declared indexes into the rendering context.
393 for (idx_id, idx) in &dataflow.index_imports {
394 let input_probe = compute_state.input_probe_for(*idx_id, dataflow.export_ids());
395 let snapshot_mode = if idx.with_snapshot || !subscribe_snapshot_optimization {
396 SnapshotMode::Include
397 } else {
398 compute_state.metrics.inc_subscribe_snapshot_optimization();
399 SnapshotMode::Exclude
400 };
401 context.import_index(
402 scope,
403 compute_state,
404 &mut tokens,
405 input_probe,
406 *idx_id,
407 &idx.desc.as_lir(),
408 &idx.typ,
409 snapshot_mode,
410 start_signal.clone(),
411 );
412 }
413
414 // Build declared objects.
415 for object in dataflow.objects_to_build {
416 let bundle = context.scope.clone().region_named(
417 &format!("BuildingObject({:?})", object.id),
418 |region| {
419 let depends = object.plan.depends();
420 let in_let = object.plan.is_recursive();
421 context
422 .enter_region(region, Some(&depends))
423 .render_recursive_plan(
424 object.id,
425 0,
426 object.plan,
427 // recursive plans _must_ have bodies in a let
428 BindingInfo::Body { in_let },
429 )
430 .leave_region(context.scope)
431 },
432 );
433 let global_id = object.id;
434
435 context.log_dataflow_global_id(
436 *bundle
437 .scope()
438 .addr()
439 .first()
440 .expect("Dataflow root id must exist"),
441 global_id,
442 );
443 context.insert_id(Id::Global(object.id), bundle);
444 }
445
446 // Export declared indexes.
447 for (idx_id, dependencies, idx) in indexes {
448 context.export_index_iterative(
449 scope,
450 compute_state,
451 &tokens,
452 dependencies,
453 idx_id,
454 &idx,
455 &output_probe,
456 );
457 }
458
459 // Export declared sinks.
460 for (sink_id, dependencies, sink) in sinks {
461 context.export_sink(
462 compute_state,
463 &tokens,
464 dependencies,
465 sink_id,
466 &sink,
467 start_signal.clone(),
468 &output_probe,
469 scope,
470 );
471 }
472 });
473 } else {
474 scope.clone().region_named(&build_name, |region| {
475 let mut context = Context::for_dataflow_in(
476 &dataflow,
477 region.clone(),
478 compute_state,
479 until,
480 dataflow_expiration,
481 );
482
483 for (id, (oks, errs)) in imported_sources.into_iter() {
484 let bundle = crate::render::CollectionBundle::from_edge(
485 oks.enter_region(region),
486 errs.enter_region(region),
487 );
488 // Associate collection bundle with the source identifier.
489 context.insert_id(id, bundle);
490 }
491
492 // Import declared indexes into the rendering context.
493 for (idx_id, idx) in &dataflow.index_imports {
494 let input_probe = compute_state.input_probe_for(*idx_id, dataflow.export_ids());
495 let snapshot_mode = if idx.with_snapshot || !subscribe_snapshot_optimization {
496 SnapshotMode::Include
497 } else {
498 compute_state.metrics.inc_subscribe_snapshot_optimization();
499 SnapshotMode::Exclude
500 };
501 context.import_index(
502 scope,
503 compute_state,
504 &mut tokens,
505 input_probe,
506 *idx_id,
507 &idx.desc.as_lir(),
508 &idx.typ,
509 snapshot_mode,
510 start_signal.clone(),
511 );
512 }
513
514 // Build declared objects.
515 for object in dataflow.objects_to_build {
516 let bundle = context.scope.clone().region_named(
517 &format!("BuildingObject({:?})", object.id),
518 |region| {
519 let depends = object.plan.depends();
520 context
521 .enter_region(region, Some(&depends))
522 .render_plan(object.id, object.plan)
523 .leave_region(context.scope)
524 },
525 );
526 let global_id = object.id;
527 context.log_dataflow_global_id(
528 *bundle
529 .scope()
530 .addr()
531 .first()
532 .expect("Dataflow root id must exist"),
533 global_id,
534 );
535 context.insert_id(Id::Global(object.id), bundle);
536 }
537
538 // Export declared indexes.
539 for (idx_id, dependencies, idx) in indexes {
540 context.export_index(
541 compute_state,
542 &tokens,
543 dependencies,
544 idx_id,
545 &idx,
546 &output_probe,
547 );
548 }
549
550 // Export declared sinks.
551 for (sink_id, dependencies, sink) in sinks {
552 context.export_sink(
553 compute_state,
554 &tokens,
555 dependencies,
556 sink_id,
557 &sink,
558 start_signal.clone(),
559 &output_probe,
560 scope,
561 );
562 }
563 });
564 }
565 });
566}
567
568// This implementation block allows child timestamps to vary from parent timestamps,
569// but requires the parent timestamp to be `repr::Timestamp`.
570impl<'g, T> Context<'g, T>
571where
572 T: Refines<mz_repr::Timestamp> + RenderTimestamp,
573{
574 /// Import the collection from the arrangement, discarding batches from the snapshot.
575 /// (This does not guarantee that no records from the snapshot are included; the assumption is
576 /// that we'll filter those out later if necessary.)
577 fn import_filtered_index_collection<
578 'outer,
579 Tr: TraceReader<Time = mz_repr::Timestamp, Batch: Navigable> + Clone,
580 V: Data,
581 >(
582 &self,
583 arranged: Arranged<'outer, Tr>,
584 start_signal: StartSignal,
585 mut logic: impl FnMut(BatchKey<'_, Tr>, BatchVal<'_, Tr>) -> V + 'static,
586 ) -> VecCollection<'g, T, V, BatchDiff<Tr>>
587 where
588 // This is implied by the fact that the outer timestamp = mz_repr::Timestamp, but it's essential
589 // for our batch-level filtering to be safe, so we document it here regardless.
590 mz_repr::Timestamp: TotalOrder,
591 BatchCursor<Tr>: Cursor<Time = mz_repr::Timestamp>,
592 {
593 let oks = arranged.stream.with_start_signal(start_signal).filter({
594 let as_of = self.as_of_frontier.clone();
595 move |b| !<Antichain<mz_repr::Timestamp> as PartialOrder>::less_equal(b.upper(), &as_of)
596 });
597 Arranged::<'outer, Tr>::flat_map_batches(oks, move |a, b| [logic(a, b)]).enter(self.scope)
598 }
599
600 /// Extracts a filtered index's contents onto the columnar collection edge, discarding
601 /// batches that the snapshot covers.
602 ///
603 /// `logic` packs each `(key, val)` pair into the row buffer it is handed. The buffer is
604 /// reused across records and pushed borrowed, so a pair that carries many times costs one
605 /// pack rather than an owned [`Row`] per time.
606 ///
607 /// The `TotalOrder` bound is what makes discarding a batch by its upper safe.
608 fn import_filtered_index_edge<'outer, Tr>(
609 &self,
610 arranged: Arranged<'outer, Tr>,
611 start_signal: StartSignal,
612 mut logic: impl FnMut(BatchKey<'_, Tr>, BatchVal<'_, Tr>, &mut Row) + 'static,
613 ) -> ColCollection<'g, T>
614 where
615 Tr: TraceReader<Time = mz_repr::Timestamp, Batch: Navigable> + Clone,
616 mz_repr::Timestamp: TotalOrder,
617 BatchCursor<Tr>: Cursor<Time = mz_repr::Timestamp, Diff = Diff>,
618 {
619 let as_of = self.as_of_frontier.clone();
620 arranged
621 .stream
622 .with_start_signal(start_signal)
623 .unary::<ColumnBuilder<(Row, mz_repr::Timestamp, Diff)>, _, _, _>(
624 Pipeline,
625 "IndexToColumnar",
626 move |_cap, _info| {
627 let mut row_buf = Row::default();
628 move |input, output| {
629 input.for_each(|time, data| {
630 let mut session = output.session_with_builder(&time);
631 for span in data.iter() {
632 if <Antichain<mz_repr::Timestamp> as PartialOrder>::less_equal(
633 span.upper(),
634 &as_of,
635 ) {
636 continue;
637 }
638 let Some(batch) = span.inner.as_ref() else {
639 continue;
640 };
641 let mut cursor = batch.cursor();
642 while let Some(key) = cursor.get_key(batch) {
643 while let Some(val) = cursor.get_val(batch) {
644 logic(key, val, &mut row_buf);
645 cursor.map_times(batch, |t, d| {
646 let t = <BatchCursor<Tr> as Cursor>::owned_time(t);
647 let d = <BatchCursor<Tr> as Cursor>::owned_diff(d);
648 session.give((&*row_buf, &t, &d));
649 });
650 cursor.step_val(batch);
651 }
652 cursor.step_key(batch);
653 }
654 }
655 });
656 }
657 },
658 )
659 .as_collection()
660 .enter(self.scope)
661 }
662
663 pub(crate) fn import_index<'outer>(
664 &mut self,
665 outer: Scope<'outer, mz_repr::Timestamp>,
666 compute_state: &mut ComputeState,
667 tokens: &mut BTreeMap<GlobalId, Rc<dyn std::any::Any>>,
668 input_probe: probe::Handle<mz_repr::Timestamp>,
669 idx_id: GlobalId,
670 idx: &IndexDesc<LirScalarExpr>,
671 typ: &ReprRelationType,
672 snapshot_mode: SnapshotMode,
673 start_signal: StartSignal,
674 ) {
675 if let Some(traces) = compute_state.traces.get_mut(&idx_id) {
676 assert!(
677 PartialOrder::less_equal(&traces.compaction_frontier(), &self.as_of_frontier),
678 "Index {idx_id} has been allowed to compact beyond the dataflow as_of"
679 );
680
681 let token = traces.to_drop().clone();
682
683 let (mut oks, ok_button) = traces.oks_mut().import_frontier_core(
684 outer,
685 &format!("Index({}, {:?})", idx.on_id, idx.key),
686 self.as_of_frontier.clone(),
687 self.until.clone(),
688 );
689
690 oks.stream = oks.stream.probe_with(&input_probe);
691
692 let (err_arranged, err_button) = traces.errs_mut().import_frontier_core(
693 outer,
694 &format!("ErrIndex({}, {:?})", idx.on_id, idx.key),
695 self.as_of_frontier.clone(),
696 self.until.clone(),
697 );
698
699 let bundle = match snapshot_mode {
700 SnapshotMode::Include => {
701 let ok_arranged = oks
702 .enter(self.scope)
703 .with_start_signal(start_signal.clone());
704 let err_arranged = err_arranged
705 .enter(self.scope)
706 .with_start_signal(start_signal);
707 CollectionBundle::from_expressions(
708 idx.key.clone(),
709 ArrangementFlavor::Trace(idx_id, ok_arranged, err_arranged),
710 )
711 }
712 SnapshotMode::Exclude => {
713 // When we import an index without a snapshot, we have two balancing considerations:
714 // - It's easy to filter out irrelevant batches from the stream, but hard to filter them out from an arrangement.
715 // (The `TraceFrontier` wrapper allows us to set an "until" frontier, but not a lower.)
716 // - We do not actually need to reference the arrangement in this dataflow, since all operators that use the arrangement
717 // (joins, reduces, etc.) also require the snapshot data.
718 // So: when the snapshot is excluded, we import only the (filtered) collection itself and ignore the arrangement.
719 let oks = {
720 let mut datums = DatumVec::new();
721 let (permutation, _thinning) =
722 permutation_for_arrangement(&idx.key, typ.arity());
723 self.import_filtered_index_edge(
724 oks,
725 start_signal.clone(),
726 move |k: DatumSeq, v: DatumSeq, row: &mut Row| {
727 let temp_storage = RowArena::new();
728 let mut datums_borrow = datums.borrow();
729 k.extend_datums(&temp_storage, &mut datums_borrow, None);
730 v.extend_datums(&temp_storage, &mut datums_borrow, None);
731 row.packer()
732 .extend(permutation.iter().map(|i| datums_borrow[*i]));
733 },
734 )
735 };
736 let errs = self.import_filtered_index_collection(
737 err_arranged,
738 start_signal,
739 |e, _| e.clone(),
740 );
741 CollectionBundle::from_edge(oks, errs)
742 }
743 };
744 self.update_id(Id::Global(idx.on_id), bundle);
745 tokens.insert(
746 idx_id,
747 Rc::new((PressOnDrop(ok_button), PressOnDrop(err_button), token)),
748 );
749 } else {
750 panic!(
751 "import of index {} failed while building dataflow {}",
752 idx_id, self.dataflow_id
753 );
754 }
755 }
756}
757
758// This implementation block requires the scopes have the same timestamp as the trace manager.
759// That makes some sense, because we are hoping to deposit an arrangement in the trace manager.
760impl<'g> Context<'g, mz_repr::Timestamp> {
761 pub(crate) fn export_index(
762 &self,
763 compute_state: &mut ComputeState,
764 tokens: &BTreeMap<GlobalId, Rc<dyn std::any::Any>>,
765 dependency_ids: BTreeSet<GlobalId>,
766 idx_id: GlobalId,
767 idx: &IndexDesc<LirScalarExpr>,
768 output_probe: &MzProbeHandle<mz_repr::Timestamp>,
769 ) {
770 // put together tokens that belong to the export
771 let mut needed_tokens = Vec::new();
772 for dep_id in dependency_ids {
773 if let Some(token) = tokens.get(&dep_id) {
774 needed_tokens.push(Rc::clone(token));
775 }
776 }
777 let bundle = self.lookup_id(Id::Global(idx_id)).unwrap_or_else(|| {
778 panic!(
779 "Arrangement alarmingly absent! id: {:?}",
780 Id::Global(idx_id)
781 )
782 });
783
784 let key = &idx.key;
785 match bundle.arrangement(key) {
786 Some(ArrangementFlavor::Local(mut oks, mut errs)) => {
787 // NOTE: Do not give an exported arrangement a second reader that holds a trace
788 // handle, such as a `reduce`. Such a reader pins the shared spine's physical
789 // frontier at its own lagging progress, and `ArrangementManager::maintenance` can
790 // then no longer advance it, so batches pile up in `Spine::pending`. A cursor is
791 // only checked for straddling over pending batches, so an importing dataflow's
792 // `cursor_through` eventually panics with `upper` straddles batch. Watching
793 // `errs.stream` in `output_probe` does not help, and neither does discarding the
794 // reader's output. Stream-level readers like `as_collection` are unaffected. This is
795 // why error multiplicity is not collapsed here, leaving multiplicity that crosses
796 // an index boundary unbounded.
797
798 // Ensure that the frontier does not advance past the expiration time, if set.
799 // Otherwise, we might write down incorrect data.
800 if let Some(&expiration) = self.dataflow_expiration.as_option() {
801 oks.stream = oks.stream.expire_stream_at(
802 &format!("{}_export_index_oks", self.debug_name),
803 expiration,
804 );
805 errs.stream = errs.stream.expire_stream_at(
806 &format!("{}_export_index_errs", self.debug_name),
807 expiration,
808 );
809 }
810
811 oks.stream = oks.stream.probe_notify_with(vec![output_probe.clone()]);
812
813 // Attach logging of dataflow errors.
814 if let Some(logger) = compute_state.compute_logger.clone() {
815 errs.stream = errs.stream.log_dataflow_errors(logger, idx_id);
816 }
817
818 compute_state.traces.set(
819 idx_id,
820 TraceBundle::new(oks.trace, errs.trace).with_drop(needed_tokens),
821 );
822 }
823 Some(ArrangementFlavor::Trace(gid, _, _)) => {
824 // Duplicate of existing arrangement with id `gid`, so
825 // just create another handle to that arrangement.
826 let trace = compute_state.traces.get(&gid).unwrap().clone();
827 compute_state.traces.set(idx_id, trace);
828 }
829 None => {
830 println!("collection available: {:?}", bundle.collection.is_none());
831 println!(
832 "keys available: {:?}",
833 bundle.arranged.keys().collect::<Vec<_>>()
834 );
835 panic!(
836 "Arrangement alarmingly absent! id: {:?}, keys: {:?}",
837 Id::Global(idx_id),
838 key
839 );
840 }
841 };
842 }
843}
844
845// This implementation block requires the scopes have the same timestamp as the trace manager.
846// That makes some sense, because we are hoping to deposit an arrangement in the trace manager.
847impl<'g, T> Context<'g, T>
848where
849 T: RenderTimestamp,
850{
851 pub(crate) fn export_index_iterative<'outer>(
852 &self,
853 outer: Scope<'outer, mz_repr::Timestamp>,
854 compute_state: &mut ComputeState,
855 tokens: &BTreeMap<GlobalId, Rc<dyn std::any::Any>>,
856 dependency_ids: BTreeSet<GlobalId>,
857 idx_id: GlobalId,
858 idx: &IndexDesc<LirScalarExpr>,
859 output_probe: &MzProbeHandle<mz_repr::Timestamp>,
860 ) {
861 // put together tokens that belong to the export
862 let mut needed_tokens = Vec::new();
863 for dep_id in dependency_ids {
864 if let Some(token) = tokens.get(&dep_id) {
865 needed_tokens.push(Rc::clone(token));
866 }
867 }
868 let bundle = self.lookup_id(Id::Global(idx_id)).unwrap_or_else(|| {
869 panic!(
870 "Arrangement alarmingly absent! id: {:?}",
871 Id::Global(idx_id)
872 )
873 });
874
875 let key = &idx.key;
876 match bundle.arrangement(key) {
877 Some(ArrangementFlavor::Local(oks, errs)) => {
878 // TODO: The following as_collection/leave/arrange sequence could be optimized.
879 // * Combine as_collection and leave into a single function.
880 // * Use columnar to extract columns from the batches to implement leave.
881 let mut oks = oks
882 .as_collection(|k, v| (k.to_row(), v.to_row()))
883 .leave(outer)
884 .mz_arrange::<RowRowBatcher<_, _>, _>("Arrange export iterative");
885
886 let mut errs = errs
887 .as_collection(|k, v| (k.clone(), v.clone()))
888 .leave(outer)
889 .mz_arrange::<ErrBatcher<_, _>, _>("Arrange export iterative err");
890
891 // Ensure that the frontier does not advance past the expiration time, if set.
892 // Otherwise, we might write down incorrect data.
893 if let Some(&expiration) = self.dataflow_expiration.as_option() {
894 oks.stream = oks.stream.expire_stream_at(
895 &format!("{}_export_index_iterative_oks", self.debug_name),
896 expiration,
897 );
898 errs.stream = errs.stream.expire_stream_at(
899 &format!("{}_export_index_iterative_err", self.debug_name),
900 expiration,
901 );
902 }
903
904 oks.stream = oks.stream.probe_notify_with(vec![output_probe.clone()]);
905
906 // Attach logging of dataflow errors.
907 if let Some(logger) = compute_state.compute_logger.clone() {
908 errs.stream = errs.stream.log_dataflow_errors(logger, idx_id);
909 }
910
911 compute_state.traces.set(
912 idx_id,
913 TraceBundle::new(oks.trace, errs.trace).with_drop(needed_tokens),
914 );
915 }
916 Some(ArrangementFlavor::Trace(gid, _, _)) => {
917 // Duplicate of existing arrangement with id `gid`, so
918 // just create another handle to that arrangement.
919 let trace = compute_state.traces.get(&gid).unwrap().clone();
920 compute_state.traces.set(idx_id, trace);
921 }
922 None => {
923 println!("collection available: {:?}", bundle.collection.is_none());
924 println!(
925 "keys available: {:?}",
926 bundle.arranged.keys().collect::<Vec<_>>()
927 );
928 panic!(
929 "Arrangement alarmingly absent! id: {:?}, keys: {:?}",
930 Id::Global(idx_id),
931 key,
932 );
933 }
934 };
935 }
936}
937
938/// Information about bindings, tracked in `render_recursive_plan` and
939/// `render_plan`, to be passed to `render_letfree_plan`.
940///
941/// `render_letfree_plan` uses these to produce nice output (e.g., `With ...
942/// Returning ...`) for local bindings in the `mz_lir_mapping` output.
943enum BindingInfo {
944 Body { in_let: bool },
945 Let { id: LocalId, last: bool },
946 LetRec { id: LocalId, last: bool },
947}
948
949impl<'scope> Context<'scope, Product<mz_repr::Timestamp, PointStamp<u64>>> {
950 /// Renders a plan to a differential dataflow, producing the collection of results.
951 ///
952 /// This method allows for `plan` to contain [`RecBind`]s, and is planned
953 /// in the context of `level` pre-existing iteration coordinates.
954 ///
955 /// This method recursively descends [`RecBind`] values, establishing nested scopes for each
956 /// and establishing the appropriate recursive dependencies among the bound variables.
957 /// Once all [`RecBind`]s have been rendered it calls in to `render_plan` which will error if
958 /// further [`RecBind`]s are found.
959 ///
960 /// The method requires that all variables conclude with a physical representation that
961 /// contains a collection (i.e. a non-arrangement), and it will panic otherwise.
962 fn render_recursive_plan(
963 &mut self,
964 object_id: GlobalId,
965 level: usize,
966 plan: RenderPlan,
967 binding: BindingInfo,
968 ) -> CollectionBundle<'scope, Product<mz_repr::Timestamp, PointStamp<u64>>> {
969 for BindStage { lets, recs } in plan.binds {
970 // Render the let bindings in order.
971 let mut let_iter = lets.into_iter().peekable();
972 while let Some(LetBind { id, value }) = let_iter.next() {
973 let bundle =
974 self.scope
975 .clone()
976 .region_named(&format!("Binding({:?})", id), |region| {
977 let depends = value.depends();
978 let last = let_iter.peek().is_none();
979 let binding = BindingInfo::Let { id, last };
980 self.enter_region(region, Some(&depends))
981 .render_letfree_plan(object_id, value, binding)
982 .leave_region(self.scope)
983 });
984 let bundle = self.distinct_binding_errs(bundle);
985 self.insert_id(Id::Local(id), bundle);
986 }
987
988 let rec_ids: Vec<_> = recs.iter().map(|r| r.id).collect();
989
990 // A binding's `Variable` serves the `Get`s rendered before the rec
991 // loop binds the real value, which are exactly the values of
992 // `recs[0..=i]`. A binding no such value reads has no use for a
993 // `Variable`-backed bundle, and installing one would build a
994 // re-encode that repacks the whole collection once per iteration
995 // with nothing to consume it.
996 let mut variable_read = BTreeSet::new();
997 let mut read_so_far = BTreeSet::new();
998 for rec in recs.iter() {
999 read_so_far.extend(rec.value.depends());
1000 if read_so_far.contains(&Id::Local(rec.id)) {
1001 variable_read.insert(rec.id);
1002 }
1003 }
1004
1005 // Define variables for rec bindings.
1006 // It is important that we only use the `Variable` until the object is bound.
1007 // At that point, all subsequent uses should have access to the object itself.
1008 let mut variables = BTreeMap::new();
1009 for id in rec_ids.iter() {
1010 use differential_dataflow::dynamic::feedback_summary;
1011 let inner = feedback_summary::<u64>(level + 1, 1);
1012 let (oks_v, oks_collection) =
1013 Variable::new(self.scope, Product::new(Default::default(), inner.clone()));
1014 let (err_v, err_collection) =
1015 Variable::new(self.scope, Product::new(Default::default(), inner));
1016
1017 if variable_read.contains(id) {
1018 self.insert_id(
1019 Id::Local(*id),
1020 CollectionBundle::from_edge(oks_collection, err_collection),
1021 );
1022 }
1023 variables.insert(Id::Local(*id), (oks_v, err_v));
1024 }
1025 // The bound value is kept so the extraction below reuses it rather than
1026 // reaching back into a bundle that forward reads have since collapsed.
1027 let mut bound_oks = BTreeMap::new();
1028 let mut rec_iter = recs.into_iter().peekable();
1029 while let Some(RecBind { id, value, limit }) = rec_iter.next() {
1030 let last = rec_iter.peek().is_none();
1031 let binding = BindingInfo::LetRec { id, last };
1032 let bundle = self.render_recursive_plan(object_id, level + 1, value, binding);
1033 // We need to ensure that the raw collection exists, but do not have enough information
1034 // here to cause that to happen.
1035 let (oks, mut err) = bundle.collection.clone().unwrap();
1036 bound_oks.insert(id, oks.clone());
1037 // Collapses what forward reads see. `err_v` below feeds reads rendered before this
1038 // binding and is collapsed separately; without this, a `Get` in a later rec binding
1039 // or in the body resolves to the bundle stored here and compounds level over level,
1040 // which is exactly what the collapse prevents for non-recursive bindings.
1041 let bundle = self.distinct_binding_errs(bundle);
1042 self.insert_id(Id::Local(id), bundle);
1043 let (oks_v, err_v) = variables.remove(&Id::Local(id)).unwrap();
1044
1045 // Set oks variable to `oks` but consolidated to ensure iteration ceases at fixed point.
1046 let mut oks = columnar_consolidate(oks, "LetRecConsolidation");
1047
1048 if let Some(limit) = limit {
1049 // We swallow the results of the `max_iter`th iteration, because
1050 // these results would go into the `max_iter + 1`th iteration.
1051 //
1052 // The split consults each update's own time. A container's capability
1053 // is only a lower bound on the times it carries, so deciding per
1054 // container by capability could retain updates from an iteration
1055 // beyond the one the capability names.
1056 //
1057 // The predicate reads the time out of the column into `time`, whose
1058 // coordinate vector it reuses across records.
1059 let mut time = RecTimestamp::default();
1060 let (over_limit, in_limit) = oks
1061 .inner
1062 .partition_by::<ColumnBuilder<(Row, RecTimestamp, Diff)>, _>(
1063 "LetRecLimit",
1064 move |(_data, reference, _diff)| {
1065 time.copy_from(*reference);
1066 // The iteration number, or zero if absent, since trailing zero
1067 // coordinates are truncated.
1068 let iteration_index = time.inner.get(level).copied().unwrap_or(0);
1069 // The pointstamp starts counting from 0, so we need to add 1.
1070 iteration_index + 1 >= limit.max_iters.into()
1071 },
1072 );
1073 oks = Collection::new(in_limit);
1074 if !limit.return_at_limit {
1075 // One error per record that passed the limit, replacing the record.
1076 // `flat_map_datums` reads the record without decoding it, and the ok
1077 // side stays empty, so the builder it names never ships a container.
1078 let (_, over_limit) = flat_map_datums::<
1079 _,
1080 CapacityContainerBuilder<Vec<(Row, RecTimestamp, Diff)>>,
1081 _,
1082 >(
1083 Collection::new(over_limit),
1084 "LetRecLimitExceeded",
1085 0,
1086 move |_datums, time, diff, _ok_session, err_session| {
1087 err_session.give((
1088 DataflowErrorSer::from(EvalError::LetRecLimitExceeded(
1089 format!("{}", limit.max_iters.get()).into(),
1090 )),
1091 time,
1092 diff,
1093 ));
1094 1
1095 },
1096 );
1097 err = err.concat(over_limit.as_collection());
1098 }
1099 }
1100
1101 // Set err variable to the distinct elements of `err`.
1102 // Distinctness is important, as we otherwise might add the same error each iteration,
1103 // say if the limit of `oks` has an error. This would result in non-terminating rather
1104 // than a clean report of the error. The trade-off is that we lose information about
1105 // multiplicities of errors, but .. this seems to be the better call.
1106 let err: KeyCollection<_, _, _> = err.into();
1107 let errs = err
1108 .mz_arrange::<ErrBatcher<_, _>, ErrSpine<_, _>>("Arrange recursive err")
1109 .mz_reduce_abelian::<_, ErrBuilder<_, _>, ErrSpine<_, _>, _>(
1110 "Distinct recursive err",
1111 move |_k, _s, t| t.push(((), Diff::ONE)),
1112 )
1113 .as_collection(|k, _| k.clone());
1114
1115 oks_v.set(oks);
1116 err_v.set(errs);
1117 }
1118 // Now extract each of the rec bindings into the outer scope.
1119 for id in rec_ids.into_iter() {
1120 let bundle = self.remove_id(Id::Local(id)).unwrap();
1121 let (_, err) = bundle.collection.unwrap();
1122 let oks = bound_oks
1123 .remove(&id)
1124 .expect("rec binding bound while rendering above");
1125 self.insert_id(
1126 Id::Local(id),
1127 CollectionBundle::from_edge(
1128 columnar_leave_dynamic(oks, level + 1),
1129 err.leave_dynamic(level + 1),
1130 ),
1131 );
1132 }
1133 }
1134
1135 self.render_letfree_plan(object_id, plan.body, binding)
1136 }
1137}
1138
1139impl<'scope, T: RenderTimestamp + MaybeBucketByTime> Context<'scope, T> {
1140 /// Renders a non-recursive plan to a differential dataflow, producing the collection of
1141 /// results.
1142 ///
1143 /// The return type reflects the uncertainty about the data representation, perhaps
1144 /// as a stream of data, perhaps as an arrangement, perhaps as a stream of batches.
1145 ///
1146 /// # Panics
1147 ///
1148 /// Panics if the given plan contains any [`RecBind`]s. Recursive plans must be rendered using
1149 /// `render_recursive_plan` instead.
1150 fn render_plan(
1151 &mut self,
1152 object_id: GlobalId,
1153 plan: RenderPlan,
1154 ) -> CollectionBundle<'scope, T> {
1155 let mut in_let = false;
1156 for BindStage { lets, recs } in plan.binds {
1157 assert!(recs.is_empty());
1158
1159 let mut let_iter = lets.into_iter().peekable();
1160 while let Some(LetBind { id, value }) = let_iter.next() {
1161 // if we encounter a single let, the body is in a let
1162 in_let = true;
1163 let bundle =
1164 self.scope
1165 .clone()
1166 .region_named(&format!("Binding({:?})", id), |region| {
1167 let depends = value.depends();
1168 let last = let_iter.peek().is_none();
1169 let binding = BindingInfo::Let { id, last };
1170 self.enter_region(region, Some(&depends))
1171 .render_letfree_plan(object_id, value, binding)
1172 .leave_region(self.scope)
1173 });
1174 let bundle = self.distinct_binding_errs(bundle);
1175 self.insert_id(Id::Local(id), bundle);
1176 }
1177 }
1178
1179 self.scope.clone().region_named("Main Body", |region| {
1180 let depends = plan.body.depends();
1181 self.enter_region(region, Some(&depends))
1182 .render_letfree_plan(object_id, plan.body, BindingInfo::Body { in_let })
1183 .leave_region(self.scope)
1184 })
1185 }
1186
1187 /// Collapses a binding's error multiplicities.
1188 ///
1189 /// Applied to every binding, not only the multiply-read ones. Gating on the reference count
1190 /// would be a pure optimization, since collapsing a binding one `Get` reads is harmless, and
1191 /// there is almost nothing to gate: `NormalizeLets` inlines single-use bindings, so the ones
1192 /// reaching rendering are shared. See [`CollectionBundle::distinct_errs`] for why the collapse
1193 /// is needed at all, and why a binding's definition is the place for it rather than the
1194 /// multi-input operators where the duplicate copies happen to meet again.
1195 fn distinct_binding_errs(
1196 &self,
1197 bundle: CollectionBundle<'scope, T>,
1198 ) -> CollectionBundle<'scope, T> {
1199 if ENABLE_ERROR_DISTINCT.get(&self.config_set) {
1200 bundle.distinct_errs()
1201 } else {
1202 bundle
1203 }
1204 }
1205
1206 /// Renders a let-free plan to a differential dataflow, producing the collection of results.
1207 fn render_letfree_plan(
1208 &self,
1209 object_id: GlobalId,
1210 plan: LetFreePlan,
1211 binding: BindingInfo,
1212 ) -> CollectionBundle<'scope, T> {
1213 let (mut nodes, root_id, topological_order) = plan.destruct();
1214
1215 // Rendered collections by their `LirId`.
1216 let mut collections = BTreeMap::new();
1217
1218 // Mappings to send along.
1219 // To save overhead, we'll only compute mappings when we need to,
1220 // which means things get gated behind options. Unfortunately, that means we
1221 // have several `Option<...>` types that are _all_ `Some` or `None` together,
1222 // but there's no convenient way to express the invariant.
1223 let should_compute_lir_metadata = self.compute_logger.is_some();
1224 let mut lir_mapping_metadata = if should_compute_lir_metadata {
1225 Some(Vec::with_capacity(nodes.len()))
1226 } else {
1227 None
1228 };
1229
1230 let mut topo_iter = topological_order.into_iter().peekable();
1231 while let Some(lir_id) = topo_iter.next() {
1232 let node = nodes.remove(&lir_id).unwrap();
1233
1234 // TODO(mgree) need ExprHumanizer in DataflowDescription to get nice column names
1235 // ActiveComputeState can't have a catalog reference, so we'll need to capture the names
1236 // in some other structure and have that structure impl ExprHumanizer
1237 let metadata = if should_compute_lir_metadata {
1238 let operator = node.expr.humanize(&DummyHumanizer);
1239
1240 // mark the last operator in topo order with any binding decoration
1241 let operator = if topo_iter.peek().is_none() {
1242 match &binding {
1243 BindingInfo::Body { in_let: true } => format!("Returning {operator}"),
1244 BindingInfo::Body { in_let: false } => operator,
1245 BindingInfo::Let { id, last: true } => {
1246 format!("With {id} = {operator}")
1247 }
1248 BindingInfo::Let { id, last: false } => {
1249 format!("{id} = {operator}")
1250 }
1251 BindingInfo::LetRec { id, last: true } => {
1252 format!("With Recursive {id} = {operator}")
1253 }
1254 BindingInfo::LetRec { id, last: false } => {
1255 format!("{id} = {operator}")
1256 }
1257 }
1258 } else {
1259 operator
1260 };
1261
1262 // NOTE: `EXPLAIN ANALYZE ... CPU` (mz-sql) counts the operators of a span at the
1263 // smallest address depth as its outermost ones. That holds because every node
1264 // renders in `self.scope` and closes the regions it opens before
1265 // `render_plan_expr` returns. A region open across span boundaries would make
1266 // those queries count nested operators' time twice.
1267 let operator_id_start = self.scope.worker().peek_identifier();
1268 Some((operator, operator_id_start))
1269 } else {
1270 None
1271 };
1272
1273 let mut bundle = self.render_plan_expr(node.expr, &collections);
1274
1275 if let Some((operator, operator_id_start)) = metadata {
1276 let operator_id_end = self.scope.worker().peek_identifier();
1277 let operator_span = (operator_id_start, operator_id_end);
1278
1279 if let Some(lir_mapping_metadata) = &mut lir_mapping_metadata {
1280 lir_mapping_metadata.push((
1281 lir_id,
1282 LirMetadata::new(operator, node.parent, node.nesting, operator_span),
1283 ))
1284 }
1285 }
1286
1287 self.log_operator_hydration(&mut bundle, lir_id);
1288
1289 collections.insert(lir_id, bundle);
1290 }
1291
1292 if let Some(lir_mapping_metadata) = lir_mapping_metadata {
1293 self.log_lir_mapping(object_id, lir_mapping_metadata);
1294 }
1295
1296 collections
1297 .remove(&root_id)
1298 .expect("LetFreePlan invariant (1)")
1299 }
1300
1301 /// Renders a [`render_plan::Expr`], producing the collection of results.
1302 ///
1303 /// # Panics
1304 ///
1305 /// Panics if any of the expr's inputs is not found in `collections`.
1306 /// Callers must ensure that input nodes have been rendered previously.
1307 fn render_plan_expr(
1308 &self,
1309 expr: render_plan::Expr,
1310 collections: &BTreeMap<LirId, CollectionBundle<'scope, T>>,
1311 ) -> CollectionBundle<'scope, T> {
1312 use render_plan::Expr::*;
1313
1314 let expect_input = |id| {
1315 collections
1316 .get(&id)
1317 .cloned()
1318 .unwrap_or_else(|| panic!("missing input collection: {id}"))
1319 };
1320
1321 match expr {
1322 Constant { rows } => {
1323 // Produce both rows and errs to avoid conditional dataflow construction.
1324 let (rows, errs) = match rows {
1325 Ok(rows) => (rows, Vec::new()),
1326 Err(e) => (Vec::new(), vec![e]),
1327 };
1328
1329 // We should advance times in constant collections to start from `as_of`.
1330 let as_of_frontier = self.as_of_frontier.clone();
1331 let until = self.until.clone();
1332 // Advancing times to `as_of` can collapse distinct times onto one, so
1333 // rows the planner left distinct can become duplicates. The
1334 // `ConsolidatingColumnBuilder` folds those within the batch.
1335 let ok_collection = rows
1336 .into_iter()
1337 .filter_map(move |(row, mut time, diff)| {
1338 time.advance_by(as_of_frontier.borrow());
1339 if !until.less_equal(&time) {
1340 Some((
1341 row.0,
1342 <T as Refines<mz_repr::Timestamp>>::to_inner(time),
1343 diff,
1344 ))
1345 } else {
1346 None
1347 }
1348 })
1349 .to_stream_with_builder::<_, ConsolidatingColumnBuilder<Row, T, Diff>>(
1350 self.scope,
1351 )
1352 .as_collection();
1353
1354 let mut error_time: mz_repr::Timestamp = Timestamp::minimum();
1355 error_time.advance_by(self.as_of_frontier.borrow());
1356 // The as-of can be at or beyond the `until`, for example on restart of a REFRESH
1357 // MV past its last refresh. The error must then be dropped like the rows above,
1358 // because `apply_refresh` asserts that no update arrives after the last refresh.
1359 let errs = if self.until.less_equal(&error_time) {
1360 Vec::new()
1361 } else {
1362 errs
1363 };
1364 let err_collection = errs
1365 .into_iter()
1366 .map(move |e| {
1367 (
1368 DataflowErrorSer::from(e),
1369 <T as Refines<mz_repr::Timestamp>>::to_inner(error_time),
1370 Diff::ONE,
1371 )
1372 })
1373 .to_stream(self.scope)
1374 .as_collection();
1375
1376 CollectionBundle::from_edge(ok_collection, err_collection)
1377 }
1378 Get { id, keys, plan } => {
1379 // Recover the collection from `self` and then apply `mfp` to it.
1380 // If `mfp` happens to be trivial, we can just return the collection.
1381 let mut collection = self
1382 .lookup_id(id)
1383 .unwrap_or_else(|| panic!("Get({:?}) not found at render time", id));
1384 match plan {
1385 mz_compute_types::plan::GetPlan::PassArrangements => {
1386 // Assert that each of `keys` are present in `collection`.
1387 assert!(
1388 keys.arranged
1389 .iter()
1390 .all(|(key, _, _)| collection.arranged.contains_key(key))
1391 );
1392 assert!(keys.raw <= collection.collection.is_some());
1393 // Retain only those keys we want to import.
1394 collection.arranged.retain(|key, _value| {
1395 keys.arranged.iter().any(|(key2, _, _)| key2 == key)
1396 });
1397 collection
1398 }
1399 mz_compute_types::plan::GetPlan::Arrangement(key, row, mfp) => {
1400 let (oks, errs) = collection.as_collection_core(
1401 mfp,
1402 Some((key, row)),
1403 self.until.clone(),
1404 );
1405 CollectionBundle::from_edge(oks, errs)
1406 }
1407 mz_compute_types::plan::GetPlan::Collection(mfp) => {
1408 let (oks, errs) =
1409 collection.as_collection_core(mfp, None, self.until.clone());
1410 CollectionBundle::from_edge(oks, errs)
1411 }
1412 }
1413 }
1414 Mfp {
1415 input,
1416 mfp,
1417 input_key_val,
1418 } => {
1419 let input = expect_input(input);
1420 // If `mfp` is non-trivial, we should apply it and produce a collection.
1421 if mfp.is_identity() {
1422 input
1423 } else {
1424 let (oks, errs) =
1425 input.as_collection_core(mfp, input_key_val, self.until.clone());
1426 CollectionBundle::from_edge(oks, errs)
1427 }
1428 }
1429 FlatMap {
1430 input_key,
1431 input,
1432 exprs,
1433 func,
1434 mfp_after: mfp,
1435 } => {
1436 let input = expect_input(input);
1437 self.render_flat_map(input_key, input, exprs, func, mfp)
1438 }
1439 Join { inputs, plan } => {
1440 let inputs = inputs.into_iter().map(expect_input).collect();
1441 match plan {
1442 mz_compute_types::plan::join::JoinPlan::Linear(linear_plan) => {
1443 self.render_join(inputs, linear_plan)
1444 }
1445 mz_compute_types::plan::join::JoinPlan::Delta(delta_plan) => {
1446 self.render_delta_join(inputs, delta_plan)
1447 }
1448 }
1449 }
1450 Reduce {
1451 input_key,
1452 input,
1453 key_val_plan,
1454 plan,
1455 mfp_after,
1456 temporal_bucketing_strategy,
1457 } => {
1458 let input = expect_input(input);
1459 let mfp_option = (!mfp_after.is_identity()).then_some(mfp_after);
1460 self.render_reduce(
1461 input_key,
1462 input,
1463 key_val_plan,
1464 plan,
1465 mfp_option,
1466 temporal_bucketing_strategy,
1467 )
1468 }
1469 TopK {
1470 input,
1471 top_k_plan,
1472 temporal_bucketing_strategy,
1473 } => {
1474 let input = expect_input(input);
1475 self.render_topk(input, top_k_plan, temporal_bucketing_strategy)
1476 }
1477 Negate { input } => {
1478 let input = expect_input(input);
1479 let (oks, errs) = input
1480 .collection
1481 .clone()
1482 .expect("Negate input must be an unarranged collection");
1483 CollectionBundle::from_edge(columnar_negate(oks), errs)
1484 }
1485 Threshold {
1486 input,
1487 threshold_plan,
1488 } => {
1489 let input = expect_input(input);
1490 self.render_threshold(input, threshold_plan)
1491 }
1492 Union {
1493 inputs,
1494 consolidate_output,
1495 temporal_bucketing_strategies,
1496 } => {
1497 let mut oks = Vec::new();
1498 let mut errs = Vec::new();
1499 for (input, strategy) in inputs.into_iter().zip_eq(temporal_bucketing_strategies) {
1500 let (os, es) = expect_input(input)
1501 .collection
1502 .clone()
1503 .expect("Union input must be an unarranged collection");
1504 // Apply per-input temporal bucketing. No-op for `Direct`.
1505 // Only consolidating Unions carry non-`Direct` strategies;
1506 // see the `Union` arm of `lower_mir_expr_stack_safe`.
1507 let os = if matches!(strategy, ArrangementStrategy::TemporalBucketing) {
1508 let summary: mz_repr::Timestamp = TEMPORAL_BUCKETING_SUMMARY
1509 .get(&self.config_set)
1510 .try_into()
1511 .expect("must fit");
1512 T::maybe_apply_temporal_bucketing(
1513 os.inner,
1514 self.as_of_frontier.clone(),
1515 summary,
1516 )
1517 } else {
1518 os
1519 };
1520 oks.push(os);
1521 errs.push(es);
1522 }
1523 let oks = concat_many(self.scope, oks);
1524 let oks = if consolidate_output {
1525 columnar_consolidate(oks, "UnionConsolidation")
1526 } else {
1527 oks
1528 };
1529 let errs = differential_dataflow::collection::concatenate(self.scope, errs);
1530 CollectionBundle::from_edge(oks, errs)
1531 }
1532 ArrangeBy {
1533 input_key,
1534 input,
1535 input_mfp,
1536 forms: keys,
1537 strategy,
1538 } => {
1539 let input = expect_input(input);
1540 input.ensure_collections(
1541 keys,
1542 input_key,
1543 input_mfp,
1544 self.as_of_frontier.clone(),
1545 self.until.clone(),
1546 &self.config_set,
1547 strategy,
1548 )
1549 }
1550 }
1551 }
1552
1553 fn log_dataflow_global_id(&self, dataflow_index: usize, global_id: GlobalId) {
1554 if let Some(logger) = &self.compute_logger {
1555 logger.log(&ComputeEvent::DataflowGlobal(DataflowGlobal {
1556 dataflow_index,
1557 global_id,
1558 }));
1559 }
1560 }
1561
1562 fn log_lir_mapping(&self, global_id: GlobalId, mapping: Vec<(LirId, LirMetadata)>) {
1563 if let Some(logger) = &self.compute_logger {
1564 logger.log(&ComputeEvent::LirMapping(LirMapping { global_id, mapping }));
1565 }
1566 }
1567
1568 fn log_operator_hydration(&self, bundle: &mut CollectionBundle<'scope, T>, lir_id: LirId) {
1569 // A `CollectionBundle` can contain more than one collection, which makes it not obvious to
1570 // which we should attach the logging operator.
1571 //
1572 // We could attach to each collection and track the lower bound of output frontiers.
1573 // However, that would be of limited use because we expect all collections to hydrate at
1574 // roughly the same time: The `ArrangeBy` operator is not fueled, so as soon as it sees the
1575 // frontier of the unarranged collection advance, it will perform all work necessary to
1576 // also advance its own frontier. We don't expect significant delays between frontier
1577 // advancements of the unarranged and arranged collections, so attaching the logging
1578 // operator to any one of them should produce accurate results.
1579 //
1580 // If the `CollectionBundle` contains both unarranged and arranged representations it is
1581 // beneficial to attach the logging operator to one of the arranged representation to avoid
1582 // unnecessary cloning of data. The unarranged collection feeds into the arrangements, so
1583 // if we attached the logging operator to it, we would introduce a fork in its output
1584 // stream, which would necessitate that all output data is cloned. In contrast, we can hope
1585 // that the output streams of the arrangements don't yet feed into anything else, so
1586 // attaching a (pass-through) logging operator does not introduce a fork.
1587
1588 match bundle.arranged.values_mut().next() {
1589 Some(arrangement) => {
1590 use ArrangementFlavor::*;
1591
1592 match arrangement {
1593 Local(a, _) => {
1594 a.stream = self.log_operator_hydration_inner(a.stream.clone(), lir_id);
1595 }
1596 Trace(_, a, _) => {
1597 a.stream = self.log_operator_hydration_inner(a.stream.clone(), lir_id);
1598 }
1599 }
1600 }
1601 None => {
1602 let (oks, _) = bundle
1603 .collection
1604 .as_mut()
1605 .expect("CollectionBundle invariant");
1606 let stream = self.log_operator_hydration_inner(oks.inner.clone(), lir_id);
1607 *oks = stream.as_collection();
1608 }
1609 }
1610 }
1611
1612 fn log_operator_hydration_inner<D>(
1613 &self,
1614 stream: Stream<'scope, T, D>,
1615 lir_id: LirId,
1616 ) -> Stream<'scope, T, D>
1617 where
1618 D: timely::Container + Clone + 'static,
1619 {
1620 let Some(logger) = self.compute_logger.clone() else {
1621 return stream.clone(); // hydration logging disabled
1622 };
1623
1624 let export_ids = self.export_ids.clone();
1625
1626 // Convert the dataflow as-of into a frontier we can compare with input frontiers.
1627 //
1628 // We (somewhat arbitrarily) define operators in iterative scopes to be hydrated when their
1629 // frontier advances to an outer time that's greater than the `as_of`. Comparing
1630 // `refine(as_of) < input_frontier` would find the moment when the first iteration was
1631 // complete, which is not what we want. We want `refine(as_of + 1) <= input_frontier`
1632 // instead.
1633 let mut hydration_frontier = Antichain::new();
1634 for time in self.as_of_frontier.iter() {
1635 if let Some(time) = time.try_step_forward() {
1636 hydration_frontier.insert(Refines::to_inner(time));
1637 }
1638 }
1639
1640 let name = format!("LogOperatorHydration ({lir_id})");
1641 stream.unary_frontier(Pipeline, &name, |_cap, _info| {
1642 let mut hydrated = false;
1643
1644 for &export_id in &export_ids {
1645 logger.log(&ComputeEvent::OperatorHydration(OperatorHydration {
1646 export_id,
1647 lir_id,
1648 hydrated,
1649 }));
1650 }
1651
1652 move |(input, frontier), output| {
1653 // Pass through inputs.
1654 input.for_each(|cap, data| {
1655 output.session(&cap).give_container(data);
1656 });
1657
1658 if hydrated {
1659 return;
1660 }
1661
1662 if PartialOrder::less_equal(&hydration_frontier.borrow(), &frontier.frontier()) {
1663 hydrated = true;
1664
1665 for &export_id in &export_ids {
1666 logger.log(&ComputeEvent::OperatorHydration(OperatorHydration {
1667 export_id,
1668 lir_id,
1669 hydrated,
1670 }));
1671 }
1672 }
1673 }
1674 })
1675 }
1676}
1677
1678#[allow(dead_code)] // Some of the methods on this trait are unused, but useful to have.
1679/// A timestamp type that can be used for operations within MZ's dataflow layer.
1680pub trait RenderTimestamp: MzTimestamp + Default + Refines<mz_repr::Timestamp> {
1681 /// The system timestamp component of the timestamp.
1682 ///
1683 /// This is useful for manipulating the system time, as when delaying
1684 /// updates for subsequent cancellation, as with monotonic reduction.
1685 fn system_time(&mut self) -> &mut mz_repr::Timestamp;
1686 /// Effects a system delay in terms of the timestamp summary.
1687 fn system_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary;
1688 /// The event timestamp component of the timestamp.
1689 fn event_time(&self) -> mz_repr::Timestamp;
1690 /// The event timestamp component of the timestamp, as a mutable reference.
1691 fn event_time_mut(&mut self) -> &mut mz_repr::Timestamp;
1692 /// Effects an event delay in terms of the timestamp summary.
1693 fn event_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary;
1694 /// Steps the timestamp back so that logical compaction to the output will
1695 /// not conflate `self` with any historical times.
1696 fn step_back(&self) -> Self;
1697}
1698
1699/// Apply temporal bucketing to a stream when the timestamp type supports it.
1700///
1701/// Sibling to [`RenderTimestamp`]: bucketing is an arrangement-time concern, not a
1702/// general property of a render timestamp, so the dispatch lives in its own trait.
1703/// Total-ordered timestamps perform real bucketing; partially-ordered timestamps
1704/// (e.g. `Product<…>` in iterative scopes) implement this as a no-op.
1705pub trait MaybeBucketByTime: Timestamp + ColumnarData {
1706 /// Buckets a columnar dataflow edge, keeping it columnar.
1707 fn maybe_apply_temporal_bucketing<'scope, D>(
1708 stream: Stream<'scope, Self, Column<(D, Self, Diff)>>,
1709 as_of: Antichain<mz_repr::Timestamp>,
1710 summary: mz_repr::Timestamp,
1711 ) -> Collection<'scope, Self, Column<(D, Self, Diff)>>
1712 where
1713 D: differential_dataflow::ExchangeData
1714 + crate::typedefs::MzData
1715 + differential_dataflow::Hashable
1716 + ColumnarData,
1717 for<'a> ::columnar::Ref<'a, D>: Copy + Ord + std::hash::Hash,
1718 for<'a> ::columnar::Ref<'a, Self>: Copy + Ord,
1719 for<'a> ::columnar::Ref<'a, Diff>: Ord,
1720 for<'a> <(D, Self, Diff) as ColumnarData>::Container:
1721 ColumnarPush<&'a (D, Self, Diff)> + ColumnarPush<::columnar::Ref<'a, (D, Self, Diff)>>;
1722
1723 /// Buckets a `Vec` stream, keeping it `Vec`.
1724 ///
1725 /// For a consumer that re-encodes what it reads, where a `Vec` hands it moved
1726 /// allocations rather than copied bytes. The reduce key-value path is the one
1727 /// such caller, since its bucketed output feeds an arrangement.
1728 fn maybe_apply_temporal_bucketing_vec<'scope, D>(
1729 stream: StreamVec<'scope, Self, (D, Self, Diff)>,
1730 as_of: Antichain<mz_repr::Timestamp>,
1731 summary: mz_repr::Timestamp,
1732 ) -> VecCollection<'scope, Self, D, Diff>
1733 where
1734 D: differential_dataflow::ExchangeData
1735 + crate::typedefs::MzData
1736 + differential_dataflow::Hashable
1737 + ColumnarData,
1738 for<'a> ::columnar::Ref<'a, D>: Copy + Ord + std::hash::Hash,
1739 for<'a> ::columnar::Ref<'a, Self>: Copy + Ord,
1740 for<'a> ::columnar::Ref<'a, Diff>: Ord,
1741 for<'a> <(D, Self, Diff) as ColumnarData>::Container:
1742 ColumnarPush<&'a (D, Self, Diff)> + ColumnarPush<::columnar::Ref<'a, (D, Self, Diff)>>;
1743}
1744
1745impl RenderTimestamp for mz_repr::Timestamp {
1746 fn system_time(&mut self) -> &mut mz_repr::Timestamp {
1747 self
1748 }
1749 fn system_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary {
1750 delay
1751 }
1752 fn event_time(&self) -> mz_repr::Timestamp {
1753 *self
1754 }
1755 fn event_time_mut(&mut self) -> &mut mz_repr::Timestamp {
1756 self
1757 }
1758 fn event_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary {
1759 delay
1760 }
1761 fn step_back(&self) -> Self {
1762 self.saturating_sub(1)
1763 }
1764}
1765
1766impl MaybeBucketByTime for mz_repr::Timestamp {
1767 fn maybe_apply_temporal_bucketing<'scope, D>(
1768 stream: Stream<'scope, Self, Column<(D, Self, Diff)>>,
1769 as_of: Antichain<mz_repr::Timestamp>,
1770 summary: mz_repr::Timestamp,
1771 ) -> Collection<'scope, Self, Column<(D, Self, Diff)>>
1772 where
1773 D: differential_dataflow::ExchangeData
1774 + crate::typedefs::MzData
1775 + differential_dataflow::Hashable
1776 + ColumnarData,
1777 for<'a> ::columnar::Ref<'a, D>: Copy + Ord + std::hash::Hash,
1778 for<'a> ::columnar::Ref<'a, Self>: Copy + Ord,
1779 for<'a> ::columnar::Ref<'a, Diff>: Ord,
1780 for<'a> <(D, Self, Diff) as ColumnarData>::Container:
1781 ColumnarPush<&'a (D, Self, Diff)> + ColumnarPush<::columnar::Ref<'a, (D, Self, Diff)>>,
1782 {
1783 stream.bucket(as_of, summary).as_collection()
1784 }
1785
1786 fn maybe_apply_temporal_bucketing_vec<'scope, D>(
1787 stream: StreamVec<'scope, Self, (D, Self, Diff)>,
1788 as_of: Antichain<mz_repr::Timestamp>,
1789 summary: mz_repr::Timestamp,
1790 ) -> VecCollection<'scope, Self, D, Diff>
1791 where
1792 D: differential_dataflow::ExchangeData
1793 + crate::typedefs::MzData
1794 + differential_dataflow::Hashable
1795 + ColumnarData,
1796 for<'a> ::columnar::Ref<'a, D>: Copy + Ord + std::hash::Hash,
1797 for<'a> ::columnar::Ref<'a, Self>: Copy + Ord,
1798 for<'a> ::columnar::Ref<'a, Diff>: Ord,
1799 for<'a> <(D, Self, Diff) as ColumnarData>::Container:
1800 ColumnarPush<&'a (D, Self, Diff)> + ColumnarPush<::columnar::Ref<'a, (D, Self, Diff)>>,
1801 {
1802 stream.bucket(as_of, summary).as_collection()
1803 }
1804}
1805
1806impl RenderTimestamp for Product<mz_repr::Timestamp, PointStamp<u64>> {
1807 fn system_time(&mut self) -> &mut mz_repr::Timestamp {
1808 &mut self.outer
1809 }
1810 fn system_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary {
1811 Product::new(delay, Default::default())
1812 }
1813 fn event_time(&self) -> mz_repr::Timestamp {
1814 self.outer
1815 }
1816 fn event_time_mut(&mut self) -> &mut mz_repr::Timestamp {
1817 &mut self.outer
1818 }
1819 fn event_delay(delay: mz_repr::Timestamp) -> <Self as Timestamp>::Summary {
1820 Product::new(delay, Default::default())
1821 }
1822 fn step_back(&self) -> Self {
1823 // It is necessary to step back both coordinates of a product,
1824 // and when one is a `PointStamp` that also means all coordinates
1825 // of the pointstamp.
1826 let inner = self.inner.clone();
1827 let mut vec = inner.into_inner();
1828 for item in vec.iter_mut() {
1829 *item = item.saturating_sub(1);
1830 }
1831 Product::new(self.outer.saturating_sub(1), PointStamp::new(vec))
1832 }
1833}
1834
1835impl MaybeBucketByTime for Product<mz_repr::Timestamp, PointStamp<u64>> {
1836 fn maybe_apply_temporal_bucketing<'scope, D>(
1837 stream: Stream<'scope, Self, Column<(D, Self, Diff)>>,
1838 _as_of: Antichain<mz_repr::Timestamp>,
1839 _summary: mz_repr::Timestamp,
1840 ) -> Collection<'scope, Self, Column<(D, Self, Diff)>>
1841 where
1842 D: differential_dataflow::ExchangeData
1843 + crate::typedefs::MzData
1844 + differential_dataflow::Hashable
1845 + ColumnarData,
1846 for<'a> ::columnar::Ref<'a, D>: Copy + Ord + std::hash::Hash,
1847 for<'a> ::columnar::Ref<'a, Self>: Copy + Ord,
1848 for<'a> ::columnar::Ref<'a, Diff>: Ord,
1849 for<'a> <(D, Self, Diff) as ColumnarData>::Container:
1850 ColumnarPush<&'a (D, Self, Diff)> + ColumnarPush<::columnar::Ref<'a, (D, Self, Diff)>>,
1851 {
1852 // TODO: Implement bucketing on outer timestamp for iterative scopes.
1853 stream.as_collection()
1854 }
1855
1856 fn maybe_apply_temporal_bucketing_vec<'scope, D>(
1857 stream: StreamVec<'scope, Self, (D, Self, Diff)>,
1858 _as_of: Antichain<mz_repr::Timestamp>,
1859 _summary: mz_repr::Timestamp,
1860 ) -> VecCollection<'scope, Self, D, Diff>
1861 where
1862 D: differential_dataflow::ExchangeData
1863 + crate::typedefs::MzData
1864 + differential_dataflow::Hashable
1865 + ColumnarData,
1866 for<'a> ::columnar::Ref<'a, D>: Copy + Ord + std::hash::Hash,
1867 for<'a> ::columnar::Ref<'a, Self>: Copy + Ord,
1868 for<'a> ::columnar::Ref<'a, Diff>: Ord,
1869 for<'a> <(D, Self, Diff) as ColumnarData>::Container:
1870 ColumnarPush<&'a (D, Self, Diff)> + ColumnarPush<::columnar::Ref<'a, (D, Self, Diff)>>,
1871 {
1872 // TODO: Implement bucketing on outer timestamp for iterative scopes.
1873 stream.as_collection()
1874 }
1875}
1876
1877/// A signal that can be awaited by operators to suspend them prior to startup.
1878///
1879/// Creating a signal also yields a token, dropping of which causes the signal to fire.
1880///
1881/// `StartSignal` is designed to be usable by both async and sync Timely operators.
1882///
1883/// * Async operators can simply `await` it.
1884/// * Sync operators should register an [`ActivateOnDrop`] value via [`StartSignal::drop_on_fire`]
1885/// and then check `StartSignal::has_fired()` on each activation.
1886#[derive(Clone)]
1887pub(crate) struct StartSignal {
1888 /// A future that completes when the signal fires.
1889 ///
1890 /// The inner type is `Infallible` because no data is ever expected on this channel. Instead the
1891 /// signal is activated by dropping the corresponding `Sender`.
1892 fut: futures::future::Shared<oneshot::Receiver<Infallible>>,
1893 /// A weak reference to the token, to register drop-on-fire values.
1894 token_ref: Weak<RefCell<Box<dyn Any>>>,
1895}
1896
1897impl StartSignal {
1898 /// Create a new `StartSignal` and a corresponding token that activates the signal when
1899 /// dropped.
1900 pub fn new() -> (Self, Rc<dyn Any>) {
1901 let (tx, rx) = oneshot::channel::<Infallible>();
1902 let token: Rc<RefCell<Box<dyn Any>>> = Rc::new(RefCell::new(Box::new(tx)));
1903 let signal = Self {
1904 fut: rx.shared(),
1905 token_ref: Rc::downgrade(&token),
1906 };
1907 (signal, token)
1908 }
1909
1910 pub fn has_fired(&self) -> bool {
1911 self.token_ref.strong_count() == 0
1912 }
1913
1914 /// Returns a Send-safe future that completes when the signal fires.
1915 ///
1916 /// Unlike `StartSignal` itself, the returned future does not retain a reference to the token,
1917 /// so it cannot be used for `drop_on_fire` or `has_fired` checks.
1918 pub fn into_send_future(self) -> impl Future<Output = ()> + Send {
1919 use futures::FutureExt;
1920 self.fut.map(|_| ())
1921 }
1922
1923 pub fn drop_on_fire(&self, to_drop: Box<dyn Any>) {
1924 if let Some(token) = self.token_ref.upgrade() {
1925 let mut token = token.borrow_mut();
1926 let inner = std::mem::replace(&mut *token, Box::new(()));
1927 *token = Box::new((inner, to_drop));
1928 }
1929 }
1930}
1931
1932impl Future for StartSignal {
1933 type Output = ();
1934
1935 fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
1936 self.fut.poll_unpin(cx).map(|_| ())
1937 }
1938}
1939
1940/// Extension trait to attach a `StartSignal` to operator outputs.
1941pub(crate) trait WithStartSignal {
1942 /// Delays data and progress updates until the start signal has fired.
1943 ///
1944 /// Note that this operator needs to buffer all incoming data, so it has some memory footprint,
1945 /// depending on the amount and shape of its inputs.
1946 fn with_start_signal(self, signal: StartSignal) -> Self;
1947}
1948
1949impl<'scope, Tr> WithStartSignal for Arranged<'scope, Tr>
1950where
1951 Tr: TraceReader<Time: RenderTimestamp> + Clone,
1952{
1953 fn with_start_signal(self, signal: StartSignal) -> Self {
1954 Arranged {
1955 stream: self.stream.with_start_signal(signal),
1956 trace: self.trace,
1957 }
1958 }
1959}
1960
1961impl<'scope, T: Timestamp, D> WithStartSignal for Stream<'scope, T, D>
1962where
1963 D: timely::Container + Clone + 'static,
1964{
1965 fn with_start_signal(self, signal: StartSignal) -> Self {
1966 let activations = self.scope().activations();
1967 self.unary(Pipeline, "StartSignal", |_cap, info| {
1968 let token = Box::new(ActivateOnDrop::new((), info.address, activations));
1969 signal.drop_on_fire(token);
1970
1971 let mut stash = Vec::new();
1972
1973 move |input, output| {
1974 // Stash incoming updates as long as the start signal has not fired.
1975 if !signal.has_fired() {
1976 input.for_each(|cap, data| stash.push((cap, std::mem::take(data))));
1977 return;
1978 }
1979
1980 // Release any data we might still have stashed.
1981 for (cap, mut data) in std::mem::take(&mut stash) {
1982 output.session(&cap).give_container(&mut data);
1983 }
1984
1985 // Pass through all remaining input data.
1986 input.for_each(|cap, data| {
1987 output.session(&cap).give_container(data);
1988 });
1989 }
1990 })
1991 }
1992}
1993
1994/// Suppress progress messages for times before the given `as_of`.
1995///
1996/// This operator exists specifically to work around a memory spike we'd otherwise see when
1997/// hydrating arrangements (database-issues#6368). The memory spike happens because when the `arrange_core`
1998/// operator observes a frontier advancement without data it inserts an empty batch into the spine.
1999/// When it later inserts the snapshot batch into the spine, an empty batch is already there and
2000/// the spine initiates a merge of these batches, which requires allocating a new batch the size of
2001/// the snapshot batch.
2002///
2003/// The strategy to avoid the spike is to prevent the insertion of that initial empty batch by
2004/// ensuring that the first frontier advancement downstream `arrange_core` operators observe is
2005/// beyond the `as_of`, so the snapshot data has already been collected.
2006///
2007/// To ensure this, this operator needs to take two measures:
2008/// * Keep around a minimum capability until the input announces progress beyond the `as_of`.
2009/// * Reclock all updates emitted at times not beyond the `as_of` to the minimum time.
2010///
2011/// The second measure requires elaboration: If we wouldn't reclock snapshot updates, they might
2012/// still be upstream of `arrange_core` operators when those get to know about us dropping the
2013/// minimum capability. The in-flight snapshot updates would hold back the input frontiers of
2014/// `arrange_core` operators to the `as_of`, which would cause them to insert empty batches.
2015fn suppress_early_progress<'scope, T: Timestamp, D>(
2016 stream: Stream<'scope, T, D>,
2017 as_of: Antichain<T>,
2018) -> Stream<'scope, T, D>
2019where
2020 D: timely::Container + Clone,
2021{
2022 stream.unary_frontier(Pipeline, "SuppressEarlyProgress", |default_cap, _info| {
2023 let mut early_cap = Some(default_cap);
2024
2025 move |(input, frontier), output| {
2026 input.for_each_stamp(|data_cap, data| {
2027 // A message whose stamp lies entirely beyond the `as_of` carries no snapshot
2028 // updates and keeps its own capability. Any other message travels under the
2029 // minimum capability, which is what suppresses the early progress.
2030 if data_cap.stamp().iter().all(|time| as_of.less_than(time)) {
2031 let mut session = output.session(&data_cap);
2032 for data in data {
2033 session.give_container(data);
2034 }
2035 } else {
2036 let cap = early_cap.as_ref().expect("early_cap can't be dropped yet");
2037 let mut session = output.session(&cap);
2038 for data in data {
2039 session.give_container(data);
2040 }
2041 }
2042 });
2043
2044 if !PartialOrder::less_equal(&frontier.frontier(), &as_of.borrow()) {
2045 early_cap.take();
2046 }
2047 }
2048 })
2049}
2050
2051/// Extension trait for [`Stream`] to selectively limit progress.
2052trait LimitProgress<T: Timestamp> {
2053 /// Limit the progress of the stream until its frontier reaches the given `upper` bound. Expects
2054 /// the implementation to observe times in data, and release capabilities based on the probe's
2055 /// frontier, after applying `slack` to round up timestamps.
2056 ///
2057 /// The implementation of this operator is subtle to avoid regressions in the rest of the
2058 /// system. Specifically joins hold back compaction on the other side of the join, so we need to
2059 /// make sure we release capabilities as soon as possible. This is why we only limit progress
2060 /// for times before the `upper`, which is the time until which the source can distinguish
2061 /// updates at the time of rendering. Once we make progress to the `upper`, we need to release
2062 /// our capability.
2063 ///
2064 /// This isn't perfect, and can result in regressions if on of the inputs lags behind. We could
2065 /// consider using the join of the uppers, i.e, use lower bound upper of all available inputs.
2066 ///
2067 /// Once the input frontier reaches `[]`, the implementation must release any capability to
2068 /// allow downstream operators to release resources.
2069 ///
2070 /// The implementation should limit the number of pending times to `limit` if it is `Some` to
2071 /// avoid unbounded memory usage.
2072 ///
2073 /// * `handle` is a probe installed on the dataflow's outputs as late as possible, but before
2074 /// any timestamp rounding happens (c.f., `REFRESH EVERY` materialized views).
2075 /// * `slack_ms` is the number of milliseconds to round up timestamps to.
2076 /// * `name` is a human-readable name for the operator.
2077 /// * `limit` is the maximum number of pending times to keep around.
2078 /// * `upper` is the upper bound of the stream's frontier until which the implementation can
2079 /// retain a capability.
2080 fn limit_progress(
2081 self,
2082 handle: MzProbeHandle<T>,
2083 slack_ms: u64,
2084 limit: Option<usize>,
2085 upper: Antichain<T>,
2086 name: String,
2087 ) -> Self;
2088}
2089
2090/// Reads the times of the records a container holds, for [`LimitProgress`].
2091trait RecordTimes {
2092 /// Call `f` once per record, with that record's time.
2093 fn for_each_time(&self, f: impl FnMut(mz_repr::Timestamp));
2094}
2095
2096impl<D, R> RecordTimes for Vec<(D, mz_repr::Timestamp, R)> {
2097 fn for_each_time(&self, mut f: impl FnMut(mz_repr::Timestamp)) {
2098 for (_, time, _) in self {
2099 f(*time);
2100 }
2101 }
2102}
2103
2104impl<D, R> RecordTimes for Column<(D, mz_repr::Timestamp, R)>
2105where
2106 D: ColumnarData,
2107 R: ColumnarData,
2108 (D, mz_repr::Timestamp, R): ColumnarData<
2109 Container = (
2110 D::Container,
2111 <mz_repr::Timestamp as ColumnarData>::Container,
2112 R::Container,
2113 ),
2114 >,
2115{
2116 fn for_each_time(&self, mut f: impl FnMut(mz_repr::Timestamp)) {
2117 for time in self.borrow().1.into_index_iter() {
2118 f(time);
2119 }
2120 }
2121}
2122
2123// TODO: We could make this generic over a `T` that can be converted to and from a u64 millisecond
2124// number.
2125impl<'scope, C> LimitProgress<mz_repr::Timestamp> for Stream<'scope, mz_repr::Timestamp, C>
2126where
2127 C: timely::Container + Clone + RecordTimes,
2128{
2129 fn limit_progress(
2130 self,
2131 handle: MzProbeHandle<mz_repr::Timestamp>,
2132 slack_ms: u64,
2133 limit: Option<usize>,
2134 upper: Antichain<mz_repr::Timestamp>,
2135 name: String,
2136 ) -> Self {
2137 let scope = self.scope();
2138 let stream =
2139 self.unary_frontier(Pipeline, &format!("LimitProgress({name})"), |_cap, info| {
2140 // Times that we've observed on our input.
2141 let mut pending_times: BTreeSet<mz_repr::Timestamp> = BTreeSet::new();
2142 // Capability for the lower bound of `pending_times`, if any. The time is
2143 // totally ordered, so the set holds at most one capability, and inserting
2144 // each message's capabilities keeps the earliest.
2145 let mut retained_cap: Option<CapabilitySet<mz_repr::Timestamp>> = None;
2146
2147 let activator = scope.activator_for(info.address);
2148 handle.activate(activator.clone());
2149
2150 move |(input, frontier), output| {
2151 input.for_each(|cap, data| {
2152 data.for_each_time(|time| {
2153 let Some(time) = u64::from(time).checked_add(slack_ms) else {
2154 return;
2155 };
2156 // `slack_ms == 0` means no rounding; otherwise round up to the next
2157 // multiple of `slack_ms`. Avoids a divide-by-zero panic when the
2158 // operator is configured without slack.
2159 let rounded_time = if slack_ms == 0 {
2160 time
2161 } else {
2162 (time / slack_ms).saturating_add(1).saturating_mul(slack_ms)
2163 };
2164 if !upper.less_than(&rounded_time.into()) {
2165 pending_times.insert(rounded_time.into());
2166 }
2167 });
2168 output.session(&cap).give_container(data);
2169 for capability in cap.retain_stamp(0).iter() {
2170 if !upper.less_than(capability.time()) {
2171 retained_cap
2172 .get_or_insert_with(CapabilitySet::new)
2173 .insert(capability.clone());
2174 }
2175 }
2176 });
2177
2178 handle.with_frontier(|f| {
2179 while pending_times
2180 .first()
2181 .map_or(false, |retained_time| !f.less_than(&retained_time))
2182 {
2183 let _ = pending_times.pop_first();
2184 }
2185 });
2186
2187 while limit.map_or(false, |limit| pending_times.len() > limit) {
2188 let _ = pending_times.pop_first();
2189 }
2190
2191 match (retained_cap.as_mut(), pending_times.first()) {
2192 (Some(cap), Some(first)) => cap.downgrade([first]),
2193 (_, None) => retained_cap = None,
2194 _ => {}
2195 }
2196
2197 if frontier.is_empty() {
2198 retained_cap = None;
2199 pending_times.clear();
2200 }
2201
2202 if !pending_times.is_empty() {
2203 tracing::debug!(
2204 name,
2205 info.global_id,
2206 pending_times = %PendingTimesDisplay(pending_times.iter().cloned()),
2207 frontier = ?frontier.frontier().get(0),
2208 probe = ?handle.with_frontier(|f| f.get(0).cloned()),
2209 ?upper,
2210 "pending times",
2211 );
2212 }
2213 }
2214 });
2215 stream
2216 }
2217}
2218
2219/// A formatter for an iterator of timestamps that displays the first element, and subsequently
2220/// the difference between timestamps.
2221struct PendingTimesDisplay<T>(T);
2222
2223impl<T> std::fmt::Display for PendingTimesDisplay<T>
2224where
2225 T: IntoIterator<Item = mz_repr::Timestamp> + Clone,
2226{
2227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2228 let mut iter = self.0.clone().into_iter();
2229 write!(f, "[")?;
2230 if let Some(first) = iter.next() {
2231 write!(f, "{}", first)?;
2232 let mut last = u64::from(first);
2233 for time in iter {
2234 write!(f, ", +{}", u64::from(time) - last)?;
2235 last = u64::from(time);
2236 }
2237 }
2238 write!(f, "]")?;
2239 Ok(())
2240 }
2241}
2242
2243/// Helper to merge pairs of datum iterators into a row or split a datum iterator
2244/// into two rows, given the arity of the first component.
2245#[derive(Clone, Copy, Debug)]
2246struct Pairer {
2247 split_arity: usize,
2248}
2249
2250impl Pairer {
2251 /// Creates a pairer with knowledge of the arity of first component in the pair.
2252 fn new(split_arity: usize) -> Self {
2253 Self { split_arity }
2254 }
2255
2256 /// Merges a pair of datum iterators creating a `Row` instance.
2257 fn merge<'a, I1, I2>(&self, first: I1, second: I2) -> Row
2258 where
2259 I1: IntoIterator<Item = Datum<'a>>,
2260 I2: IntoIterator<Item = Datum<'a>>,
2261 {
2262 SharedRow::pack(first.into_iter().chain(second))
2263 }
2264
2265 /// Splits a datum iterator into a pair of `Row` instances.
2266 fn split<'a>(&self, datum_iter: impl IntoIterator<Item = Datum<'a>>) -> (Row, Row) {
2267 let mut datum_iter = datum_iter.into_iter();
2268 let mut row_builder = SharedRow::get();
2269 let first = row_builder.pack_using(datum_iter.by_ref().take(self.split_arity));
2270 let second = row_builder.pack_using(datum_iter);
2271 (first, second)
2272 }
2273}