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