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 {
312 pub fn set_enabled(enabled: bool) {
314 mz_timely_util::columnar::chunk::set_storage_spill_enabled(enabled);
315 }
316}
317
318pub mod upsert_stash_pager {
333 use std::sync::{LazyLock, RwLock};
334
335 use mz_timely_util::column_pager::{ColumnPager, shared_pager};
336
337 static PAGER: LazyLock<RwLock<ColumnPager>> =
340 LazyLock::new(|| RwLock::new(ColumnPager::disabled()));
341
342 pub fn set_enabled(enabled: bool) {
346 *PAGER.write().expect("upsert stash pager poisoned") = shared_pager(enabled);
347 }
348
349 pub fn pager() -> ColumnPager {
351 PAGER.read().expect("upsert stash pager poisoned").clone()
352 }
353}
354
355impl Debug for UpsertKey {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 write!(f, "0x")?;
358 for byte in self.0 {
359 write!(f, "{:02x}", byte)?;
360 }
361 Ok(())
362 }
363}
364
365impl AsRef<[u8]> for UpsertKey {
366 #[inline(always)]
367 fn as_ref(&self) -> &[u8] {
371 &self.0
372 }
373}
374
375impl From<&[u8]> for UpsertKey {
376 fn from(bytes: &[u8]) -> Self {
377 UpsertKey(bytes.try_into().expect("invalid key length"))
378 }
379}
380
381type KeyHash = Sha256;
386
387impl UpsertKey {
388 pub fn from_key(key: Result<&Row, &UpsertError>) -> Self {
389 Self::from_iter(key.map(|r| r.iter()))
390 }
391
392 pub fn from_value(value: Result<&Row, &UpsertError>, key_indices: &[usize]) -> Self {
393 thread_local! {
394 static VALUE_DATUMS: RefCell<DatumVec> = RefCell::new(DatumVec::new());
396 }
397 VALUE_DATUMS.with(|value_datums| {
398 let mut value_datums = value_datums.borrow_mut();
399 let value = value.map(|v| value_datums.borrow_with(v));
400 let key = match value {
401 Ok(ref datums) => Ok(key_indices.iter().map(|&idx| datums[idx])),
402 Err(err) => Err(err),
403 };
404 Self::from_iter(key)
405 })
406 }
407
408 pub fn from_iter<'a, 'b>(
409 key: Result<impl Iterator<Item = Datum<'a>> + 'b, &UpsertError>,
410 ) -> Self {
411 thread_local! {
412 static KEY_DATUMS: RefCell<DatumVec> = RefCell::new(DatumVec::new());
414 }
415 KEY_DATUMS.with(|key_datums| {
416 let mut key_datums = key_datums.borrow_mut();
417 let mut key_datums = key_datums.borrow();
420 let key: Result<&[Datum], Datum> = match key {
421 Ok(key) => {
422 for datum in key {
423 key_datums.push(datum);
424 }
425 Ok(&*key_datums)
426 }
427 Err(UpsertError::Value(err)) => {
428 key_datums.extend(err.for_key.iter());
429 Ok(&*key_datums)
430 }
431 Err(UpsertError::KeyDecode(err)) => Err(Datum::Bytes(&err.raw)),
432 Err(UpsertError::NullKey(_)) => Err(Datum::Null),
433 };
434 let mut hasher = DigestHasher(KeyHash::new());
435 key.hash(&mut hasher);
436 Self(hasher.0.finalize().into())
437 })
438 }
439}
440
441struct DigestHasher<H: Digest>(H);
442
443impl<H: Digest> Hasher for DigestHasher<H> {
444 fn write(&mut self, bytes: &[u8]) {
445 self.0.update(bytes);
446 }
447
448 fn finish(&self) -> u64 {
449 panic!("digest wrapper used to produce a hash");
450 }
451}
452
453use std::convert::Infallible;
454use timely::container::CapacityContainerBuilder;
455use timely::dataflow::channels::pact::Pipeline;
456
457use self::types::ValueMetadata;
458
459pub fn rehydration_finished<'scope, T: Timestamp>(
463 scope: Scope<'scope, T>,
464 source_config: &crate::source::RawSourceCreationConfig,
465 token: impl std::any::Any + 'static,
467 resume_upper: Antichain<T>,
468 input: StreamVec<'scope, T, Infallible>,
469) {
470 let worker_id = source_config.worker_id;
471 let id = source_config.id;
472 let mut builder = AsyncOperatorBuilder::new(format!("rehydration_finished({id}"), scope);
473 let mut input = builder.new_disconnected_input(input, Pipeline);
474
475 builder.build(move |_capabilities| async move {
476 let mut input_upper = Antichain::from_elem(Timestamp::minimum());
477 while !PartialOrder::less_equal(&resume_upper, &input_upper) {
479 let Some(event) = input.next().await else {
480 break;
481 };
482 if let AsyncEvent::Progress(upper) = event {
483 input_upper = upper;
484 }
485 }
486 tracing::info!(
487 %worker_id,
488 source_id = %id,
489 "upsert source has downgraded past the resume upper ({resume_upper:?}) across all workers",
490 );
491 drop(token);
492 });
493}
494
495pub(crate) fn upsert<'scope, T, FromTime>(
501 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
502 upsert_envelope: UpsertEnvelope,
503 resume_upper: Antichain<T>,
504 previous: VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
505 previous_token: Option<Vec<PressOnDropButton>>,
506 source_config: crate::source::SourceExportCreationConfig,
507 instance_context: &StorageInstanceContext,
508 storage_configuration: &StorageConfiguration,
509 dataflow_paramters: &crate::internal_control::DataflowParameters,
510 backpressure_metrics: Option<UpsertBackpressureMetrics>,
511) -> (
512 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
513 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
514 StreamVec<'scope, T, Infallible>,
515 PressOnDropButton,
516)
517where
518 T: Timestamp + TotalOrder + Sync,
519 T: Refines<mz_repr::Timestamp> + TotalOrder + Sync,
520 FromTime: Timestamp + Clone + Sync,
521{
522 let upsert_metrics = source_config.metrics.get_upsert_metrics(
523 source_config.id,
524 source_config.worker_id,
525 backpressure_metrics,
526 );
527
528 let rocksdb_cleanup_tries =
529 dyncfgs::STORAGE_ROCKSDB_CLEANUP_TRIES.get(storage_configuration.config_set());
530
531 let prevent_snapshot_buffering =
534 dyncfgs::STORAGE_UPSERT_PREVENT_SNAPSHOT_BUFFERING.get(storage_configuration.config_set());
535 let snapshot_buffering_max = dyncfgs::STORAGE_UPSERT_MAX_SNAPSHOT_BATCH_BUFFERING
537 .get(storage_configuration.config_set());
538
539 let rocksdb_use_native_merge_operator =
542 dyncfgs::STORAGE_ROCKSDB_USE_MERGE_OPERATOR.get(storage_configuration.config_set());
543
544 let upsert_config = UpsertConfig {
545 shrink_upsert_unused_buffers_by_ratio: storage_configuration
546 .parameters
547 .shrink_upsert_unused_buffers_by_ratio,
548 };
549
550 let thin_input = upsert_thinning(input);
551
552 let tuning = dataflow_paramters.upsert_rocksdb_tuning_config.clone();
553
554 let rocksdb_dir = instance_context
559 .scratch_directory
560 .clone()
561 .unwrap_or_else(|| PathBuf::from("/tmp"))
562 .join("storage")
563 .join("upsert")
564 .join(source_config.id.to_string())
565 .join(source_config.worker_id.to_string());
566
567 tracing::info!(
568 worker_id = %source_config.worker_id,
569 source_id = %source_config.id,
570 ?rocksdb_dir,
571 ?tuning,
572 ?rocksdb_use_native_merge_operator,
573 "rendering upsert source"
574 );
575
576 let rocksdb_shared_metrics = Arc::clone(&upsert_metrics.rocksdb_shared);
577 let rocksdb_instance_metrics = Arc::clone(&upsert_metrics.rocksdb_instance_metrics);
578
579 let env = instance_context
580 .rocksdb_env()
581 .expect("failed to create rocksdb env");
582
583 let rocksdb_init_fn = move || async move {
585 let merge_operator = if rocksdb_use_native_merge_operator {
586 Some((
587 "upsert_state_snapshot_merge_v1".to_string(),
588 |a: &[u8], b: ValueIterator<BincodeOpts, StateValue<T, FromTime>>| {
589 consolidating_merge_function::<T, FromTime>(a.into(), b)
590 },
591 ))
592 } else {
593 None
594 };
595 rocksdb::RocksDB::new(
596 mz_rocksdb::RocksDBInstance::new(
597 &rocksdb_dir,
598 mz_rocksdb::InstanceOptions::new(
599 env,
600 rocksdb_cleanup_tries,
601 merge_operator,
602 upsert_bincode_opts(),
605 ),
606 tuning,
607 rocksdb_shared_metrics,
608 rocksdb_instance_metrics,
609 )
610 .unwrap(),
611 )
612 };
613
614 upsert_operator(
615 thin_input,
616 upsert_envelope.key_indices,
617 resume_upper,
618 previous,
619 previous_token,
620 upsert_metrics,
621 source_config,
622 rocksdb_init_fn,
623 upsert_config,
624 storage_configuration,
625 prevent_snapshot_buffering,
626 snapshot_buffering_max,
627 )
628}
629
630pub(crate) fn upsert_v2<'scope, T, FromTime>(
637 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
638 upsert_envelope: UpsertEnvelope,
639 resume_upper: Antichain<T>,
640 previous: VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
641 previous_token: Option<Vec<PressOnDropButton>>,
642 source_config: crate::source::SourceExportCreationConfig,
643 backpressure_metrics: Option<UpsertBackpressureMetrics>,
644 stash_flavor: upsert_continual_feedback_v2::UpsertStashFlavor,
645) -> (
646 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
647 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
648 StreamVec<'scope, T, Infallible>,
649 PressOnDropButton,
650)
651where
652 T: Timestamp + TotalOrder + Sync,
653 T: Refines<mz_repr::Timestamp> + differential_dataflow::lattice::Lattice,
654 T: columnation::Columnation,
655 T: columnar::Columnar + Default,
656 for<'a> columnar::Ref<'a, T>: Copy + Ord,
657 FromTime: Timestamp + Clone + Sync,
658 FromTime: UpsertSourceTime,
659{
660 let upsert_metrics = source_config.metrics.get_upsert_metrics(
661 source_config.id,
662 source_config.worker_id,
663 backpressure_metrics,
664 );
665
666 let thin_input = upsert_thinning(input);
667
668 tracing::info!(
669 worker_id = %source_config.worker_id,
670 source_id = %source_config.id,
671 ?stash_flavor,
672 "rendering upsert source (btreemap backend)"
673 );
674
675 upsert_continual_feedback_v2::upsert_inner(
676 stash_flavor,
677 thin_input,
678 upsert_envelope.key_indices,
679 resume_upper,
680 previous,
681 previous_token,
682 upsert_metrics,
683 source_config,
684 )
685}
686
687fn upsert_operator<'scope, T, FromTime, F, Fut, US>(
690 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
691 key_indices: Vec<usize>,
692 resume_upper: Antichain<T>,
693 persist_input: VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
694 persist_token: Option<Vec<PressOnDropButton>>,
695 upsert_metrics: UpsertMetrics,
696 source_config: crate::source::SourceExportCreationConfig,
697 state: F,
698 upsert_config: UpsertConfig,
699 _storage_configuration: &StorageConfiguration,
700 prevent_snapshot_buffering: bool,
701 snapshot_buffering_max: Option<usize>,
702) -> (
703 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
704 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
705 StreamVec<'scope, T, Infallible>,
706 PressOnDropButton,
707)
708where
709 T: Timestamp + TotalOrder + Sync,
710 T: Refines<mz_repr::Timestamp> + TotalOrder + Sync,
711 F: FnOnce() -> Fut + 'static,
712 Fut: std::future::Future<Output = US>,
713 US: UpsertStateBackend<T, FromTime>,
714 FromTime: Debug + timely::ExchangeData + Clone + Ord + Sync,
715{
716 let use_continual_feedback_upsert = true;
720
721 tracing::info!(id = %source_config.id, %use_continual_feedback_upsert, "upsert operator implementation");
722
723 if use_continual_feedback_upsert {
724 upsert_continual_feedback::upsert_inner(
725 input,
726 key_indices,
727 resume_upper,
728 persist_input,
729 persist_token,
730 upsert_metrics,
731 source_config,
732 state,
733 upsert_config,
734 prevent_snapshot_buffering,
735 snapshot_buffering_max,
736 )
737 } else {
738 upsert_classic(
739 input,
740 key_indices,
741 resume_upper,
742 persist_input,
743 persist_token,
744 upsert_metrics,
745 source_config,
746 state,
747 upsert_config,
748 prevent_snapshot_buffering,
749 snapshot_buffering_max,
750 )
751 }
752}
753
754fn upsert_thinning<'scope, T, K, V, FromTime>(
759 input: VecCollection<'scope, T, (K, V, FromTime), Diff>,
760) -> VecCollection<'scope, T, (K, V, FromTime), Diff>
761where
762 T: Timestamp + TotalOrder,
763 K: timely::ExchangeData + Clone + Eq + Ord,
764 V: timely::ExchangeData + Clone,
765 FromTime: Timestamp,
766{
767 input
768 .inner
769 .unary(Pipeline, "UpsertThinning", |_, _| {
770 let mut capability: Option<InputCapability<T>> = None;
772 let mut updates = Vec::new();
774 move |input, output| {
775 input.for_each(|cap, data| {
776 assert!(
777 data.iter().all(|(_, _, diff)| diff.is_positive()),
778 "invalid upsert input"
779 );
780 updates.append(data);
781 match capability.as_mut() {
782 Some(capability) => {
783 if cap.time() <= capability.time() {
784 *capability = cap;
785 }
786 }
787 None => capability = Some(cap),
788 }
789 });
790 if let Some(capability) = capability.take() {
791 updates.sort_unstable_by(|a, b| {
794 let ((key1, _, from_time1), time1, _) = a;
795 let ((key2, _, from_time2), time2, _) = b;
796 Ord::cmp(
797 &(key1, time1, Reverse(from_time1)),
798 &(key2, time2, Reverse(from_time2)),
799 )
800 });
801 let mut session = output.session(&capability);
802 session.give_iterator(updates.drain(..).dedup_by(|a, b| {
803 let ((key1, _, _), time1, _) = a;
804 let ((key2, _, _), time2, _) = b;
805 (key1, time1) == (key2, time2)
806 }))
807 }
808 }
809 })
810 .as_collection()
811}
812
813fn stage_input<T, FromTime>(
816 stash: &mut Vec<(T, UpsertKey, Reverse<FromTime>, Option<UpsertValue>)>,
817 data: &mut Vec<((UpsertKey, Option<UpsertValue>, FromTime), T, Diff)>,
818 input_upper: &Antichain<T>,
819 resume_upper: &Antichain<T>,
820 storage_shrink_upsert_unused_buffers_by_ratio: usize,
821) where
822 T: PartialOrder,
823 FromTime: Ord,
824{
825 if PartialOrder::less_equal(input_upper, resume_upper) {
826 data.retain(|(_, ts, _)| resume_upper.less_equal(ts));
827 }
828
829 stash.extend(data.drain(..).map(|((key, value, order), time, diff)| {
830 assert!(diff.is_positive(), "invalid upsert input");
831 (time, key, Reverse(order), value)
832 }));
833
834 if storage_shrink_upsert_unused_buffers_by_ratio > 0 {
835 let reduced_capacity = stash.capacity() / storage_shrink_upsert_unused_buffers_by_ratio;
836 if reduced_capacity > stash.len() {
837 stash.shrink_to(reduced_capacity);
838 }
839 }
840}
841
842#[derive(Debug)]
845enum DrainStyle<'a, T> {
846 ToUpper(&'a Antichain<T>),
847 AtTime(T),
848}
849
850async fn drain_staged_input<S, T, FromTime, E>(
853 stash: &mut Vec<(T, UpsertKey, Reverse<FromTime>, Option<UpsertValue>)>,
854 commands_state: &mut indexmap::IndexMap<UpsertKey, types::UpsertValueAndSize<T, FromTime>>,
855 output_updates: &mut Vec<(UpsertValue, T, Diff)>,
856 multi_get_scratch: &mut Vec<UpsertKey>,
857 drain_style: DrainStyle<'_, T>,
858 error_emitter: &mut E,
859 state: &mut UpsertState<'_, S, T, FromTime>,
860 source_config: &crate::source::SourceExportCreationConfig,
861) where
862 S: UpsertStateBackend<T, FromTime>,
863 T: PartialOrder + Ord + Clone + Send + Sync + Serialize + Debug + 'static,
864 FromTime: timely::ExchangeData + Clone + Ord + Sync,
865 E: UpsertErrorEmitter<T>,
866{
867 stash.sort_unstable();
868
869 let idx = stash.partition_point(|(ts, _, _, _)| match &drain_style {
871 DrainStyle::ToUpper(upper) => !upper.less_equal(ts),
872 DrainStyle::AtTime(time) => ts <= time,
873 });
874
875 tracing::trace!(?drain_style, updates = idx, "draining stash in upsert");
876
877 commands_state.clear();
880 for (_, key, _, _) in stash.iter().take(idx) {
881 commands_state.entry(*key).or_default();
882 }
883
884 multi_get_scratch.clear();
887 multi_get_scratch.extend(commands_state.iter().map(|(k, _)| *k));
888 match state
889 .multi_get(multi_get_scratch.drain(..), commands_state.values_mut())
890 .await
891 {
892 Ok(_) => {}
893 Err(e) => {
894 error_emitter
895 .emit("Failed to fetch records from state".to_string(), e)
896 .await;
897 }
898 }
899
900 let mut commands = stash.drain(..idx).dedup_by(|a, b| {
904 let ((a_ts, a_key, _, _), (b_ts, b_key, _, _)) = (a, b);
905 a_ts == b_ts && a_key == b_key
906 });
907
908 let bincode_opts = types::upsert_bincode_opts();
909 while let Some((ts, key, from_time, value)) = commands.next() {
922 let mut command_state = if let Entry::Occupied(command_state) = commands_state.entry(key) {
923 command_state
924 } else {
925 panic!("key missing from commands_state");
926 };
927
928 let existing_value = &mut command_state.get_mut().value;
929
930 if let Some(cs) = existing_value.as_mut() {
931 cs.ensure_decoded(bincode_opts, source_config.id, Some(&key));
932 }
933
934 let existing_order = existing_value
938 .as_ref()
939 .and_then(|cs| cs.provisional_order(&ts));
940 if existing_order >= Some(&from_time.0) {
941 continue;
946 }
947
948 match value {
949 Some(value) => {
950 if let Some(old_value) =
951 existing_value.replace(StateValue::finalized_value(value.clone()))
952 {
953 if let Some(old_value) = old_value.into_decoded().finalized {
954 output_updates.push((old_value, ts.clone(), Diff::MINUS_ONE));
955 }
956 }
957 output_updates.push((value, ts, Diff::ONE));
958 }
959 None => {
960 if let Some(old_value) = existing_value.take() {
961 if let Some(old_value) = old_value.into_decoded().finalized {
962 output_updates.push((old_value, ts, Diff::MINUS_ONE));
963 }
964 }
965
966 *existing_value = Some(StateValue::tombstone());
968 }
969 }
970 }
971
972 match state
973 .multi_put(
974 true, commands_state.drain(..).map(|(k, cv)| {
976 (
977 k,
978 types::PutValue {
979 value: cv.value.map(|cv| cv.into_decoded()),
980 previous_value_metadata: cv.metadata.map(|v| ValueMetadata {
981 size: v.size.try_into().expect("less than i64 size"),
982 is_tombstone: v.is_tombstone,
983 }),
984 },
985 )
986 }),
987 )
988 .await
989 {
990 Ok(_) => {}
991 Err(e) => {
992 error_emitter
993 .emit("Failed to update records in state".to_string(), e)
994 .await;
995 }
996 }
997}
998
999#[cfg(feature = "fuzzing")]
1003struct PanicErrorEmitter;
1004
1005#[cfg(feature = "fuzzing")]
1006#[async_trait::async_trait(?Send)]
1007impl<T> UpsertErrorEmitter<T> for PanicErrorEmitter {
1008 async fn emit(&mut self, context: String, e: anyhow::Error) {
1009 panic!("unexpected upsert state error during fuzzing: {context}: {e}");
1010 }
1011}
1012
1013#[cfg(feature = "fuzzing")]
1020pub async fn fuzz_drain_staged_input(
1021 parts: &types::FuzzUpsertParts,
1022 source_config: &crate::source::SourceExportCreationConfig,
1023 commands: Vec<(u64, UpsertKey, u64, Option<UpsertValue>)>,
1024 drain_to: u64,
1025 all_keys: &[UpsertKey],
1026) -> (Vec<(UpsertValue, u64, Diff)>, Vec<Option<UpsertValue>>) {
1027 let mut state = parts.state();
1028 let mut stash: Vec<(u64, UpsertKey, Reverse<u64>, Option<UpsertValue>)> = commands
1029 .into_iter()
1030 .map(|(ts, key, order, value)| (ts, key, Reverse(order), value))
1031 .collect();
1032 let mut commands_state = indexmap::IndexMap::new();
1033 let mut output = Vec::new();
1034 let mut multi_get_scratch = Vec::new();
1035 let mut emitter = PanicErrorEmitter;
1036
1037 drain_staged_input(
1038 &mut stash,
1039 &mut commands_state,
1040 &mut output,
1041 &mut multi_get_scratch,
1042 DrainStyle::ToUpper(&Antichain::from_elem(drain_to)),
1043 &mut emitter,
1044 &mut state,
1045 source_config,
1046 )
1047 .await;
1048
1049 let bincode_opts = types::upsert_bincode_opts();
1050 let mut results = vec![types::UpsertValueAndSize::default(); all_keys.len()];
1051 state
1052 .multi_get(all_keys.iter().copied(), results.iter_mut())
1053 .await
1054 .expect("multi_get in fuzz hook should not error");
1055 let final_state = results
1056 .into_iter()
1057 .map(|r| match r.value {
1058 None => None,
1059 Some(mut sv) => {
1060 sv.ensure_decoded(bincode_opts, GlobalId::User(0), None);
1061 sv.into_decoded().finalized
1062 }
1063 })
1064 .collect();
1065
1066 (output, final_state)
1067}
1068
1069pub(crate) struct UpsertConfig {
1072 pub shrink_upsert_unused_buffers_by_ratio: usize,
1073}
1074
1075fn upsert_classic<'scope, T, FromTime, F, Fut, US>(
1076 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
1077 key_indices: Vec<usize>,
1078 resume_upper: Antichain<T>,
1079 previous: VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
1080 previous_token: Option<Vec<PressOnDropButton>>,
1081 upsert_metrics: UpsertMetrics,
1082 source_config: crate::source::SourceExportCreationConfig,
1083 state: F,
1084 upsert_config: UpsertConfig,
1085 prevent_snapshot_buffering: bool,
1086 snapshot_buffering_max: Option<usize>,
1087) -> (
1088 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
1089 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
1090 StreamVec<'scope, T, Infallible>,
1091 PressOnDropButton,
1092)
1093where
1094 T: Timestamp + TotalOrder + Sync,
1095 F: FnOnce() -> Fut + 'static,
1096 Fut: std::future::Future<Output = US>,
1097 US: UpsertStateBackend<T, FromTime>,
1098 FromTime: timely::ExchangeData + Clone + Ord + Sync,
1099{
1100 let mut builder = AsyncOperatorBuilder::new("Upsert".to_string(), input.scope());
1101
1102 let previous = previous.flat_map(move |result| {
1104 let value = match result {
1105 Ok(ok) => Ok(ok),
1106 Err(DataflowError::EnvelopeError(err)) => match *err {
1107 EnvelopeError::Upsert(err) => Err(Box::new(err)),
1108 EnvelopeError::Flat(_) => return None,
1109 },
1110 Err(_) => return None,
1111 };
1112 let value_ref = match value {
1113 Ok(ref row) => Ok(row),
1114 Err(ref err) => Err(&**err),
1115 };
1116 Some((UpsertKey::from_value(value_ref, &key_indices), value))
1117 });
1118 let (output_handle, output) = builder.new_output();
1119
1120 let (_snapshot_handle, snapshot_stream) =
1123 builder.new_output::<CapacityContainerBuilder<Vec<Infallible>>>();
1124
1125 let (mut health_output, health_stream) = builder.new_output();
1126 let mut input = builder.new_input_for(
1127 input.inner,
1128 Exchange::new(move |((key, _, _), _, _)| UpsertKey::hashed(key)),
1129 &output_handle,
1130 );
1131
1132 let mut previous = builder.new_input_for(
1133 previous.inner,
1134 Exchange::new(|((key, _), _, _)| UpsertKey::hashed(key)),
1135 &output_handle,
1136 );
1137
1138 let upsert_shared_metrics = Arc::clone(&upsert_metrics.shared);
1139 let shutdown_button = builder.build(move |caps| async move {
1140 let [mut output_cap, mut snapshot_cap, health_cap]: [_; 3] = caps.try_into().unwrap();
1141
1142 let mut state = UpsertState::<_, _, FromTime>::new(
1143 state().await,
1144 upsert_shared_metrics,
1145 &upsert_metrics,
1146 source_config.source_statistics.clone(),
1147 upsert_config.shrink_upsert_unused_buffers_by_ratio,
1148 );
1149 let mut events = vec![];
1150 let mut snapshot_upper = Antichain::from_elem(Timestamp::minimum());
1151
1152 let mut stash = vec![];
1153
1154 let mut error_emitter = (&mut health_output, &health_cap);
1155
1156 tracing::info!(
1157 ?resume_upper,
1158 ?snapshot_upper,
1159 "timely-{} upsert source {} starting rehydration",
1160 source_config.worker_id,
1161 source_config.id
1162 );
1163 while !PartialOrder::less_equal(&resume_upper, &snapshot_upper) {
1166 previous.ready().await;
1167 while let Some(event) = previous.next_sync() {
1168 match event {
1169 AsyncEvent::Data(_cap, data) => {
1170 events.extend(data.into_iter().filter_map(|((key, value), ts, diff)| {
1171 if !resume_upper.less_equal(&ts) {
1172 Some((key, value, diff))
1173 } else {
1174 None
1175 }
1176 }))
1177 }
1178 AsyncEvent::Progress(upper) => {
1179 snapshot_upper = upper;
1180 }
1181 };
1182 }
1183
1184 match state
1185 .consolidate_chunk(
1186 events.drain(..),
1187 PartialOrder::less_equal(&resume_upper, &snapshot_upper),
1188 )
1189 .await
1190 {
1191 Ok(_) => {
1192 if let Some(ts) = snapshot_upper.clone().into_option() {
1193 if !resume_upper.less_equal(&ts) {
1197 snapshot_cap.downgrade(&ts);
1198 output_cap.downgrade(&ts);
1199 }
1200 }
1201 }
1202 Err(e) => {
1203 UpsertErrorEmitter::<T>::emit(
1204 &mut error_emitter,
1205 "Failed to rehydrate state".to_string(),
1206 e,
1207 )
1208 .await;
1209 }
1210 }
1211 }
1212
1213 drop(events);
1214 drop(previous_token);
1215 drop(snapshot_cap);
1216
1217 while let Some(_event) = previous.next().await {}
1223
1224 if let Some(ts) = resume_upper.as_option() {
1226 output_cap.downgrade(ts);
1227 }
1228
1229 tracing::info!(
1230 "timely-{} upsert source {} finished rehydration",
1231 source_config.worker_id,
1232 source_config.id
1233 );
1234
1235 let mut commands_state: indexmap::IndexMap<_, types::UpsertValueAndSize<T, FromTime>> =
1238 indexmap::IndexMap::new();
1239 let mut multi_get_scratch = Vec::new();
1240
1241 let mut output_updates = vec![];
1243 let mut input_upper = Antichain::from_elem(Timestamp::minimum());
1244
1245 while let Some(event) = input.next().await {
1246 let events = [event]
1249 .into_iter()
1250 .chain(std::iter::from_fn(|| input.next().now_or_never().flatten()))
1251 .enumerate();
1252
1253 let mut partial_drain_time = None;
1254 for (i, event) in events {
1255 match event {
1256 AsyncEvent::Data(cap, mut data) => {
1257 tracing::trace!(
1258 time=?cap.time(),
1259 updates=%data.len(),
1260 "received data in upsert"
1261 );
1262 stage_input(
1263 &mut stash,
1264 &mut data,
1265 &input_upper,
1266 &resume_upper,
1267 upsert_config.shrink_upsert_unused_buffers_by_ratio,
1268 );
1269
1270 let event_time = cap.time();
1271 if prevent_snapshot_buffering && output_cap.time() == event_time {
1278 partial_drain_time = Some(event_time.clone());
1279 }
1280 }
1281 AsyncEvent::Progress(upper) => {
1282 tracing::trace!(?upper, "received progress in upsert");
1283 if PartialOrder::less_than(&upper, &resume_upper) {
1286 continue;
1287 }
1288
1289 partial_drain_time = None;
1292 drain_staged_input::<_, _, _, _>(
1293 &mut stash,
1294 &mut commands_state,
1295 &mut output_updates,
1296 &mut multi_get_scratch,
1297 DrainStyle::ToUpper(&upper),
1298 &mut error_emitter,
1299 &mut state,
1300 &source_config,
1301 )
1302 .await;
1303
1304 output_handle.give_container(&output_cap, &mut output_updates);
1305
1306 if let Some(ts) = upper.as_option() {
1307 output_cap.downgrade(ts);
1308 }
1309 input_upper = upper;
1310 }
1311 }
1312 let events_processed = i + 1;
1313 if let Some(max) = snapshot_buffering_max {
1314 if events_processed >= max {
1315 break;
1316 }
1317 }
1318 }
1319
1320 if let Some(partial_drain_time) = partial_drain_time {
1329 drain_staged_input::<_, _, _, _>(
1330 &mut stash,
1331 &mut commands_state,
1332 &mut output_updates,
1333 &mut multi_get_scratch,
1334 DrainStyle::AtTime(partial_drain_time),
1335 &mut error_emitter,
1336 &mut state,
1337 &source_config,
1338 )
1339 .await;
1340
1341 output_handle.give_container(&output_cap, &mut output_updates);
1342 }
1343 }
1344 });
1345
1346 (
1347 output.as_collection().map(|result| match result {
1348 Ok(ok) => Ok(ok),
1349 Err(err) => Err(DataflowError::from(EnvelopeError::Upsert(*err))),
1350 }),
1351 health_stream,
1352 snapshot_stream,
1353 shutdown_button.press_on_drop(),
1354 )
1355}
1356
1357#[async_trait::async_trait(?Send)]
1358pub(crate) trait UpsertErrorEmitter<T> {
1359 async fn emit(&mut self, context: String, e: anyhow::Error);
1360}
1361
1362#[async_trait::async_trait(?Send)]
1363impl<T: Timestamp> UpsertErrorEmitter<T>
1364 for (
1365 &mut AsyncOutputHandle<
1366 T,
1367 CapacityContainerBuilder<Vec<(Option<GlobalId>, HealthStatusUpdate)>>,
1368 >,
1369 &Capability<T>,
1370 )
1371{
1372 async fn emit(&mut self, context: String, e: anyhow::Error) {
1373 process_upsert_state_error::<T>(context, e, self.0, self.1).await
1374 }
1375}
1376
1377async fn process_upsert_state_error<T: Timestamp>(
1379 context: String,
1380 e: anyhow::Error,
1381 health_output: &AsyncOutputHandle<
1382 T,
1383 CapacityContainerBuilder<Vec<(Option<GlobalId>, HealthStatusUpdate)>>,
1384 >,
1385 health_cap: &Capability<T>,
1386) {
1387 let update = HealthStatusUpdate::halting(e.context(context).to_string_with_causes(), None);
1388 health_output.give(health_cap, (None, update));
1389 std::future::pending::<()>().await;
1390 unreachable!("pending future never returns");
1391}