1use std::time::{Duration, Instant};
15
16use differential_dataflow::consolidation::ConsolidatingContainerBuilder;
17use differential_dataflow::lattice::Lattice;
18use differential_dataflow::operators::arrange::arrangement::Arranged;
19use differential_dataflow::trace::cursor::{BatchCursor, BatchKey, BatchVal};
20use differential_dataflow::trace::{Cursor, Navigable, TraceReader};
21use differential_dataflow::{AsCollection, Data, VecCollection};
22use mz_compute_types::dyncfgs::{
23 ENABLE_COLUMN_PAGED_BATCHER, ENABLE_MZ_JOIN_CORE, LINEAR_JOIN_YIELDING,
24};
25use mz_compute_types::plan::join::JoinClosure;
26use mz_compute_types::plan::join::linear_join::{LinearJoinPlan, LinearStagePlan};
27use mz_dyncfg::ConfigSet;
28use mz_expr::Eval;
29use mz_repr::fixed_length::ExtendDatums;
30use mz_repr::{DatumVec, Diff, Row, RowArena, SharedRow};
31use mz_timely_util::columnar::batcher;
32use mz_timely_util::columnar::builder::ColumnBuilder;
33use mz_timely_util::columnar::{Col2ValBatcher, Col2ValPagedBatcher, columnar_exchange};
34use mz_timely_util::operator::{CollectionExt, StreamExt};
35use timely::dataflow::Scope;
36use timely::dataflow::channels::pact::{ExchangeCore, Pipeline};
37use timely::dataflow::operators::OkErr;
38
39use crate::extensions::arrange::MzArrangeCore;
40use crate::render::RenderTimestamp;
41use crate::render::context::{ArrangementFlavor, CollectionBundle, Context};
42use crate::render::errors::DataflowErrorSer;
43use crate::render::join::mz_join_core::mz_join_core;
44use crate::typedefs::{RowRowAgent, RowRowEnter};
45use mz_row_spine::{RowRowBuilder, RowRowColPagedBuilder, RowRowSpine};
46
47#[derive(Clone, Copy)]
51enum LinearJoinImpl {
52 Materialize,
53 DifferentialDataflow,
54}
55
56#[derive(Clone, Copy)]
63pub struct LinearJoinSpec {
64 implementation: LinearJoinImpl,
65 yielding: YieldSpec,
66}
67
68impl Default for LinearJoinSpec {
69 fn default() -> Self {
70 Self {
71 implementation: LinearJoinImpl::Materialize,
72 yielding: Default::default(),
73 }
74 }
75}
76
77impl LinearJoinSpec {
78 pub fn from_config(config: &ConfigSet) -> Self {
80 let implementation = if ENABLE_MZ_JOIN_CORE.get(config) {
81 LinearJoinImpl::Materialize
82 } else {
83 LinearJoinImpl::DifferentialDataflow
84 };
85
86 let yielding_raw = LINEAR_JOIN_YIELDING.get(config);
87 let yielding = YieldSpec::try_from_str(&yielding_raw).unwrap_or_else(|| {
88 tracing::error!("invalid LINEAR_JOIN_YIELDING config: {yielding_raw}");
89 YieldSpec::default()
90 });
91
92 Self {
93 implementation,
94 yielding,
95 }
96 }
97
98 fn render<'s, T, Tr1, Tr2, L, I>(
100 &self,
101 arranged1: Arranged<'s, Tr1>,
102 arranged2: Arranged<'s, Tr2>,
103 result: L,
104 ) -> VecCollection<'s, T, I::Item, Diff>
105 where
106 T: Lattice + timely::progress::Timestamp,
107 Tr1: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
108 Tr2: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
109 BatchCursor<Tr1>: Cursor<Time = T, Diff = Diff>,
110 for<'a> BatchCursor<Tr2>: Cursor<Key<'a> = BatchKey<'a, Tr1>, Time = T, Diff = Diff>,
111 L: FnMut(BatchKey<'_, Tr1>, BatchVal<'_, Tr1>, BatchVal<'_, Tr2>) -> I + 'static,
112 I: IntoIterator<Item: Data> + 'static,
113 {
114 use LinearJoinImpl::*;
115
116 match (
117 self.implementation,
118 self.yielding.after_work,
119 self.yielding.after_time,
120 ) {
121 (DifferentialDataflow, _, _) => arranged1.join_core(arranged2, result),
122 (Materialize, Some(work_limit), Some(time_limit)) => {
123 let yield_fn =
124 move |start: Instant, work| work >= work_limit || start.elapsed() >= time_limit;
125 mz_join_core(arranged1, arranged2, result, yield_fn).as_collection()
126 }
127 (Materialize, Some(work_limit), None) => {
128 let yield_fn = move |_start, work| work >= work_limit;
129 mz_join_core(arranged1, arranged2, result, yield_fn).as_collection()
130 }
131 (Materialize, None, Some(time_limit)) => {
132 let yield_fn = move |start: Instant, _work| start.elapsed() >= time_limit;
133 mz_join_core(arranged1, arranged2, result, yield_fn).as_collection()
134 }
135 (Materialize, None, None) => {
136 let yield_fn = |_start, _work| false;
137 mz_join_core(arranged1, arranged2, result, yield_fn).as_collection()
138 }
139 }
140 }
141}
142
143#[derive(Clone, Copy)]
145struct YieldSpec {
146 after_work: Option<usize>,
148 after_time: Option<Duration>,
150}
151
152impl Default for YieldSpec {
153 fn default() -> Self {
154 Self {
155 after_work: Some(1_000_000),
156 after_time: Some(Duration::from_millis(100)),
157 }
158 }
159}
160
161impl YieldSpec {
162 fn try_from_str(s: &str) -> Option<Self> {
163 let mut after_work = None;
164 let mut after_time = None;
165
166 let options = s.split(',').map(|o| o.trim());
167 for option in options {
168 let mut iter = option.split(':').map(|p| p.trim());
169 match std::array::from_fn(|_| iter.next()) {
170 [Some("work"), Some(amount), None] => {
171 let amount = amount.parse().ok()?;
172 after_work = Some(amount);
173 }
174 [Some("time"), Some(millis), None] => {
175 let millis = millis.parse().ok()?;
176 let duration = Duration::from_millis(millis);
177 after_time = Some(duration);
178 }
179 _ => return None,
180 }
181 }
182
183 Some(Self {
184 after_work,
185 after_time,
186 })
187 }
188}
189
190enum JoinedFlavor<'scope, T: RenderTimestamp> {
192 Collection(VecCollection<'scope, T, Row, Diff>),
194 Local(Arranged<'scope, RowRowAgent<T, Diff>>),
196 Trace(Arranged<'scope, RowRowEnter<mz_repr::Timestamp, Diff, T>>),
198}
199
200impl<'scope, T> Context<'scope, T>
201where
202 T: Lattice + RenderTimestamp,
203{
204 pub(crate) fn render_join(
205 &self,
206 inputs: Vec<CollectionBundle<'scope, T>>,
207 linear_plan: LinearJoinPlan,
208 ) -> CollectionBundle<'scope, T> {
209 self.scope.clone().region_named("Join(Linear)", |inner| {
210 self.render_join_inner(inputs, linear_plan, inner)
211 })
212 }
213
214 fn render_join_inner(
215 &self,
216 inputs: Vec<CollectionBundle<'scope, T>>,
217 linear_plan: LinearJoinPlan,
218 inner: Scope<'_, T>,
219 ) -> CollectionBundle<'scope, T> {
220 let mut errors = Vec::new();
222
223 let arrangement = linear_plan
228 .stage_plans
229 .get(0)
230 .and_then(|stage| inputs[linear_plan.source_relation].arrangement(&stage.stream_key));
231 let mut joined = match (arrangement, linear_plan.initial_closure) {
233 (Some(ArrangementFlavor::Local(oks, errs)), None) => {
234 errors.push(errs.as_collection(|k, _v| k.clone()).enter_region(inner));
235 JoinedFlavor::Local(oks.enter_region(inner))
236 }
237 (Some(ArrangementFlavor::Trace(_gid, oks, errs)), None) => {
238 errors.push(errs.as_collection(|k, _v| k.clone()).enter_region(inner));
239 JoinedFlavor::Trace(oks.enter_region(inner))
240 }
241 (_, initial_closure) => {
242 let (joined, errs) = inputs[linear_plan.source_relation]
245 .as_specific_collection(linear_plan.source_key.as_deref(), &self.config_set);
246 errors.push(errs.enter_region(inner));
247 let mut joined = joined.enter_region(inner);
248
249 if let Some(closure) = initial_closure {
252 let name = "LinearJoinInitialization";
256 type CB<C> = ConsolidatingContainerBuilder<C>;
257 let (j, errs) = joined.flat_map_fallible::<CB<_>, CB<_>, _, _, _, _>(name, {
258 let mut datums = DatumVec::new();
260 move |row| {
261 let mut row_builder = SharedRow::get();
262 let temp_storage = RowArena::new();
263 let mut datums_local = datums.borrow_with(&row);
264 closure
266 .apply(&mut datums_local, &temp_storage, &mut row_builder)
267 .map(|row| row.cloned())
268 .map_err(DataflowErrorSer::from)
269 .transpose()
270 }
271 });
272 joined = j;
273 errors.push(errs);
274 }
275
276 JoinedFlavor::Collection(joined)
277 }
278 };
279
280 for stage_plan in linear_plan.stage_plans.into_iter() {
282 let stream = self.differential_join(
285 joined,
286 inputs[stage_plan.lookup_relation].enter_region(inner),
287 stage_plan,
288 &mut errors,
289 );
290 joined = JoinedFlavor::Collection(stream);
292 }
293
294 let bundle = if let JoinedFlavor::Collection(mut joined) = joined {
298 if let Some(closure) = linear_plan.final_closure {
299 let name = "LinearJoinFinalization";
300 type CB<C> = ConsolidatingContainerBuilder<C>;
301 let (updates, errs) = joined.flat_map_fallible::<CB<_>, CB<_>, _, _, _, _>(name, {
302 let mut datums = DatumVec::new();
304 move |row| {
305 let mut row_builder = SharedRow::get();
306 let temp_storage = RowArena::new();
307 let mut datums_local = datums.borrow_with(&row);
308 closure
310 .apply(&mut datums_local, &temp_storage, &mut row_builder)
311 .map(|row| row.cloned())
312 .map_err(DataflowErrorSer::from)
313 .transpose()
314 }
315 });
316
317 joined = updates;
318 errors.push(errs);
319 }
320
321 CollectionBundle::from_collections(
323 joined,
324 differential_dataflow::collection::concatenate(inner, errors),
325 )
326 } else {
327 panic!("Unexpectedly arranged join output");
328 };
329 bundle.leave_region(self.scope)
330 }
331
332 fn differential_join<'s>(
335 &self,
336 mut joined: JoinedFlavor<'s, T>,
337 lookup_relation: CollectionBundle<'s, T>,
338 LinearStagePlan {
339 stream_key,
340 stream_thinning,
341 lookup_key,
342 closure,
343 lookup_relation: _,
344 }: LinearStagePlan,
345 errors: &mut Vec<VecCollection<'s, T, DataflowErrorSer, Diff>>,
346 ) -> VecCollection<'s, T, Row, Diff> {
347 if let JoinedFlavor::Collection(stream) = joined {
349 let name = "LinearJoinKeyPreparation";
350 let (keyed, errs) = stream
351 .inner
352 .unary_fallible::<ColumnBuilder<((Row, Row), T, Diff)>, _, _, _>(
353 Pipeline,
354 name,
355 |_, _| {
356 Box::new(move |input, ok, errs| {
357 let mut temp_storage = RowArena::new();
358 let mut key_buf = Row::default();
359 let mut val_buf = Row::default();
360 let mut datums = DatumVec::new();
361 input.for_each(|time, data| {
362 let mut ok_session = ok.session_with_builder(&time);
363 let mut err_session = errs.session(&time);
364 for (row, time, diff) in data.iter() {
365 temp_storage.clear();
366 let datums_local = datums.borrow_with(row);
367 let datums = stream_key
368 .iter()
369 .map(|e| e.eval(&datums_local, &temp_storage));
370 let result = key_buf.packer().try_extend(datums);
371 match result {
372 Ok(()) => {
373 val_buf.packer().extend(
374 stream_thinning.iter().map(|e| datums_local[*e]),
375 );
376 ok_session.give(((&key_buf, &val_buf), time, diff));
377 }
378 Err(e) => {
379 err_session.give((e.into(), time.clone(), *diff));
380 }
381 }
382 }
383 });
384 })
385 },
386 );
387
388 errors.push(errs.as_collection());
389
390 let exchange = ExchangeCore::<ColumnBuilder<_>, _>::new_core(
391 columnar_exchange::<Row, Row, T, Diff>,
392 );
393 let arranged = if ENABLE_COLUMN_PAGED_BATCHER.get(&self.config_set) {
394 keyed.mz_arrange_core::<
395 _,
396 batcher::ColumnChunker<_>,
397 Col2ValPagedBatcher<_, _, _, _>,
398 RowRowColPagedBuilder<_, _>,
399 RowRowSpine<_, _>,
400 >(exchange, "JoinStage")
401 } else {
402 keyed.mz_arrange_core::<
403 _,
404 batcher::Chunker<_>,
405 Col2ValBatcher<_, _, _, _>,
406 RowRowBuilder<_, _>,
407 RowRowSpine<_, _>,
408 >(exchange, "JoinStage")
409 };
410 joined = JoinedFlavor::Local(arranged);
411 }
412
413 let arrangement = lookup_relation
415 .arrangement(&lookup_key[..])
416 .expect("Arrangement absent despite explicit construction");
417
418 match joined {
419 JoinedFlavor::Collection(_) => {
420 unreachable!("JoinedFlavor::VecCollection variant avoided at top of method");
421 }
422 JoinedFlavor::Local(local) => match arrangement {
423 ArrangementFlavor::Local(oks, errs1) => {
424 let (oks, errs2) = self
425 .differential_join_inner::<RowRowAgent<_, _>, RowRowAgent<_, _>>(
426 local, oks, closure,
427 );
428
429 errors.push(errs1.as_collection(|k, _v| k.clone()));
430 errors.extend(errs2);
431 oks
432 }
433 ArrangementFlavor::Trace(_gid, oks, errs1) => {
434 let (oks, errs2) = self
435 .differential_join_inner::<RowRowAgent<_, _>, RowRowEnter<_, _, _>>(
436 local, oks, closure,
437 );
438
439 errors.push(errs1.as_collection(|k, _v| k.clone()));
440 errors.extend(errs2);
441 oks
442 }
443 },
444 JoinedFlavor::Trace(trace) => match arrangement {
445 ArrangementFlavor::Local(oks, errs1) => {
446 let (oks, errs2) = self
447 .differential_join_inner::<RowRowEnter<_, _, _>, RowRowAgent<_, _>>(
448 trace, oks, closure,
449 );
450
451 errors.push(errs1.as_collection(|k, _v| k.clone()));
452 errors.extend(errs2);
453 oks
454 }
455 ArrangementFlavor::Trace(_gid, oks, errs1) => {
456 let (oks, errs2) = self
457 .differential_join_inner::<RowRowEnter<_, _, _>, RowRowEnter<_, _, _>>(
458 trace, oks, closure,
459 );
460
461 errors.push(errs1.as_collection(|k, _v| k.clone()));
462 errors.extend(errs2);
463 oks
464 }
465 },
466 }
467 }
468
469 fn differential_join_inner<'s, Tr1, Tr2>(
476 &self,
477 prev_keyed: Arranged<'s, Tr1>,
478 next_input: Arranged<'s, Tr2>,
479 closure: JoinClosure,
480 ) -> (
481 VecCollection<'s, T, Row, Diff>,
482 Option<VecCollection<'s, T, DataflowErrorSer, Diff>>,
483 )
484 where
485 Tr1: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
486 Tr2: TraceReader<Batch: Navigable, Time = T> + Clone + 'static,
487 for<'a> BatchCursor<Tr1>:
488 Cursor<Key<'a>: ExtendDatums, Val<'a>: ExtendDatums, Time = T, Diff = Diff>,
489 for<'a> BatchCursor<Tr2>:
490 Cursor<Key<'a> = BatchKey<'a, Tr1>, Val<'a>: ExtendDatums, Time = T, Diff = Diff>,
491 {
492 let mut datums = DatumVec::new();
494
495 if closure.could_error() {
496 let (oks, err) = self
497 .linear_join_spec
498 .render(prev_keyed, next_input, move |key, old, new| {
499 let mut row_builder = SharedRow::get();
500 let temp_storage = RowArena::new();
501
502 let mut datums_local = datums.borrow();
503 key.extend_datums(&temp_storage, &mut datums_local, None);
504 old.extend_datums(&temp_storage, &mut datums_local, None);
505 new.extend_datums(&temp_storage, &mut datums_local, None);
506
507 closure
508 .apply(&mut datums_local, &temp_storage, &mut row_builder)
509 .map(|row| row.cloned())
510 .map_err(DataflowErrorSer::from)
511 .transpose()
512 })
513 .inner
514 .ok_err(|(x, t, d)| {
515 match x {
517 Ok(x) => Ok((x, t, d)),
518 Err(x) => Err((x, t, d)),
519 }
520 });
521
522 (oks.as_collection(), Some(err.as_collection()))
523 } else {
524 let oks = self
525 .linear_join_spec
526 .render(prev_keyed, next_input, move |key, old, new| {
527 let mut row_builder = SharedRow::get();
528 let temp_storage = RowArena::new();
529
530 let mut datums_local = datums.borrow();
531 key.extend_datums(&temp_storage, &mut datums_local, None);
532 old.extend_datums(&temp_storage, &mut datums_local, None);
533 new.extend_datums(&temp_storage, &mut datums_local, None);
534
535 closure
536 .apply(&mut datums_local, &temp_storage, &mut row_builder)
537 .expect("Closure claimed to never error")
538 .cloned()
539 });
540
541 (oks, None)
542 }
543 }
544}