1use std::cell::RefCell;
11use std::cmp::Reverse;
12use std::convert::AsRef;
13use std::fmt::Debug;
14use std::hash::{Hash, Hasher};
15use std::path::PathBuf;
16use std::sync::Arc;
17
18use differential_dataflow::hashable::Hashable;
19use differential_dataflow::{AsCollection, VecCollection};
20use futures::StreamExt;
21use futures::future::FutureExt;
22use indexmap::map::Entry;
23use itertools::Itertools;
24use mz_ore::error::ErrorExt;
25use mz_repr::{Datum, DatumVec, Diff, GlobalId, Row};
26use mz_rocksdb::ValueIterator;
27use mz_sql_server_util::cdc::Lsn;
28use mz_storage_operators::metrics::BackpressureMetrics;
29use mz_storage_types::configuration::StorageConfiguration;
30use mz_storage_types::dyncfgs;
31use mz_storage_types::errors::{DataflowError, EnvelopeError, UpsertError};
32use mz_storage_types::sources::MzOffset;
33use mz_storage_types::sources::envelope::UpsertEnvelope;
34use mz_storage_types::sources::kafka::{KafkaTimestamp, RangeBound};
35use mz_storage_types::sources::mysql::GtidPartition;
36use mz_timely_util::builder_async::{
37 AsyncOutputHandle, Event as AsyncEvent, OperatorBuilder as AsyncOperatorBuilder,
38 PressOnDropButton,
39};
40use serde::{Deserialize, Serialize};
41use sha2::{Digest, Sha256};
42use timely::dataflow::channels::pact::Exchange;
43use timely::dataflow::operators::{Capability, InputCapability, Operator};
44use timely::dataflow::{Scope, StreamVec};
45use timely::order::{PartialOrder, TotalOrder};
46use timely::progress::timestamp::Refines;
47use timely::progress::{Antichain, Timestamp};
48
49use crate::healthcheck::HealthStatusUpdate;
50use crate::metrics::upsert::UpsertMetrics;
51use crate::storage_state::StorageInstanceContext;
52use crate::{upsert_continual_feedback, upsert_continual_feedback_v2};
53use types::{
54 BincodeOpts, StateValue, UpsertState, UpsertStateBackend, consolidating_merge_function,
55 upsert_bincode_opts,
56};
57
58#[cfg(any(test, feature = "fuzzing"))]
59pub mod memory;
60pub(crate) mod rocksdb;
61pub(crate) mod types;
63
64pub type UpsertValue = Result<Row, Box<UpsertError>>;
65
66#[derive(
67 Copy,
68 Clone,
69 Hash,
70 PartialEq,
71 Eq,
72 PartialOrd,
73 Ord,
74 Serialize,
75 Deserialize,
76 bytemuck::AnyBitPattern,
77 bytemuck::NoUninit
78)]
79#[repr(transparent)]
80pub struct UpsertKey([u8; 32]);
81
82impl columnation::Columnation for UpsertKey {
83 type InnerRegion = columnation::CopyRegion<UpsertKey>;
84}
85
86mod columnar_upsert_key {
102 use super::UpsertKey;
103 use columnar::Columnar;
104 use mz_ore::cast::CastFrom;
105 use std::ops::Range;
106
107 #[derive(Clone, Copy, Default, Debug)]
109 pub struct UpsertKeys<T>(T);
110 impl<D, T: columnar::Push<D>> columnar::Push<D> for UpsertKeys<T> {
111 #[inline(always)]
112 fn push(&mut self, item: D) {
113 self.0.push(item)
114 }
115 }
116 impl<T: columnar::Clear> columnar::Clear for UpsertKeys<T> {
117 #[inline(always)]
118 fn clear(&mut self) {
119 self.0.clear()
120 }
121 }
122 impl<T: columnar::Len> columnar::Len for UpsertKeys<T> {
123 #[inline(always)]
124 fn len(&self) -> usize {
125 self.0.len()
126 }
127 }
128 impl<'a> columnar::Index for UpsertKeys<&'a [UpsertKey]> {
129 type Ref = &'a UpsertKey;
130
131 #[inline(always)]
132 fn get(&self, index: usize) -> Self::Ref {
133 &self.0[index]
134 }
135 }
136
137 impl Columnar for UpsertKey {
138 #[inline(always)]
139 fn into_owned<'a>(other: columnar::Ref<'a, Self>) -> Self {
140 *other
141 }
142 type Container = UpsertKeys<Vec<UpsertKey>>;
143 #[inline(always)]
144 fn reborrow<'b, 'a: 'b>(thing: columnar::Ref<'a, Self>) -> columnar::Ref<'b, Self>
145 where
146 Self: 'a,
147 {
148 thing
149 }
150 }
151
152 impl columnar::Borrow for UpsertKeys<Vec<UpsertKey>> {
153 type Ref<'a> = &'a UpsertKey;
154 type Borrowed<'a>
155 = UpsertKeys<&'a [UpsertKey]>
156 where
157 Self: 'a;
158 #[inline(always)]
159 fn borrow<'a>(&'a self) -> Self::Borrowed<'a> {
160 UpsertKeys(self.0.as_slice())
161 }
162 #[inline(always)]
163 fn reborrow<'b, 'a: 'b>(item: Self::Borrowed<'a>) -> Self::Borrowed<'b>
164 where
165 Self: 'a,
166 {
167 UpsertKeys(item.0)
168 }
169 #[inline(always)]
170 fn reborrow_ref<'b, 'a: 'b>(item: Self::Ref<'a>) -> Self::Ref<'b>
171 where
172 Self: 'a,
173 {
174 item
175 }
176 }
177
178 impl columnar::Container for UpsertKeys<Vec<UpsertKey>> {
179 #[inline(always)]
180 fn extend_from_self(&mut self, other: Self::Borrowed<'_>, range: Range<usize>) {
181 self.0.extend_from_self(other.0, range)
182 }
183 #[inline(always)]
184 fn reserve_for<'a, I>(&mut self, selves: I)
185 where
186 Self: 'a,
187 I: Iterator<Item = Self::Borrowed<'a>> + Clone,
188 {
189 self.0.reserve_for(selves.map(|s| s.0));
190 }
191 }
192
193 impl<'a> columnar::AsBytes<'a> for UpsertKeys<&'a [UpsertKey]> {
194 const SLICE_COUNT: usize = 1;
195 #[inline(always)]
196 fn get_byte_slice(&self, index: usize) -> (u64, &'a [u8]) {
197 mz_ore::soft_assert_no_log!(index < Self::SLICE_COUNT);
198 (
199 u64::cast_from(align_of::<UpsertKey>()),
200 bytemuck::cast_slice(self.0),
201 )
202 }
203 #[inline(always)]
204 fn as_bytes(&self) -> impl Iterator<Item = (u64, &'a [u8])> {
205 std::iter::once((
206 u64::cast_from(align_of::<UpsertKey>()),
207 bytemuck::cast_slice(self.0),
208 ))
209 }
210 }
211 impl<'a> columnar::FromBytes<'a> for UpsertKeys<&'a [UpsertKey]> {
212 const SLICE_COUNT: usize = 1;
213 #[inline(always)]
214 fn from_bytes(bytes: &mut impl Iterator<Item = &'a [u8]>) -> Self {
215 UpsertKeys(bytemuck::cast_slice(
216 bytes.next().expect("Iterator exhausted prematurely"),
217 ))
218 }
219 }
220}
221
222pub trait UpsertSourceTime
238where
239 for<'a> columnar::Ref<'a, Self::Order>: Ord,
240{
241 type Order: columnar::Columnar + Clone + Default + Ord + Send + Sync + 'static;
244 fn upsert_order(&self) -> Self::Order;
246}
247
248impl UpsertSourceTime for KafkaTimestamp {
249 type Order = (i64, u64);
255 fn upsert_order(&self) -> (i64, u64) {
256 let partition = match self.interval().lower {
257 RangeBound::NegInfinity => i64::MIN,
258 RangeBound::Elem(p, _) => i64::from(p),
259 RangeBound::PosInfinity => i64::MAX,
260 };
261 (partition, self.timestamp().offset)
262 }
263}
264
265impl UpsertSourceTime for MzOffset {
271 type Order = u64;
272 fn upsert_order(&self) -> u64 {
273 self.offset
274 }
275}
276
277macro_rules! upsert_source_time_unit {
284 ($($ty:ty),+ $(,)?) => {$(
285 impl UpsertSourceTime for $ty {
286 type Order = ();
287 fn upsert_order(&self) {
288 unreachable!(
289 "upsert source stash is not rendered for this source, but \
290 {} reached the projection",
291 std::any::type_name::<Self>(),
292 )
293 }
294 }
295 )+};
296}
297upsert_source_time_unit!(GtidPartition, Lsn);
298
299pub mod upsert_stash_spill {
313 pub fn set_enabled(enabled: bool) {
315 mz_timely_util::columnar::chunk::set_storage_spill_enabled(enabled);
316 }
317}
318
319pub mod upsert_stash_pager {
334 use std::sync::{LazyLock, RwLock};
335
336 use mz_timely_util::column_pager::{ColumnPager, shared_pager};
337
338 static PAGER: LazyLock<RwLock<ColumnPager>> =
341 LazyLock::new(|| RwLock::new(ColumnPager::disabled()));
342
343 pub fn set_enabled(enabled: bool) {
347 *PAGER.write().expect("upsert stash pager poisoned") = shared_pager(enabled);
348 }
349
350 pub fn pager() -> ColumnPager {
352 PAGER.read().expect("upsert stash pager poisoned").clone()
353 }
354}
355
356impl Debug for UpsertKey {
357 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358 write!(f, "0x")?;
359 for byte in self.0 {
360 write!(f, "{:02x}", byte)?;
361 }
362 Ok(())
363 }
364}
365
366impl AsRef<[u8]> for UpsertKey {
367 #[inline(always)]
368 fn as_ref(&self) -> &[u8] {
372 &self.0
373 }
374}
375
376impl From<&[u8]> for UpsertKey {
377 fn from(bytes: &[u8]) -> Self {
378 UpsertKey(bytes.try_into().expect("invalid key length"))
379 }
380}
381
382type KeyHash = Sha256;
387
388impl UpsertKey {
389 pub fn from_key(key: Result<&Row, &UpsertError>) -> Self {
390 Self::from_iter(key.map(|r| r.iter()))
391 }
392
393 pub fn from_value(value: Result<&Row, &UpsertError>, key_indices: &[usize]) -> Self {
394 thread_local! {
395 static VALUE_DATUMS: RefCell<DatumVec> = RefCell::new(DatumVec::new());
397 }
398 VALUE_DATUMS.with(|value_datums| {
399 let mut value_datums = value_datums.borrow_mut();
400 let value = value.map(|v| value_datums.borrow_with(v));
401 let key = match value {
402 Ok(ref datums) => Ok(key_indices.iter().map(|&idx| datums[idx])),
403 Err(err) => Err(err),
404 };
405 Self::from_iter(key)
406 })
407 }
408
409 pub fn from_iter<'a, 'b>(
410 key: Result<impl Iterator<Item = Datum<'a>> + 'b, &UpsertError>,
411 ) -> Self {
412 thread_local! {
413 static KEY_DATUMS: RefCell<DatumVec> = RefCell::new(DatumVec::new());
415 }
416 KEY_DATUMS.with(|key_datums| {
417 let mut key_datums = key_datums.borrow_mut();
418 let mut key_datums = key_datums.borrow();
421 let key: Result<&[Datum], Datum> = match key {
422 Ok(key) => {
423 for datum in key {
424 key_datums.push(datum);
425 }
426 Ok(&*key_datums)
427 }
428 Err(UpsertError::Value(err)) => {
429 key_datums.extend(err.for_key.iter());
430 Ok(&*key_datums)
431 }
432 Err(UpsertError::KeyDecode(err)) => Err(Datum::Bytes(&err.raw)),
433 Err(UpsertError::NullKey(_)) => Err(Datum::Null),
434 };
435 let mut hasher = DigestHasher(KeyHash::new());
436 key.hash(&mut hasher);
437 Self(hasher.0.finalize().into())
438 })
439 }
440}
441
442struct DigestHasher<H: Digest>(H);
443
444impl<H: Digest> Hasher for DigestHasher<H> {
445 fn write(&mut self, bytes: &[u8]) {
446 self.0.update(bytes);
447 }
448
449 fn finish(&self) -> u64 {
450 panic!("digest wrapper used to produce a hash");
451 }
452}
453
454use std::convert::Infallible;
455use timely::container::CapacityContainerBuilder;
456use timely::dataflow::channels::pact::Pipeline;
457
458use self::types::ValueMetadata;
459
460pub fn rehydration_finished<'scope, T: Timestamp>(
464 scope: Scope<'scope, T>,
465 source_config: &crate::source::RawSourceCreationConfig,
466 token: impl std::any::Any + 'static,
468 resume_upper: Antichain<T>,
469 input: StreamVec<'scope, T, Infallible>,
470) {
471 let worker_id = source_config.worker_id;
472 let id = source_config.id;
473 let mut builder = AsyncOperatorBuilder::new(format!("rehydration_finished({id}"), scope);
474 let mut input = builder.new_disconnected_input(input, Pipeline);
475
476 builder.build(move |_capabilities| async move {
477 let mut input_upper = Antichain::from_elem(Timestamp::minimum());
478 while !PartialOrder::less_equal(&resume_upper, &input_upper) {
480 let Some(event) = input.next().await else {
481 break;
482 };
483 if let AsyncEvent::Progress(upper) = event {
484 input_upper = upper;
485 }
486 }
487 tracing::info!(
488 %worker_id,
489 source_id = %id,
490 "upsert source has downgraded past the resume upper ({resume_upper:?}) across all workers",
491 );
492 drop(token);
493 });
494}
495
496pub(crate) fn upsert<'scope, T, FromTime>(
502 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
503 upsert_envelope: UpsertEnvelope,
504 resume_upper: Antichain<T>,
505 previous: VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
506 previous_token: Option<Vec<PressOnDropButton>>,
507 source_config: crate::source::SourceExportCreationConfig,
508 instance_context: &StorageInstanceContext,
509 storage_configuration: &StorageConfiguration,
510 dataflow_paramters: &crate::internal_control::DataflowParameters,
511 backpressure_metrics: Option<BackpressureMetrics>,
512) -> (
513 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
514 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
515 StreamVec<'scope, T, Infallible>,
516 PressOnDropButton,
517)
518where
519 T: Timestamp + TotalOrder + Sync,
520 T: Refines<mz_repr::Timestamp> + TotalOrder + Sync,
521 FromTime: Timestamp + Clone + Sync,
522{
523 let upsert_metrics = source_config.metrics.get_upsert_metrics(
524 source_config.id,
525 source_config.worker_id,
526 backpressure_metrics,
527 );
528
529 let rocksdb_cleanup_tries =
530 dyncfgs::STORAGE_ROCKSDB_CLEANUP_TRIES.get(storage_configuration.config_set());
531
532 let prevent_snapshot_buffering =
535 dyncfgs::STORAGE_UPSERT_PREVENT_SNAPSHOT_BUFFERING.get(storage_configuration.config_set());
536 let snapshot_buffering_max = dyncfgs::STORAGE_UPSERT_MAX_SNAPSHOT_BATCH_BUFFERING
538 .get(storage_configuration.config_set());
539
540 let rocksdb_use_native_merge_operator =
543 dyncfgs::STORAGE_ROCKSDB_USE_MERGE_OPERATOR.get(storage_configuration.config_set());
544
545 let upsert_config = UpsertConfig {
546 shrink_upsert_unused_buffers_by_ratio: storage_configuration
547 .parameters
548 .shrink_upsert_unused_buffers_by_ratio,
549 };
550
551 let thin_input = upsert_thinning(input);
552
553 let tuning = dataflow_paramters.upsert_rocksdb_tuning_config.clone();
554
555 let rocksdb_dir = instance_context
560 .scratch_directory
561 .clone()
562 .unwrap_or_else(|| PathBuf::from("/tmp"))
563 .join("storage")
564 .join("upsert")
565 .join(source_config.id.to_string())
566 .join(source_config.worker_id.to_string());
567
568 tracing::info!(
569 worker_id = %source_config.worker_id,
570 source_id = %source_config.id,
571 ?rocksdb_dir,
572 ?tuning,
573 ?rocksdb_use_native_merge_operator,
574 "rendering upsert source"
575 );
576
577 let rocksdb_shared_metrics = Arc::clone(&upsert_metrics.rocksdb_shared);
578 let rocksdb_instance_metrics = Arc::clone(&upsert_metrics.rocksdb_instance_metrics);
579
580 let env = instance_context
581 .rocksdb_env()
582 .expect("failed to create rocksdb env");
583
584 let rocksdb_init_fn = move || async move {
586 let merge_operator = if rocksdb_use_native_merge_operator {
587 Some((
588 "upsert_state_snapshot_merge_v1".to_string(),
589 |a: &[u8], b: ValueIterator<BincodeOpts, StateValue<T, FromTime>>| {
590 consolidating_merge_function::<T, FromTime>(a.into(), b)
591 },
592 ))
593 } else {
594 None
595 };
596 rocksdb::RocksDB::new(
597 mz_rocksdb::RocksDBInstance::new(
598 &rocksdb_dir,
599 mz_rocksdb::InstanceOptions::new(
600 env,
601 rocksdb_cleanup_tries,
602 merge_operator,
603 upsert_bincode_opts(),
606 ),
607 tuning,
608 rocksdb_shared_metrics,
609 rocksdb_instance_metrics,
610 )
611 .unwrap(),
612 )
613 };
614
615 upsert_operator(
616 thin_input,
617 upsert_envelope.key_indices,
618 resume_upper,
619 previous,
620 previous_token,
621 upsert_metrics,
622 source_config,
623 rocksdb_init_fn,
624 upsert_config,
625 storage_configuration,
626 prevent_snapshot_buffering,
627 snapshot_buffering_max,
628 )
629}
630
631pub(crate) fn upsert_v2<'scope, T, FromTime>(
638 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
639 upsert_envelope: UpsertEnvelope,
640 resume_upper: Antichain<T>,
641 previous: VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
642 previous_token: Option<Vec<PressOnDropButton>>,
643 source_config: crate::source::SourceExportCreationConfig,
644 backpressure_metrics: Option<BackpressureMetrics>,
645 stash_flavor: upsert_continual_feedback_v2::UpsertStashFlavor,
646) -> (
647 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
648 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
649 StreamVec<'scope, T, Infallible>,
650 PressOnDropButton,
651)
652where
653 T: Timestamp + TotalOrder + Sync,
654 T: Refines<mz_repr::Timestamp> + differential_dataflow::lattice::Lattice,
655 T: columnation::Columnation,
656 T: columnar::Columnar + Default,
657 for<'a> columnar::Ref<'a, T>: Copy + Ord,
658 FromTime: Timestamp + Clone + Sync,
659 FromTime: UpsertSourceTime,
660{
661 let upsert_metrics = source_config.metrics.get_upsert_metrics(
662 source_config.id,
663 source_config.worker_id,
664 backpressure_metrics,
665 );
666
667 let thin_input = upsert_thinning(input);
668
669 tracing::info!(
670 worker_id = %source_config.worker_id,
671 source_id = %source_config.id,
672 ?stash_flavor,
673 "rendering upsert source (btreemap backend)"
674 );
675
676 upsert_continual_feedback_v2::upsert_inner(
677 stash_flavor,
678 thin_input,
679 upsert_envelope.key_indices,
680 resume_upper,
681 previous,
682 previous_token,
683 upsert_metrics,
684 source_config,
685 )
686}
687
688fn upsert_operator<'scope, T, FromTime, F, Fut, US>(
691 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
692 key_indices: Vec<usize>,
693 resume_upper: Antichain<T>,
694 persist_input: VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
695 persist_token: Option<Vec<PressOnDropButton>>,
696 upsert_metrics: UpsertMetrics,
697 source_config: crate::source::SourceExportCreationConfig,
698 state: F,
699 upsert_config: UpsertConfig,
700 _storage_configuration: &StorageConfiguration,
701 prevent_snapshot_buffering: bool,
702 snapshot_buffering_max: Option<usize>,
703) -> (
704 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
705 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
706 StreamVec<'scope, T, Infallible>,
707 PressOnDropButton,
708)
709where
710 T: Timestamp + TotalOrder + Sync,
711 T: Refines<mz_repr::Timestamp> + TotalOrder + Sync,
712 F: FnOnce() -> Fut + 'static,
713 Fut: std::future::Future<Output = US>,
714 US: UpsertStateBackend<T, FromTime>,
715 FromTime: Debug + timely::ExchangeData + Clone + Ord + Sync,
716{
717 let use_continual_feedback_upsert = true;
721
722 tracing::info!(id = %source_config.id, %use_continual_feedback_upsert, "upsert operator implementation");
723
724 if use_continual_feedback_upsert {
725 upsert_continual_feedback::upsert_inner(
726 input,
727 key_indices,
728 resume_upper,
729 persist_input,
730 persist_token,
731 upsert_metrics,
732 source_config,
733 state,
734 upsert_config,
735 prevent_snapshot_buffering,
736 snapshot_buffering_max,
737 )
738 } else {
739 upsert_classic(
740 input,
741 key_indices,
742 resume_upper,
743 persist_input,
744 persist_token,
745 upsert_metrics,
746 source_config,
747 state,
748 upsert_config,
749 prevent_snapshot_buffering,
750 snapshot_buffering_max,
751 )
752 }
753}
754
755fn upsert_thinning<'scope, T, K, V, FromTime>(
760 input: VecCollection<'scope, T, (K, V, FromTime), Diff>,
761) -> VecCollection<'scope, T, (K, V, FromTime), Diff>
762where
763 T: Timestamp + TotalOrder,
764 K: timely::ExchangeData + Clone + Eq + Ord,
765 V: timely::ExchangeData + Clone,
766 FromTime: Timestamp,
767{
768 input
769 .inner
770 .unary(Pipeline, "UpsertThinning", |_, _| {
771 let mut capability: Option<InputCapability<T>> = None;
773 let mut updates = Vec::new();
775 move |input, output| {
776 input.for_each(|cap, data| {
777 assert!(
778 data.iter().all(|(_, _, diff)| diff.is_positive()),
779 "invalid upsert input"
780 );
781 updates.append(data);
782 match capability.as_mut() {
783 Some(capability) => {
784 if cap.time() <= capability.time() {
785 *capability = cap;
786 }
787 }
788 None => capability = Some(cap),
789 }
790 });
791 if let Some(capability) = capability.take() {
792 updates.sort_unstable_by(|a, b| {
795 let ((key1, _, from_time1), time1, _) = a;
796 let ((key2, _, from_time2), time2, _) = b;
797 Ord::cmp(
798 &(key1, time1, Reverse(from_time1)),
799 &(key2, time2, Reverse(from_time2)),
800 )
801 });
802 let mut session = output.session(&capability);
803 session.give_iterator(updates.drain(..).dedup_by(|a, b| {
804 let ((key1, _, _), time1, _) = a;
805 let ((key2, _, _), time2, _) = b;
806 (key1, time1) == (key2, time2)
807 }))
808 }
809 }
810 })
811 .as_collection()
812}
813
814fn stage_input<T, FromTime>(
817 stash: &mut Vec<(T, UpsertKey, Reverse<FromTime>, Option<UpsertValue>)>,
818 data: &mut Vec<((UpsertKey, Option<UpsertValue>, FromTime), T, Diff)>,
819 input_upper: &Antichain<T>,
820 resume_upper: &Antichain<T>,
821 storage_shrink_upsert_unused_buffers_by_ratio: usize,
822) where
823 T: PartialOrder,
824 FromTime: Ord,
825{
826 if PartialOrder::less_equal(input_upper, resume_upper) {
827 data.retain(|(_, ts, _)| resume_upper.less_equal(ts));
828 }
829
830 stash.extend(data.drain(..).map(|((key, value, order), time, diff)| {
831 assert!(diff.is_positive(), "invalid upsert input");
832 (time, key, Reverse(order), value)
833 }));
834
835 if storage_shrink_upsert_unused_buffers_by_ratio > 0 {
836 let reduced_capacity = stash.capacity() / storage_shrink_upsert_unused_buffers_by_ratio;
837 if reduced_capacity > stash.len() {
838 stash.shrink_to(reduced_capacity);
839 }
840 }
841}
842
843#[derive(Debug)]
846enum DrainStyle<'a, T> {
847 ToUpper(&'a Antichain<T>),
848 AtTime(T),
849}
850
851async fn drain_staged_input<S, T, FromTime, E>(
854 stash: &mut Vec<(T, UpsertKey, Reverse<FromTime>, Option<UpsertValue>)>,
855 commands_state: &mut indexmap::IndexMap<UpsertKey, types::UpsertValueAndSize<T, FromTime>>,
856 output_updates: &mut Vec<(UpsertValue, T, Diff)>,
857 multi_get_scratch: &mut Vec<UpsertKey>,
858 drain_style: DrainStyle<'_, T>,
859 error_emitter: &mut E,
860 state: &mut UpsertState<'_, S, T, FromTime>,
861 source_config: &crate::source::SourceExportCreationConfig,
862) where
863 S: UpsertStateBackend<T, FromTime>,
864 T: PartialOrder + Ord + Clone + Send + Sync + Serialize + Debug + 'static,
865 FromTime: timely::ExchangeData + Clone + Ord + Sync,
866 E: UpsertErrorEmitter<T>,
867{
868 stash.sort_unstable();
869
870 let idx = stash.partition_point(|(ts, _, _, _)| match &drain_style {
872 DrainStyle::ToUpper(upper) => !upper.less_equal(ts),
873 DrainStyle::AtTime(time) => ts <= time,
874 });
875
876 tracing::trace!(?drain_style, updates = idx, "draining stash in upsert");
877
878 commands_state.clear();
881 for (_, key, _, _) in stash.iter().take(idx) {
882 commands_state.entry(*key).or_default();
883 }
884
885 multi_get_scratch.clear();
888 multi_get_scratch.extend(commands_state.iter().map(|(k, _)| *k));
889 match state
890 .multi_get(multi_get_scratch.drain(..), commands_state.values_mut())
891 .await
892 {
893 Ok(_) => {}
894 Err(e) => {
895 error_emitter
896 .emit("Failed to fetch records from state".to_string(), e)
897 .await;
898 }
899 }
900
901 let mut commands = stash.drain(..idx).dedup_by(|a, b| {
905 let ((a_ts, a_key, _, _), (b_ts, b_key, _, _)) = (a, b);
906 a_ts == b_ts && a_key == b_key
907 });
908
909 let bincode_opts = types::upsert_bincode_opts();
910 while let Some((ts, key, from_time, value)) = commands.next() {
923 let mut command_state = if let Entry::Occupied(command_state) = commands_state.entry(key) {
924 command_state
925 } else {
926 panic!("key missing from commands_state");
927 };
928
929 let existing_value = &mut command_state.get_mut().value;
930
931 if let Some(cs) = existing_value.as_mut() {
932 cs.ensure_decoded(bincode_opts, source_config.id, Some(&key));
933 }
934
935 let existing_order = existing_value
939 .as_ref()
940 .and_then(|cs| cs.provisional_order(&ts));
941 if existing_order >= Some(&from_time.0) {
942 continue;
947 }
948
949 match value {
950 Some(value) => {
951 if let Some(old_value) =
952 existing_value.replace(StateValue::finalized_value(value.clone()))
953 {
954 if let Some(old_value) = old_value.into_decoded().finalized {
955 output_updates.push((old_value, ts.clone(), Diff::MINUS_ONE));
956 }
957 }
958 output_updates.push((value, ts, Diff::ONE));
959 }
960 None => {
961 if let Some(old_value) = existing_value.take() {
962 if let Some(old_value) = old_value.into_decoded().finalized {
963 output_updates.push((old_value, ts, Diff::MINUS_ONE));
964 }
965 }
966
967 *existing_value = Some(StateValue::tombstone());
969 }
970 }
971 }
972
973 match state
974 .multi_put(
975 true, commands_state.drain(..).map(|(k, cv)| {
977 (
978 k,
979 types::PutValue {
980 value: cv.value.map(|cv| cv.into_decoded()),
981 previous_value_metadata: cv.metadata.map(|v| ValueMetadata {
982 size: v.size.try_into().expect("less than i64 size"),
983 is_tombstone: v.is_tombstone,
984 }),
985 },
986 )
987 }),
988 )
989 .await
990 {
991 Ok(_) => {}
992 Err(e) => {
993 error_emitter
994 .emit("Failed to update records in state".to_string(), e)
995 .await;
996 }
997 }
998}
999
1000#[cfg(feature = "fuzzing")]
1004struct PanicErrorEmitter;
1005
1006#[cfg(feature = "fuzzing")]
1007#[async_trait::async_trait(?Send)]
1008impl<T> UpsertErrorEmitter<T> for PanicErrorEmitter {
1009 async fn emit(&mut self, context: String, e: anyhow::Error) {
1010 panic!("unexpected upsert state error during fuzzing: {context}: {e}");
1011 }
1012}
1013
1014#[cfg(feature = "fuzzing")]
1021pub async fn fuzz_drain_staged_input(
1022 parts: &types::FuzzUpsertParts,
1023 source_config: &crate::source::SourceExportCreationConfig,
1024 commands: Vec<(u64, UpsertKey, u64, Option<UpsertValue>)>,
1025 drain_to: u64,
1026 all_keys: &[UpsertKey],
1027) -> (Vec<(UpsertValue, u64, Diff)>, Vec<Option<UpsertValue>>) {
1028 let mut state = parts.state();
1029 let mut stash: Vec<(u64, UpsertKey, Reverse<u64>, Option<UpsertValue>)> = commands
1030 .into_iter()
1031 .map(|(ts, key, order, value)| (ts, key, Reverse(order), value))
1032 .collect();
1033 let mut commands_state = indexmap::IndexMap::new();
1034 let mut output = Vec::new();
1035 let mut multi_get_scratch = Vec::new();
1036 let mut emitter = PanicErrorEmitter;
1037
1038 drain_staged_input(
1039 &mut stash,
1040 &mut commands_state,
1041 &mut output,
1042 &mut multi_get_scratch,
1043 DrainStyle::ToUpper(&Antichain::from_elem(drain_to)),
1044 &mut emitter,
1045 &mut state,
1046 source_config,
1047 )
1048 .await;
1049
1050 let bincode_opts = types::upsert_bincode_opts();
1051 let mut results = vec![types::UpsertValueAndSize::default(); all_keys.len()];
1052 state
1053 .multi_get(all_keys.iter().copied(), results.iter_mut())
1054 .await
1055 .expect("multi_get in fuzz hook should not error");
1056 let final_state = results
1057 .into_iter()
1058 .map(|r| match r.value {
1059 None => None,
1060 Some(mut sv) => {
1061 sv.ensure_decoded(bincode_opts, GlobalId::User(0), None);
1062 sv.into_decoded().finalized
1063 }
1064 })
1065 .collect();
1066
1067 (output, final_state)
1068}
1069
1070pub(crate) struct UpsertConfig {
1073 pub shrink_upsert_unused_buffers_by_ratio: usize,
1074}
1075
1076fn upsert_classic<'scope, T, FromTime, F, Fut, US>(
1077 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
1078 key_indices: Vec<usize>,
1079 resume_upper: Antichain<T>,
1080 previous: VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
1081 previous_token: Option<Vec<PressOnDropButton>>,
1082 upsert_metrics: UpsertMetrics,
1083 source_config: crate::source::SourceExportCreationConfig,
1084 state: F,
1085 upsert_config: UpsertConfig,
1086 prevent_snapshot_buffering: bool,
1087 snapshot_buffering_max: Option<usize>,
1088) -> (
1089 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
1090 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
1091 StreamVec<'scope, T, Infallible>,
1092 PressOnDropButton,
1093)
1094where
1095 T: Timestamp + TotalOrder + Sync,
1096 F: FnOnce() -> Fut + 'static,
1097 Fut: std::future::Future<Output = US>,
1098 US: UpsertStateBackend<T, FromTime>,
1099 FromTime: timely::ExchangeData + Clone + Ord + Sync,
1100{
1101 let mut builder = AsyncOperatorBuilder::new("Upsert".to_string(), input.scope());
1102
1103 let previous = previous.flat_map(move |result| {
1105 let value = match result {
1106 Ok(ok) => Ok(ok),
1107 Err(DataflowError::EnvelopeError(err)) => match *err {
1108 EnvelopeError::Upsert(err) => Err(Box::new(err)),
1109 EnvelopeError::Flat(_) => return None,
1110 },
1111 Err(_) => return None,
1112 };
1113 let value_ref = match value {
1114 Ok(ref row) => Ok(row),
1115 Err(ref err) => Err(&**err),
1116 };
1117 Some((UpsertKey::from_value(value_ref, &key_indices), value))
1118 });
1119 let (output_handle, output) = builder.new_output();
1120
1121 let (_snapshot_handle, snapshot_stream) =
1124 builder.new_output::<CapacityContainerBuilder<Vec<Infallible>>>();
1125
1126 let (mut health_output, health_stream) = builder.new_output();
1127 let mut input = builder.new_input_for(
1128 input.inner,
1129 Exchange::new(move |((key, _, _), _, _)| UpsertKey::hashed(key)),
1130 &output_handle,
1131 );
1132
1133 let mut previous = builder.new_input_for(
1134 previous.inner,
1135 Exchange::new(|((key, _), _, _)| UpsertKey::hashed(key)),
1136 &output_handle,
1137 );
1138
1139 let upsert_shared_metrics = Arc::clone(&upsert_metrics.shared);
1140 let shutdown_button = builder.build(move |caps| async move {
1141 let [mut output_cap, mut snapshot_cap, health_cap]: [_; 3] = caps.try_into().unwrap();
1142
1143 let mut state = UpsertState::<_, _, FromTime>::new(
1144 state().await,
1145 upsert_shared_metrics,
1146 &upsert_metrics,
1147 source_config.source_statistics.clone(),
1148 upsert_config.shrink_upsert_unused_buffers_by_ratio,
1149 );
1150 let mut events = vec![];
1151 let mut snapshot_upper = Antichain::from_elem(Timestamp::minimum());
1152
1153 let mut stash = vec![];
1154
1155 let mut error_emitter = (&mut health_output, &health_cap);
1156
1157 tracing::info!(
1158 ?resume_upper,
1159 ?snapshot_upper,
1160 "timely-{} upsert source {} starting rehydration",
1161 source_config.worker_id,
1162 source_config.id
1163 );
1164 while !PartialOrder::less_equal(&resume_upper, &snapshot_upper) {
1167 previous.ready().await;
1168 while let Some(event) = previous.next_sync() {
1169 match event {
1170 AsyncEvent::Data(_cap, data) => {
1171 events.extend(data.into_iter().filter_map(|((key, value), ts, diff)| {
1172 if !resume_upper.less_equal(&ts) {
1173 Some((key, value, diff))
1174 } else {
1175 None
1176 }
1177 }))
1178 }
1179 AsyncEvent::Progress(upper) => {
1180 snapshot_upper = upper;
1181 }
1182 };
1183 }
1184
1185 match state
1186 .consolidate_chunk(
1187 events.drain(..),
1188 PartialOrder::less_equal(&resume_upper, &snapshot_upper),
1189 )
1190 .await
1191 {
1192 Ok(_) => {
1193 if let Some(ts) = snapshot_upper.clone().into_option() {
1194 if !resume_upper.less_equal(&ts) {
1198 snapshot_cap.downgrade(&ts);
1199 output_cap.downgrade(&ts);
1200 }
1201 }
1202 }
1203 Err(e) => {
1204 UpsertErrorEmitter::<T>::emit(
1205 &mut error_emitter,
1206 "Failed to rehydrate state".to_string(),
1207 e,
1208 )
1209 .await;
1210 }
1211 }
1212 }
1213
1214 drop(events);
1215 drop(previous_token);
1216 drop(snapshot_cap);
1217
1218 while let Some(_event) = previous.next().await {}
1224
1225 if let Some(ts) = resume_upper.as_option() {
1227 output_cap.downgrade(ts);
1228 }
1229
1230 tracing::info!(
1231 "timely-{} upsert source {} finished rehydration",
1232 source_config.worker_id,
1233 source_config.id
1234 );
1235
1236 let mut commands_state: indexmap::IndexMap<_, types::UpsertValueAndSize<T, FromTime>> =
1239 indexmap::IndexMap::new();
1240 let mut multi_get_scratch = Vec::new();
1241
1242 let mut output_updates = vec![];
1244 let mut input_upper = Antichain::from_elem(Timestamp::minimum());
1245
1246 while let Some(event) = input.next().await {
1247 let events = [event]
1250 .into_iter()
1251 .chain(std::iter::from_fn(|| input.next().now_or_never().flatten()))
1252 .enumerate();
1253
1254 let mut partial_drain_time = None;
1255 for (i, event) in events {
1256 match event {
1257 AsyncEvent::Data(cap, mut data) => {
1258 tracing::trace!(
1259 time=?cap.time(),
1260 updates=%data.len(),
1261 "received data in upsert"
1262 );
1263 stage_input(
1264 &mut stash,
1265 &mut data,
1266 &input_upper,
1267 &resume_upper,
1268 upsert_config.shrink_upsert_unused_buffers_by_ratio,
1269 );
1270
1271 let event_time = cap.time();
1272 if prevent_snapshot_buffering && output_cap.time() == event_time {
1279 partial_drain_time = Some(event_time.clone());
1280 }
1281 }
1282 AsyncEvent::Progress(upper) => {
1283 tracing::trace!(?upper, "received progress in upsert");
1284 if PartialOrder::less_than(&upper, &resume_upper) {
1287 continue;
1288 }
1289
1290 partial_drain_time = None;
1293 drain_staged_input::<_, _, _, _>(
1294 &mut stash,
1295 &mut commands_state,
1296 &mut output_updates,
1297 &mut multi_get_scratch,
1298 DrainStyle::ToUpper(&upper),
1299 &mut error_emitter,
1300 &mut state,
1301 &source_config,
1302 )
1303 .await;
1304
1305 output_handle.give_container(&output_cap, &mut output_updates);
1306
1307 if let Some(ts) = upper.as_option() {
1308 output_cap.downgrade(ts);
1309 }
1310 input_upper = upper;
1311 }
1312 }
1313 let events_processed = i + 1;
1314 if let Some(max) = snapshot_buffering_max {
1315 if events_processed >= max {
1316 break;
1317 }
1318 }
1319 }
1320
1321 if let Some(partial_drain_time) = partial_drain_time {
1330 drain_staged_input::<_, _, _, _>(
1331 &mut stash,
1332 &mut commands_state,
1333 &mut output_updates,
1334 &mut multi_get_scratch,
1335 DrainStyle::AtTime(partial_drain_time),
1336 &mut error_emitter,
1337 &mut state,
1338 &source_config,
1339 )
1340 .await;
1341
1342 output_handle.give_container(&output_cap, &mut output_updates);
1343 }
1344 }
1345 });
1346
1347 (
1348 output.as_collection().map(|result| match result {
1349 Ok(ok) => Ok(ok),
1350 Err(err) => Err(DataflowError::from(EnvelopeError::Upsert(*err))),
1351 }),
1352 health_stream,
1353 snapshot_stream,
1354 shutdown_button.press_on_drop(),
1355 )
1356}
1357
1358#[async_trait::async_trait(?Send)]
1359pub(crate) trait UpsertErrorEmitter<T> {
1360 async fn emit(&mut self, context: String, e: anyhow::Error);
1361}
1362
1363#[async_trait::async_trait(?Send)]
1364impl<T: Timestamp> UpsertErrorEmitter<T>
1365 for (
1366 &mut AsyncOutputHandle<
1367 T,
1368 CapacityContainerBuilder<Vec<(Option<GlobalId>, HealthStatusUpdate)>>,
1369 >,
1370 &Capability<T>,
1371 )
1372{
1373 async fn emit(&mut self, context: String, e: anyhow::Error) {
1374 process_upsert_state_error::<T>(context, e, self.0, self.1).await
1375 }
1376}
1377
1378async fn process_upsert_state_error<T: Timestamp>(
1380 context: String,
1381 e: anyhow::Error,
1382 health_output: &AsyncOutputHandle<
1383 T,
1384 CapacityContainerBuilder<Vec<(Option<GlobalId>, HealthStatusUpdate)>>,
1385 >,
1386 health_cap: &Capability<T>,
1387) {
1388 let update = HealthStatusUpdate::halting(e.context(context).to_string_with_causes(), None);
1389 health_output.give(health_cap, (None, update));
1390 std::future::pending::<()>().await;
1391 unreachable!("pending future never returns");
1392}