1use std::fmt::Debug;
73
74use columnar::Index as _;
75use differential_dataflow::difference::{IsZero, Semigroup};
76use differential_dataflow::hashable::Hashable;
77use differential_dataflow::lattice::Lattice;
78use differential_dataflow::logging::Logger;
79use differential_dataflow::operators::arrange::agent::TraceAgent;
80use differential_dataflow::operators::arrange::arrangement::arrange_core;
81use differential_dataflow::trace::{Batcher, Cursor, Description, TraceReader};
82use differential_dataflow::{AsCollection, VecCollection};
83use mz_repr::{Datum, Diff, GlobalId, Row};
84use mz_row_spine::{ValRowColPagedBuilder, ValRowSpine};
85#[cfg(feature = "fuzzing")]
87use mz_row_spine::DatumSeq;
88use mz_storage_types::errors::{DataflowError, EnvelopeError, UpsertError};
89use mz_timely_util::builder_async::{
90 AsyncOutputHandle, Event as AsyncEvent, OperatorBuilder as AsyncOperatorBuilder,
91 PressOnDropButton,
92};
93use mz_timely_util::columnar::batcher::ColumnChunker;
94use mz_timely_util::columnar::builder::ColumnBuilder;
95use mz_timely_util::columnar::merge_batcher::ColumnMergeBatcher;
96use mz_timely_util::columnar::{Col2ValPagedBatcher, Column};
97use mz_timely_util::containers::stack::FueledBuilder;
98use std::convert::Infallible;
99use timely::container::{CapacityContainerBuilder, PushInto};
100use timely::dataflow::StreamVec;
101use timely::dataflow::channels::pact::{Exchange, Pipeline};
102use timely::dataflow::operators::generic::Operator;
103use timely::dataflow::operators::{Capability, CapabilitySet, Exchange as _};
104use timely::order::{PartialOrder, TotalOrder};
105use timely::progress::frontier::AntichainRef;
106use timely::progress::timestamp::Refines;
107use timely::progress::{Antichain, Timestamp};
108
109use crate::healthcheck::HealthStatusUpdate;
110use crate::metrics::upsert::UpsertMetrics;
111use crate::upsert::UpsertKey;
112use crate::upsert::UpsertSourceTime;
113use crate::upsert::UpsertValue;
114
115struct UpsertFeedbackBatcher<T: columnar::Columnar>(Col2ValPagedBatcher<UpsertKey, Row, T, Diff>);
126
127impl<T> Batcher for UpsertFeedbackBatcher<T>
128where
129 T: Timestamp + columnar::Columnar + Default + PartialOrder,
130 for<'a> columnar::Ref<'a, T>: Copy + Ord,
131{
132 type Output = Column<((UpsertKey, Row), T, Diff)>;
133 type Time = T;
134
135 fn new(logger: Option<Logger>, operator_id: usize) -> Self {
136 let mut batcher =
137 <Col2ValPagedBatcher<UpsertKey, Row, T, Diff> as Batcher>::new(logger, operator_id);
138 batcher.set_pager(crate::upsert::upsert_stash_pager::pager());
139 Self(batcher)
140 }
141
142 fn seal(&mut self, upper: Antichain<T>) -> (Vec<Self::Output>, Description<T>) {
143 self.0.seal(upper)
144 }
145
146 fn frontier(&mut self) -> AntichainRef<'_, T> {
147 self.0.frontier()
148 }
149}
150
151impl<T> PushInto<Column<((UpsertKey, Row), T, Diff)>> for UpsertFeedbackBatcher<T>
152where
153 T: Timestamp + columnar::Columnar + Default + PartialOrder,
154 for<'a> columnar::Ref<'a, T>: Copy + Ord,
155{
156 fn push_into(&mut self, chunk: Column<((UpsertKey, Row), T, Diff)>) {
157 self.0.push_into(chunk)
158 }
159}
160
161#[derive(Clone, Debug, Default, columnar::Columnar)]
179#[columnar(derive(PartialEq, Eq, PartialOrd, Ord))]
180struct UpsertDiff<O> {
181 from_time: O,
182 value: Option<Row>,
183}
184
185impl<O> IsZero for UpsertDiff<O> {
186 fn is_zero(&self) -> bool {
187 false
188 }
189}
190
191impl<O: Ord + Clone> Semigroup for UpsertDiff<O> {
192 fn plus_equals(&mut self, rhs: &Self) {
193 if rhs.from_time > self.from_time {
194 *self = rhs.clone();
195 }
196 }
197}
198
199impl<'a, O> Semigroup<columnar::Ref<'a, UpsertDiff<O>>> for UpsertDiff<O>
206where
207 O: columnar::Columnar + Ord + Clone,
208{
209 fn plus_equals(&mut self, rhs: &columnar::Ref<'a, UpsertDiff<O>>) {
210 let rhs_from_time = <O as columnar::Columnar>::into_owned(rhs.from_time);
211 if rhs_from_time > self.from_time {
212 self.from_time = rhs_from_time;
213 self.value = <Option<Row> as columnar::Columnar>::into_owned(rhs.value);
214 }
215 }
216}
217
218fn flush_to_batcher<T, O>(
223 updates: &mut Vec<UpsertUpdate<T, O>>,
224 chunker: &mut UpsertChunker<T, O>,
225 batcher: &mut UpsertBatcher<T, O>,
226) where
227 T: columnar::Columnar + Default + Clone + PartialOrder,
228 for<'a> columnar::Ref<'a, T>: Copy + Ord,
229 O: columnar::Columnar + Default + Ord + Clone,
230 for<'a> columnar::Ref<'a, O>: Ord,
231{
232 use timely::container::{ContainerBuilder as _, PushInto as _};
233 if updates.is_empty() {
234 return;
235 }
236 let mut raw: Column<UpsertUpdate<T, O>> = Default::default();
237 for update in updates.drain(..) {
238 raw.push_into(&update);
239 }
240 chunker.push_into(&mut raw);
241 while let Some(chunk) = chunker.extract() {
242 batcher.push_into(std::mem::take(chunk));
243 }
244}
245
246type UpsertUpdate<T, O> = (UpsertKey, T, UpsertDiff<O>);
257
258type UpsertBatcher<T, O> = ColumnMergeBatcher<UpsertKey, T, UpsertDiff<O>>;
259
260type UpsertChunker<T, O> = ColumnChunker<UpsertUpdate<T, O>>;
263
264type UpsertOutputHandle<T> =
268 AsyncOutputHandle<T, FueledBuilder<CapacityContainerBuilder<Vec<(UpsertValue, T, Diff)>>>>;
269
270#[doc(hidden)]
285pub fn upsert_value_to_row(value: &UpsertValue) -> Row {
286 let mut row = Row::default();
287 let mut packer = row.packer();
288 match value {
289 Ok(ok) => {
290 packer.push(Datum::UInt8(0));
291 packer.extend(ok.iter());
292 }
293 Err(err) => {
294 packer.push(Datum::UInt8(1));
295 let bytes =
296 bincode::serialize(err.as_ref()).expect("UpsertError is serializable via bincode");
297 packer.push(Datum::Bytes(&bytes));
298 }
299 }
300 row
301}
302
303fn upsert_value_byte_len(value: &UpsertValue) -> usize {
306 match value {
307 Ok(row) => row.byte_len(),
308 Err(err) => std::mem::size_of_val(err.as_ref()),
309 }
310}
311
312#[cfg(feature = "fuzzing")]
318#[doc(hidden)]
319pub fn datum_seq_to_upsert_value(seq: DatumSeq<'_>) -> UpsertValue {
320 decode_upsert_value(seq)
321}
322
323fn decode_upsert_value<'a>(mut iter: impl Iterator<Item = Datum<'a>>) -> UpsertValue {
326 let tag = match iter.next() {
327 Some(Datum::UInt8(tag)) => tag,
328 other => panic!("upsert value missing UInt8 tag, got {:?}", other),
329 };
330 match tag {
331 0 => {
332 let mut row = Row::default();
333 row.packer().extend(iter);
334 Ok(row)
335 }
336 1 => {
337 let bytes = match iter.next() {
338 Some(Datum::Bytes(b)) => b,
339 other => panic!("upsert error tag missing Bytes payload, got {:?}", other),
340 };
341 let err: UpsertError =
342 bincode::deserialize(bytes).expect("UpsertError bincode round-trip");
343 Err(Box::new(err))
344 }
345 tag => panic!("unknown upsert value tag {tag}"),
346 }
347}
348
349#[allow(clippy::disallowed_methods)]
361pub fn upsert_inner<'scope, T, FromTime>(
362 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
363 key_indices: Vec<usize>,
364 resume_upper: Antichain<T>,
365 persist_input: VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
366 persist_token: Option<Vec<PressOnDropButton>>,
367 upsert_metrics: UpsertMetrics,
368 source_config: crate::source::SourceExportCreationConfig,
369) -> (
370 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
371 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
372 StreamVec<'scope, T, Infallible>,
373 PressOnDropButton,
374)
375where
376 T: Timestamp + TotalOrder + Sync,
377 T: Refines<mz_repr::Timestamp> + differential_dataflow::lattice::Lattice,
378 T: columnation::Columnation,
379 T: columnar::Columnar + Default,
380 for<'a> columnar::Ref<'a, T>: Copy + Ord,
381 FromTime: Debug + timely::ExchangeData + Clone + Ord + Sync,
382 FromTime: UpsertSourceTime,
383{
384 let persist_keyed = persist_input.flat_map(move |result| {
388 let value = match result {
389 Ok(ok) => Ok(ok),
390 Err(DataflowError::EnvelopeError(err)) => match *err {
391 EnvelopeError::Upsert(err) => Err(Box::new(err)),
392 EnvelopeError::Flat(_) => return None,
393 },
394 Err(_) => return None,
395 };
396 let value_ref = match value {
397 Ok(ref row) => Ok(row),
398 Err(ref err) => Err(&**err),
399 };
400 Some((UpsertKey::from_value(value_ref, &key_indices), value))
401 });
402 let persist_keyed = persist_keyed
403 .inner
404 .exchange(move |((key, _), _, _)| UpsertKey::hashed(key))
407 .as_collection()
408 .inspect(move |((_, row), _, diff)| {
409 source_config
410 .source_statistics
411 .update_records_indexed_by(diff.into_inner());
412 source_config.source_statistics.update_bytes_indexed_by(
413 row.as_ref().map_or(0, |r| r.byte_len().try_into().unwrap()) * diff.into_inner(),
414 );
415 });
416 let encoded = persist_keyed
424 .inner
425 .unary::<ColumnBuilder<((UpsertKey, Row), T, Diff)>, _, _, _>(
426 Pipeline,
427 "Persist feedback encode",
428 |_, _| {
429 move |input, output| {
430 input.for_each(|time, data| {
431 let mut session = output.session_with_builder(&time);
432 for ((key, value), ts, diff) in data.drain(..) {
433 let row = upsert_value_to_row(&value);
434 session.give(((&key, &row), &ts, &diff));
435 }
436 });
437 }
438 },
439 );
440 let persist_arranged = arrange_core::<
441 _,
442 _,
443 ColumnChunker<((UpsertKey, Row), T, Diff)>,
444 UpsertFeedbackBatcher<T>,
445 ValRowColPagedBuilder<UpsertKey, T, Diff>,
446 ValRowSpine<UpsertKey, T, Diff>,
447 >(encoded, Pipeline, "Persist feedback");
448 let mut persist_trace = persist_arranged.trace.clone();
449
450 use timely::dataflow::operators::Probe;
454 let (persist_probe, _persist_probe_stream) = persist_arranged.stream.probe();
455
456 let mut builder = AsyncOperatorBuilder::new("Upsert V2".to_string(), input.scope());
458
459 let (output_handle, output) = builder
460 .new_output::<FueledBuilder<CapacityContainerBuilder<Vec<(UpsertValue, T, Diff)>>>>();
461 let (_snapshot_handle, snapshot_stream) =
462 builder.new_output::<CapacityContainerBuilder<Vec<Infallible>>>();
463 let (_health_output, health_stream) = builder
464 .new_output::<CapacityContainerBuilder<Vec<(Option<GlobalId>, HealthStatusUpdate)>>>();
465
466 let mut input = builder.new_input_for(
467 input.inner,
468 Exchange::new(move |((key, _, _), _, _)| UpsertKey::hashed(key)),
469 &output_handle,
470 );
471
472 let mut persist_wakeup = builder.new_disconnected_input(_persist_probe_stream, Pipeline);
476
477 let shutdown_button = builder.build(move |caps| async move {
478 let _persist_token = persist_token;
481
482 let [output_cap, snapshot_cap, _health_cap]: [_; 3] = caps.try_into().unwrap();
483 drop(output_cap);
484 let mut snapshot_cap = CapabilitySet::from_elem(snapshot_cap);
485
486 let mut hydrating = true;
487
488 let mut batcher: UpsertBatcher<T, FromTime::Order> = Batcher::new(None, 0);
501 batcher.set_pager(crate::upsert::upsert_stash_pager::pager());
502 let mut chunker: UpsertChunker<T, FromTime::Order> = Default::default();
505 let mut push_buffer: Vec<UpsertUpdate<T, FromTime::Order>> = Vec::new();
508
509 let mut stash_cap: Option<Capability<T>> = None;
513 let mut input_upper = Antichain::from_elem(Timestamp::minimum());
514
515 let snapshot_start = std::time::Instant::now();
516 let mut prev_persist_upper = Antichain::from_elem(Timestamp::minimum());
517
518 let mut rehydration_total: u64 = 0;
520 let mut rehydration_updates: u64 = 0;
521
522 loop {
528 tokio::select! {
530 _ = input.ready() => {}
531 _ = persist_wakeup.ready() => {
532 while persist_wakeup.next_sync().is_some() {}
533 }
534 }
535
536 while let Some(event) = input.next_sync() {
541 match event {
542 AsyncEvent::Data(cap, data) => {
543 let mut pushed_any = false;
544 for ((key, value, from_time), ts, diff) in data {
545 assert!(diff.is_positive(), "invalid upsert input");
546 if PartialOrder::less_equal(&input_upper, &resume_upper)
547 && !resume_upper.less_equal(&ts)
548 {
549 continue;
550 }
551 let value = value.as_ref().map(upsert_value_to_row);
552 let from_time = from_time.upsert_order();
553 push_buffer.push((key, ts, UpsertDiff { from_time, value }));
554 pushed_any = true;
555 }
556 if pushed_any {
559 stash_cap = Some(match stash_cap {
560 Some(prev) if cap.time() < prev.time() => cap,
561 Some(prev) => prev,
562 None => cap,
563 });
564 }
565 }
566 AsyncEvent::Progress(upper) => {
567 if PartialOrder::less_than(&upper, &resume_upper) {
568 continue;
569 }
570 input_upper = upper;
571 }
572 }
573 }
574
575 flush_to_batcher(&mut push_buffer, &mut chunker, &mut batcher);
579
580 let persist_upper = persist_probe.with_frontier(|f| f.to_owned());
589
590 if persist_upper != prev_persist_upper {
591 let last_rehydration_chunk =
592 hydrating && PartialOrder::less_equal(&resume_upper, &persist_upper);
593
594 if last_rehydration_chunk {
595 hydrating = false;
596 upsert_metrics
597 .rehydration_latency
598 .set(snapshot_start.elapsed().as_secs_f64());
599 upsert_metrics.rehydration_total.set(rehydration_total);
600 upsert_metrics.rehydration_updates.set(rehydration_updates);
601 tracing::info!(
602 worker_id = %source_config.worker_id,
603 source_id = %source_config.id,
604 "upsert finished rehydration",
605 );
606 snapshot_cap.downgrade(&[]);
607 }
608
609 let _ = snapshot_cap.try_downgrade(persist_upper.iter());
610
611 persist_trace.set_logical_compaction(persist_upper.borrow());
613 persist_trace.set_physical_compaction(persist_upper.borrow());
614
615 prev_persist_upper = persist_upper.clone();
616 }
617
618 if let Some(cap) = stash_cap.as_mut()
655 && !persist_upper.less_than(cap.time())
656 && PartialOrder::less_than(&persist_upper, &input_upper)
657 {
658 let (sealed, _description) = batcher.seal(input_upper.clone());
662 let remaining_frontier = batcher.frontier().to_owned();
664
665 let mut ineligible = Vec::new();
666 let drain_stats = drain_sealed_input(
670 sealed,
671 &mut ineligible,
672 &output_handle,
673 &*cap,
674 &persist_upper,
675 &mut persist_trace,
676 &source_config.worker_id,
677 &source_config.id,
678 )
679 .await;
680
681 upsert_metrics.multi_get_size.inc_by(drain_stats.eligible);
682 upsert_metrics
683 .multi_get_result_count
684 .inc_by(drain_stats.result_count);
685 upsert_metrics
686 .multi_put_size
687 .inc_by(drain_stats.output_count);
688 upsert_metrics.upsert_inserts.inc_by(drain_stats.inserts);
689 upsert_metrics.upsert_updates.inc_by(drain_stats.updates);
690 upsert_metrics.upsert_deletes.inc_by(drain_stats.deletes);
691
692 if hydrating {
693 rehydration_total += drain_stats.inserts;
694 rehydration_updates += drain_stats.eligible;
695 }
696
697 let min_ineligible_ts = ineligible.iter().map(|(_, ts, _)| ts).min().cloned();
702 flush_to_batcher(&mut ineligible, &mut chunker, &mut batcher);
703
704 let has_remaining = !remaining_frontier.is_empty() || min_ineligible_ts.is_some();
705 if has_remaining {
706 let min_ts = match (
707 remaining_frontier.elements().first(),
708 min_ineligible_ts.as_ref(),
709 ) {
710 (Some(a), Some(b)) => std::cmp::min(a, b).clone(),
711 (Some(a), None) => a.clone(),
712 (None, Some(b)) => b.clone(),
713 (None, None) => unreachable!(),
714 };
715 cap.downgrade(&min_ts);
716 } else {
717 stash_cap = None;
720 }
721 }
722
723 if input_upper.is_empty() {
724 break;
725 }
726 }
727 });
728
729 (
730 output
731 .as_collection()
732 .map(|result: UpsertValue| match result {
733 Ok(ok) => Ok(ok),
734 Err(err) => Err(DataflowError::from(EnvelopeError::Upsert(*err))),
735 }),
736 health_stream,
737 snapshot_stream,
738 shutdown_button.press_on_drop(),
739 )
740}
741
742struct DrainStats {
744 eligible: u64,
746 result_count: u64,
748 inserts: u64,
750 updates: u64,
752 deletes: u64,
754 output_count: u64,
756}
757
758async fn drain_sealed_input<T, O>(
767 sealed: Vec<Column<UpsertUpdate<T, O>>>,
768 ineligible: &mut Vec<UpsertUpdate<T, O>>,
769 output_handle: &UpsertOutputHandle<T>,
770 output_cap: &Capability<T>,
771 persist_upper: &Antichain<T>,
772 trace: &mut TraceAgent<ValRowSpine<UpsertKey, T, Diff>>,
773 worker_id: &usize,
774 source_id: &GlobalId,
775) -> DrainStats
776where
777 T: TotalOrder + Lattice + timely::ExchangeData + Timestamp + Clone + Debug + Ord + Sync,
778 T: columnation::Columnation + columnar::Columnar,
779 O: columnar::Columnar,
780{
781 let mut eligible_count: u64 = 0;
802 let mut result_count: u64 = 0;
803 let mut output_count: u64 = 0;
804 let mut inserts: u64 = 0;
805 let mut updates: u64 = 0;
806 let mut deletes: u64 = 0;
807
808 let (mut cursor, storage) = trace.cursor();
809
810 for chunk in &sealed {
811 for (key, ts, diff) in chunk.borrow().into_index_iter() {
812 let ts = <T as columnar::Columnar>::into_owned(ts);
813 if !persist_upper.less_equal(&ts) {
814 continue;
816 }
817 if persist_upper.less_than(&ts) {
818 ineligible.push((
820 *key,
821 ts,
822 <UpsertDiff<O> as columnar::Columnar>::into_owned(diff),
823 ));
824 continue;
825 }
826
827 eligible_count += 1;
832 cursor.seek_key(&storage, key);
833 let old_value = match cursor.get_key(&storage) {
834 Some(found) if found == key => {
835 let mut result = None;
836 while let Some(val) = cursor.get_val(&storage) {
837 let mut count = Diff::ZERO;
838 cursor.map_times(&storage, |_time, d| {
839 count += d.clone();
840 });
841 if count.is_positive() {
842 assert!(
843 count == 1.into(),
844 "unexpected multiple entries for the same key in persist trace"
845 );
846 assert!(
847 result.is_none(),
848 "unexpected multiple values for the same key in persist trace"
849 );
850 result = Some(decode_upsert_value(val));
851 }
852 cursor.step_val(&storage);
853 }
854 result
855 }
856 _ => None,
857 };
858
859 if old_value.is_some() {
860 result_count += 1;
861 }
862
863 match diff.value {
864 Some(row) => {
865 if let Some(old_val) = old_value {
866 let size = upsert_value_byte_len(&old_val);
867 output_handle
868 .give_fueled(output_cap, (old_val, ts.clone(), Diff::MINUS_ONE), size)
869 .await;
870 output_count += 1;
871 updates += 1;
872 } else {
873 inserts += 1;
874 }
875 let new_val = decode_upsert_value(row.iter());
876 let size = upsert_value_byte_len(&new_val);
877 output_handle
878 .give_fueled(output_cap, (new_val, ts, Diff::ONE), size)
879 .await;
880 output_count += 1;
881 }
882 None => {
883 if let Some(old_val) = old_value {
884 let size = upsert_value_byte_len(&old_val);
885 output_handle
886 .give_fueled(output_cap, (old_val, ts, Diff::MINUS_ONE), size)
887 .await;
888 output_count += 1;
889 deletes += 1;
890 }
891 }
892 }
893 }
894 }
895
896 tracing::debug!(
897 worker_id = %worker_id,
898 source_id = %source_id,
899 ineligible = ineligible.len(),
900 eligible = eligible_count,
901 "drained stash",
902 );
903
904 DrainStats {
905 eligible: eligible_count,
906 result_count,
907 inserts,
908 updates,
909 deletes,
910 output_count,
911 }
912}
913
914#[cfg(test)]
915mod test {
916 use mz_ore::metrics::MetricsRegistry;
917 use mz_persist_types::ShardId;
918 use mz_repr::{Datum, Timestamp as MzTimestamp};
919 use mz_storage_operators::persist_source::Subtime;
920 use mz_storage_types::sources::SourceEnvelope;
921 use mz_storage_types::sources::envelope::{KeyEnvelope, UpsertEnvelope, UpsertStyle};
922 use timely::dataflow::operators::capture::Extract;
923 use timely::dataflow::operators::{Capture, Input};
924 use timely::progress::Timestamp;
925
926 use crate::metrics::StorageMetrics;
927 use crate::metrics::upsert::UpsertMetricDefs;
928 use crate::source::SourceExportCreationConfig;
929 use crate::statistics::{SourceStatistics, SourceStatisticsMetricDefs};
930
931 use super::*;
932
933 impl UpsertSourceTime for i32 {
936 type Order = i32;
937 fn upsert_order(&self) -> i32 {
938 *self
939 }
940 }
941
942 type Ts = (MzTimestamp, Subtime);
943
944 fn new_ts(ts: u64) -> Ts {
945 (MzTimestamp::new(ts), Subtime::minimum())
946 }
947
948 fn key(k: i64) -> UpsertKey {
949 UpsertKey::from_key(Ok(&Row::pack_slice(&[Datum::Int64(k)])))
950 }
951
952 fn row(k: i64, v: i64) -> Row {
953 Row::pack_slice(&[Datum::Int64(k), Datum::Int64(v)])
954 }
955
956 macro_rules! upsert_test {
957 (|$input:ident, $persist:ident, $worker:ident| $body:block) => {{
958 let output_handle = timely::execute_directly(move |$worker| {
959 let (mut $input, mut $persist, output_handle) = $worker
960 .dataflow::<MzTimestamp, _, _>(|scope| {
961 scope.scoped::<Ts, _, _>("upsert", |scope| {
962 let (input_handle, input) = scope.new_input();
963 let (persist_handle, persist_input) = scope.new_input();
964 let source_id = GlobalId::User(0);
965
966 let reg = MetricsRegistry::new();
967 let upsert_defs = UpsertMetricDefs::register_with(®);
968 let upsert_metrics =
969 UpsertMetrics::new(&upsert_defs, source_id, 0, None);
970
971 let reg2 = MetricsRegistry::new();
972 let storage_metrics = StorageMetrics::register_with(®2);
973
974 let reg3 = MetricsRegistry::new();
975 let stats_defs =
976 SourceStatisticsMetricDefs::register_with(®3);
977 let envelope = SourceEnvelope::Upsert(UpsertEnvelope {
978 source_arity: 2,
979 style: UpsertStyle::Default(KeyEnvelope::Flattened),
980 key_indices: vec![0],
981 });
982 let source_statistics = SourceStatistics::new(
983 source_id, 0, &stats_defs, source_id, &ShardId::new(),
984 envelope, Antichain::from_elem(Timestamp::minimum()),
985 );
986 let source_config = SourceExportCreationConfig {
987 id: source_id,
988 worker_id: 0,
989 metrics: storage_metrics,
990 source_statistics,
991 };
992
993 let (output, _, _, button) = upsert_inner(
994 input.as_collection(),
995 vec![0],
996 Antichain::from_elem(Timestamp::minimum()),
997 persist_input.as_collection(),
998 None,
999 upsert_metrics,
1000 source_config,
1001 );
1002 std::mem::forget(button);
1003 (input_handle, persist_handle, output.inner.capture())
1004 })
1005 });
1006
1007 $body
1008
1009 output_handle
1010 });
1011
1012 let mut actual: Vec<_> = output_handle
1013 .extract()
1014 .into_iter()
1015 .flat_map(|(_cap, container)| container)
1016 .collect();
1017 differential_dataflow::consolidation::consolidate_updates(&mut actual);
1018 actual
1019 }};
1020 }
1021
1022 #[mz_ore::test]
1023 #[cfg_attr(miri, ignore)]
1024 fn gh_9160_repro() {
1025 let actual = upsert_test!(|input, persist, worker| {
1026 let key0 = key(0);
1027 let key1 = key(1);
1028 let value1 = row(0, 0);
1029 let value3 = row(0, 1);
1030 let value4 = row(0, 2);
1031
1032 input.send(((key0, Some(Ok(value1.clone())), 1), new_ts(0), Diff::ONE));
1033 input.advance_to(new_ts(2));
1034 worker.step();
1035
1036 persist.send((Ok(value1), new_ts(0), Diff::ONE));
1037 persist.advance_to(new_ts(1));
1038 worker.step();
1039
1040 input.send_batch(&mut vec![
1041 ((key1, None, 2), new_ts(2), Diff::ONE),
1042 ((key0, Some(Ok(value3)), 3), new_ts(3), Diff::ONE),
1043 ]);
1044 input.advance_to(new_ts(3));
1045 input.send_batch(&mut vec![(
1046 (key0, Some(Ok(value4)), 4),
1047 new_ts(3),
1048 Diff::ONE,
1049 )]);
1050 input.advance_to(new_ts(4));
1051 worker.step();
1052
1053 persist.advance_to(new_ts(3));
1054 worker.step();
1055 });
1056
1057 let value1 = row(0, 0);
1058 let value4 = row(0, 2);
1059 let expected: Vec<(Result<Row, DataflowError>, _, _)> = vec![
1060 (Ok(value1.clone()), new_ts(0), Diff::ONE),
1061 (Ok(value1), new_ts(3), Diff::MINUS_ONE),
1062 (Ok(value4), new_ts(3), Diff::ONE),
1063 ];
1064 assert_eq!(actual, expected);
1065 }
1066
1067 #[mz_ore::test]
1068 #[cfg_attr(miri, ignore)]
1069 fn out_of_order_keys_across_timestamps() {
1070 let actual = upsert_test!(|input, persist, worker| {
1071 let key_high = key(99);
1072 let key_low = key(1);
1073 let val_a = row(99, 1);
1074 let val_b = row(1, 2);
1075
1076 input.send(((key_high, Some(Ok(val_a.clone())), 1), new_ts(0), Diff::ONE));
1077 input.advance_to(new_ts(1));
1078 worker.step();
1079 persist.send((Ok(val_a.clone()), new_ts(0), Diff::ONE));
1080 persist.advance_to(new_ts(1));
1081 worker.step();
1082
1083 input.send(((key_low, Some(Ok(val_b.clone())), 2), new_ts(1), Diff::ONE));
1084 input.advance_to(new_ts(2));
1085 worker.step();
1086 persist.send((Ok(val_b.clone()), new_ts(1), Diff::ONE));
1087 persist.advance_to(new_ts(2));
1088 worker.step();
1089
1090 let val_a2 = row(99, 10);
1091 let val_b2 = row(1, 20);
1092 input.send_batch(&mut vec![
1093 (
1094 (key_high, Some(Ok(val_a2.clone())), 3),
1095 new_ts(2),
1096 Diff::ONE,
1097 ),
1098 ((key_low, Some(Ok(val_b2.clone())), 4), new_ts(2), Diff::ONE),
1099 ]);
1100 input.advance_to(new_ts(3));
1101 worker.step();
1102 persist.advance_to(new_ts(3));
1103 worker.step();
1104 });
1105
1106 let val_a = row(99, 1);
1107 let val_b = row(1, 2);
1108 let val_a2 = row(99, 10);
1109 let val_b2 = row(1, 20);
1110 let expected: Vec<(Result<Row, DataflowError>, _, _)> = vec![
1111 (Ok(val_b.clone()), new_ts(1), Diff::ONE),
1112 (Ok(val_b), new_ts(2), Diff::MINUS_ONE),
1113 (Ok(val_b2), new_ts(2), Diff::ONE),
1114 (Ok(val_a.clone()), new_ts(0), Diff::ONE),
1115 (Ok(val_a), new_ts(2), Diff::MINUS_ONE),
1116 (Ok(val_a2), new_ts(2), Diff::ONE),
1117 ];
1118 let mut actual_sorted = actual;
1119 let mut expected_sorted = expected;
1120 actual_sorted.sort();
1121 expected_sorted.sort();
1122 assert_eq!(actual_sorted, expected_sorted);
1123 }
1124
1125 #[mz_ore::test]
1126 #[cfg_attr(miri, ignore)]
1127 fn rehydration_then_update() {
1128 let actual = upsert_test!(|input, persist, worker| {
1129 let k = key(42);
1130 let old_val = row(42, 100);
1131 let new_val = row(42, 200);
1132
1133 persist.send((Ok(old_val), new_ts(0), Diff::ONE));
1134 persist.advance_to(new_ts(1));
1135 worker.step();
1136
1137 input.send(((k, Some(Ok(new_val)), 1), new_ts(1), Diff::ONE));
1138 input.advance_to(new_ts(2));
1139 worker.step();
1140 persist.advance_to(new_ts(2));
1141 worker.step();
1142 });
1143
1144 let old_val = row(42, 100);
1145 let new_val = row(42, 200);
1146 let expected: Vec<(Result<Row, DataflowError>, _, _)> = vec![
1147 (Ok(old_val), new_ts(1), Diff::MINUS_ONE),
1148 (Ok(new_val), new_ts(1), Diff::ONE),
1149 ];
1150 assert_eq!(actual, expected);
1151 }
1152
1153 #[mz_ore::test]
1154 #[cfg_attr(miri, ignore)]
1155 fn delete_existing_key() {
1156 let actual = upsert_test!(|input, persist, worker| {
1157 let k = key(7);
1158 let val = row(7, 77);
1159
1160 input.send(((k, Some(Ok(val.clone())), 1), new_ts(0), Diff::ONE));
1161 input.advance_to(new_ts(1));
1162 worker.step();
1163 persist.send((Ok(val), new_ts(0), Diff::ONE));
1164 persist.advance_to(new_ts(1));
1165 worker.step();
1166
1167 input.send(((k, None, 2), new_ts(1), Diff::ONE));
1168 input.advance_to(new_ts(2));
1169 worker.step();
1170 persist.advance_to(new_ts(2));
1171 worker.step();
1172 });
1173
1174 let val = row(7, 77);
1175 let expected: Vec<(Result<Row, DataflowError>, _, _)> = vec![
1176 (Ok(val.clone()), new_ts(0), Diff::ONE),
1177 (Ok(val), new_ts(1), Diff::MINUS_ONE),
1178 ];
1179 assert_eq!(actual, expected);
1180 }
1181
1182 #[mz_ore::test]
1183 #[cfg_attr(miri, ignore)]
1184 fn multi_batch_rehydration() {
1185 let actual = upsert_test!(|input, persist, worker| {
1186 let k = key(5);
1187 let old_val = row(5, 10);
1188 let new_val = row(5, 20);
1189 let updated_val = row(5, 30);
1190
1191 persist.send((Ok(old_val.clone()), new_ts(0), Diff::ONE));
1192 persist.send((Ok(old_val), new_ts(0), Diff::MINUS_ONE));
1193 persist.send((Ok(new_val), new_ts(0), Diff::ONE));
1194 persist.advance_to(new_ts(1));
1195 worker.step();
1196
1197 input.send(((k, Some(Ok(updated_val)), 1), new_ts(1), Diff::ONE));
1198 input.advance_to(new_ts(2));
1199 worker.step();
1200 persist.advance_to(new_ts(2));
1201 worker.step();
1202 });
1203
1204 let new_val = row(5, 20);
1205 let updated_val = row(5, 30);
1206 let expected: Vec<(Result<Row, DataflowError>, _, _)> = vec![
1207 (Ok(new_val), new_ts(1), Diff::MINUS_ONE),
1208 (Ok(updated_val), new_ts(1), Diff::ONE),
1209 ];
1210 assert_eq!(actual, expected);
1211 }
1212
1213 #[mz_ore::test]
1214 #[cfg_attr(miri, ignore)]
1215 fn delete_nonexistent_key() {
1216 let actual = upsert_test!(|input, persist, worker| {
1217 let k = key(99);
1218
1219 persist.advance_to(new_ts(1));
1220 worker.step();
1221
1222 input.send(((k, None, 1), new_ts(1), Diff::ONE));
1223 input.advance_to(new_ts(2));
1224 worker.step();
1225 persist.advance_to(new_ts(2));
1226 worker.step();
1227 });
1228
1229 assert!(actual.is_empty(), "expected empty output, got: {actual:?}");
1230 }
1231
1232 #[mz_ore::test]
1233 #[cfg_attr(miri, ignore)]
1234 fn reinsert_after_delete() {
1235 let actual = upsert_test!(|input, persist, worker| {
1236 let k = key(3);
1237 let val_a = row(3, 10);
1238 let val_b = row(3, 20);
1239
1240 input.send(((k, Some(Ok(val_a.clone())), 1), new_ts(0), Diff::ONE));
1241 input.advance_to(new_ts(1));
1242 worker.step();
1243 persist.send((Ok(val_a.clone()), new_ts(0), Diff::ONE));
1244 persist.advance_to(new_ts(1));
1245 worker.step();
1246
1247 input.send(((k, None, 2), new_ts(1), Diff::ONE));
1248 input.advance_to(new_ts(2));
1249 worker.step();
1250 persist.send((Ok(val_a), new_ts(1), Diff::MINUS_ONE));
1251 persist.advance_to(new_ts(2));
1252 worker.step();
1253
1254 input.send(((k, Some(Ok(val_b.clone())), 3), new_ts(2), Diff::ONE));
1255 input.advance_to(new_ts(3));
1256 worker.step();
1257 persist.advance_to(new_ts(3));
1258 worker.step();
1259 });
1260
1261 let val_a = row(3, 10);
1262 let val_b = row(3, 20);
1263 let mut expected: Vec<(Result<Row, DataflowError>, _, _)> = vec![
1264 (Ok(val_a.clone()), new_ts(0), Diff::ONE),
1265 (Ok(val_a), new_ts(1), Diff::MINUS_ONE),
1266 (Ok(val_b), new_ts(2), Diff::ONE),
1267 ];
1268 expected.sort();
1269 let mut actual = actual;
1270 actual.sort();
1271 assert_eq!(actual, expected);
1272 }
1273
1274 #[mz_ore::test]
1275 #[cfg_attr(miri, ignore)]
1276 fn idempotent_update() {
1277 let actual = upsert_test!(|input, persist, worker| {
1278 let k = key(11);
1279 let val = row(11, 50);
1280
1281 input.send(((k, Some(Ok(val.clone())), 1), new_ts(0), Diff::ONE));
1282 input.advance_to(new_ts(1));
1283 worker.step();
1284 persist.send((Ok(val.clone()), new_ts(0), Diff::ONE));
1285 persist.advance_to(new_ts(1));
1286 worker.step();
1287
1288 input.send(((k, Some(Ok(val.clone())), 2), new_ts(1), Diff::ONE));
1289 input.advance_to(new_ts(2));
1290 worker.step();
1291 persist.advance_to(new_ts(2));
1292 worker.step();
1293 });
1294
1295 let val = row(11, 50);
1296 let expected: Vec<(Result<Row, DataflowError>, _, _)> =
1297 vec![(Ok(val), new_ts(0), Diff::ONE)];
1298 assert_eq!(actual, expected);
1299 }
1300
1301 #[mz_ore::test]
1318 #[cfg_attr(miri, ignore)]
1319 fn lagging_replacement_below_upper_strands_data() {
1320 let (frontier, emitted) = run_below_upper_scenario_v2();
1321
1322 assert!(
1326 emitted.is_empty(),
1327 "below-upper data should be dropped, not emitted; got {emitted:?}"
1328 );
1329 assert_eq!(
1330 frontier,
1331 vec![new_ts(11)],
1332 "v2 output frontier should advance to the input upper, not pin below \
1333 persist_upper"
1334 );
1335 assert!(
1336 frontier[0] >= new_ts(10),
1337 "v2 output frontier {frontier:?} should reach at least persist_upper (10)"
1338 );
1339 }
1340
1341 fn run_below_upper_scenario_v2() -> (Vec<Ts>, Vec<(Result<Row, DataflowError>, Ts, Diff)>) {
1344 use timely::dataflow::operators::Probe;
1345
1346 let (frontier, capture) = timely::execute_directly(move |worker| {
1347 let (mut input, mut persist, probe, capture) =
1348 worker.dataflow::<MzTimestamp, _, _>(|scope| {
1349 scope.scoped::<Ts, _, _>("upsert", |scope| {
1350 let (input_handle, input) = scope.new_input();
1351 let (persist_handle, persist_input) = scope.new_input();
1352 let source_id = GlobalId::User(0);
1353
1354 let reg = MetricsRegistry::new();
1355 let upsert_defs = UpsertMetricDefs::register_with(®);
1356 let upsert_metrics = UpsertMetrics::new(&upsert_defs, source_id, 0, None);
1357
1358 let reg2 = MetricsRegistry::new();
1359 let storage_metrics = StorageMetrics::register_with(®2);
1360
1361 let reg3 = MetricsRegistry::new();
1362 let stats_defs = SourceStatisticsMetricDefs::register_with(®3);
1363 let envelope = SourceEnvelope::Upsert(UpsertEnvelope {
1364 source_arity: 2,
1365 style: UpsertStyle::Default(KeyEnvelope::Flattened),
1366 key_indices: vec![0],
1367 });
1368 let source_statistics = SourceStatistics::new(
1369 source_id,
1370 0,
1371 &stats_defs,
1372 source_id,
1373 &ShardId::new(),
1374 envelope,
1375 Antichain::from_elem(Timestamp::minimum()),
1376 );
1377 let source_config = SourceExportCreationConfig {
1378 id: source_id,
1379 worker_id: 0,
1380 metrics: storage_metrics,
1381 source_statistics,
1382 };
1383
1384 let (output, _, _, button) = upsert_inner(
1385 input.as_collection(),
1386 vec![0],
1387 Antichain::from_elem(Timestamp::minimum()),
1388 persist_input.as_collection(),
1389 None,
1390 upsert_metrics,
1391 source_config,
1392 );
1393 std::mem::forget(button);
1394 let (probe, stream) = output.inner.probe();
1395 (input_handle, persist_handle, probe, stream.capture())
1396 })
1397 });
1398
1399 persist.advance_to(new_ts(10));
1402 for _ in 0..20 {
1403 worker.step();
1404 }
1405
1406 input.send(((key(0), Some(Ok(row(0, 1))), 1), new_ts(5), Diff::ONE));
1409 input.send(((key(1), Some(Ok(row(1, 2))), 2), new_ts(7), Diff::ONE));
1410 input.advance_to(new_ts(11));
1411 for _ in 0..20 {
1412 worker.step();
1413 }
1414
1415 (probe.with_frontier(|f| f.to_vec()), capture)
1416 });
1417
1418 let mut emitted: Vec<_> = capture
1419 .extract()
1420 .into_iter()
1421 .flat_map(|(_cap, c)| c)
1422 .collect();
1423 differential_dataflow::consolidation::consolidate_updates(&mut emitted);
1424 (frontier, emitted)
1425 }
1426}