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, sole_capability,
38};
39use serde::{Deserialize, Serialize};
40use sha2::{Digest, Sha256};
41use timely::dataflow::channels::pact::Exchange;
42use timely::dataflow::operators::{Capability, 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: mz_timely_util::columnar::chunk::PoolColumnar
243 + Clone
244 + Default
245 + Ord
246 + Send
247 + Sync
248 + 'static;
249 fn upsert_order(&self) -> Self::Order;
251}
252
253impl UpsertSourceTime for KafkaTimestamp {
254 type Order = (i64, u64);
260 fn upsert_order(&self) -> (i64, u64) {
261 let partition = match self.interval().lower {
262 RangeBound::NegInfinity => i64::MIN,
263 RangeBound::Elem(p, _) => i64::from(p),
264 RangeBound::PosInfinity => i64::MAX,
265 };
266 (partition, self.timestamp().offset)
267 }
268}
269
270impl UpsertSourceTime for MzOffset {
276 type Order = u64;
277 fn upsert_order(&self) -> u64 {
278 self.offset
279 }
280}
281
282macro_rules! upsert_source_time_unit {
289 ($($ty:ty),+ $(,)?) => {$(
290 impl UpsertSourceTime for $ty {
291 type Order = ();
292 fn upsert_order(&self) {
293 unreachable!(
294 "upsert source stash is not rendered for this source, but \
295 {} reached the projection",
296 std::any::type_name::<Self>(),
297 )
298 }
299 }
300 )+};
301}
302upsert_source_time_unit!(GtidPartition, Lsn);
303
304pub 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 key_persist_feedback<'scope, T>(
502 ok: VecCollection<'scope, T, Row, Diff>,
503 err: VecCollection<'scope, T, DataflowError, Diff>,
504 key_indices: Vec<usize>,
505) -> VecCollection<'scope, T, (UpsertKey, UpsertValue), Diff>
506where
507 T: Timestamp,
508{
509 let keyed_ok = {
510 let key_indices = key_indices.clone();
511 ok.map(move |row| {
512 let key = UpsertKey::from_value(Ok(&row), &key_indices);
513 (key, Ok(row))
514 })
515 };
516 let keyed_err = err.flat_map(move |err| {
517 let err = match err {
518 DataflowError::EnvelopeError(err) => match *err {
519 EnvelopeError::Upsert(err) => Box::new(err),
520 EnvelopeError::Flat(_) => return None,
521 },
522 _ => return None,
523 };
524 let key = UpsertKey::from_value(Err(&err), &key_indices);
525 Some((key, Err(err)))
526 });
527 keyed_ok.concat(keyed_err)
528}
529
530pub(crate) fn upsert<'scope, T, FromTime>(
536 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
537 upsert_envelope: UpsertEnvelope,
538 resume_upper: Antichain<T>,
539 previous_ok: VecCollection<'scope, T, Row, Diff>,
540 previous_err: VecCollection<'scope, T, DataflowError, Diff>,
541 previous_token: Option<Vec<PressOnDropButton>>,
542 source_config: crate::source::SourceExportCreationConfig,
543 instance_context: &StorageInstanceContext,
544 storage_configuration: &StorageConfiguration,
545 dataflow_paramters: &crate::internal_control::DataflowParameters,
546 backpressure_metrics: Option<UpsertBackpressureMetrics>,
547) -> (
548 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
549 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
550 StreamVec<'scope, T, Infallible>,
551 PressOnDropButton,
552)
553where
554 T: Timestamp + TotalOrder + Sync,
555 T: Refines<mz_repr::Timestamp> + TotalOrder + Sync,
556 FromTime: Timestamp + Clone + Sync,
557{
558 let upsert_metrics = source_config.metrics.get_upsert_metrics(
559 source_config.id,
560 source_config.worker_id,
561 backpressure_metrics,
562 );
563
564 let rocksdb_cleanup_tries =
565 dyncfgs::STORAGE_ROCKSDB_CLEANUP_TRIES.get(storage_configuration.config_set());
566
567 let prevent_snapshot_buffering =
570 dyncfgs::STORAGE_UPSERT_PREVENT_SNAPSHOT_BUFFERING.get(storage_configuration.config_set());
571 let snapshot_buffering_max = dyncfgs::STORAGE_UPSERT_MAX_SNAPSHOT_BATCH_BUFFERING
573 .get(storage_configuration.config_set());
574
575 let rocksdb_use_native_merge_operator =
578 dyncfgs::STORAGE_ROCKSDB_USE_MERGE_OPERATOR.get(storage_configuration.config_set());
579
580 let upsert_config = UpsertConfig {
581 shrink_upsert_unused_buffers_by_ratio: storage_configuration
582 .parameters
583 .shrink_upsert_unused_buffers_by_ratio,
584 };
585
586 let thin_input = upsert_thinning(input);
587
588 let tuning = dataflow_paramters.upsert_rocksdb_tuning_config.clone();
589
590 let rocksdb_dir = instance_context
595 .scratch_directory
596 .clone()
597 .unwrap_or_else(|| PathBuf::from("/tmp"))
598 .join("storage")
599 .join("upsert")
600 .join(source_config.id.to_string())
601 .join(source_config.worker_id.to_string());
602
603 tracing::info!(
604 worker_id = %source_config.worker_id,
605 source_id = %source_config.id,
606 ?rocksdb_dir,
607 ?tuning,
608 ?rocksdb_use_native_merge_operator,
609 "rendering upsert source"
610 );
611
612 let rocksdb_shared_metrics = Arc::clone(&upsert_metrics.rocksdb_shared);
613 let rocksdb_instance_metrics = Arc::clone(&upsert_metrics.rocksdb_instance_metrics);
614
615 let env = instance_context
616 .rocksdb_env()
617 .expect("failed to create rocksdb env");
618
619 let rocksdb_init_fn = move || async move {
621 let merge_operator = if rocksdb_use_native_merge_operator {
622 Some((
623 "upsert_state_snapshot_merge_v1".to_string(),
624 |a: &[u8], b: ValueIterator<BincodeOpts, StateValue<T, FromTime>>| {
625 consolidating_merge_function::<T, FromTime>(a.into(), b)
626 },
627 ))
628 } else {
629 None
630 };
631 rocksdb::RocksDB::new(
632 mz_rocksdb::RocksDBInstance::new(
633 &rocksdb_dir,
634 mz_rocksdb::InstanceOptions::new(
635 env,
636 rocksdb_cleanup_tries,
637 merge_operator,
638 upsert_bincode_opts(),
641 ),
642 tuning,
643 rocksdb_shared_metrics,
644 rocksdb_instance_metrics,
645 )
646 .unwrap(),
647 )
648 };
649
650 upsert_operator(
651 thin_input,
652 upsert_envelope.key_indices,
653 resume_upper,
654 previous_ok,
655 previous_err,
656 previous_token,
657 upsert_metrics,
658 source_config,
659 rocksdb_init_fn,
660 upsert_config,
661 storage_configuration,
662 prevent_snapshot_buffering,
663 snapshot_buffering_max,
664 )
665}
666
667pub(crate) fn upsert_v2<'scope, T, FromTime>(
674 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
675 upsert_envelope: UpsertEnvelope,
676 resume_upper: Antichain<T>,
677 previous_ok: VecCollection<'scope, T, Row, Diff>,
678 previous_err: VecCollection<'scope, T, DataflowError, Diff>,
679 previous_token: Option<Vec<PressOnDropButton>>,
680 source_config: crate::source::SourceExportCreationConfig,
681 backpressure_metrics: Option<UpsertBackpressureMetrics>,
682 stash_flavor: upsert_continual_feedback_v2::UpsertStashFlavor,
683) -> (
684 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
685 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
686 StreamVec<'scope, T, Infallible>,
687 PressOnDropButton,
688)
689where
690 T: Timestamp + TotalOrder + Sync,
691 T: Refines<mz_repr::Timestamp> + differential_dataflow::lattice::Lattice,
692 T: columnation::Columnation,
693 T: mz_timely_util::columnar::chunk::PoolColumnar + Default,
694 for<'a> columnar::Ref<'a, T>: Copy + Ord,
695 FromTime: Timestamp + Clone + Sync,
696 FromTime: UpsertSourceTime,
697{
698 let upsert_metrics = source_config.metrics.get_upsert_metrics(
699 source_config.id,
700 source_config.worker_id,
701 backpressure_metrics,
702 );
703
704 let thin_input = upsert_thinning(input);
705
706 tracing::info!(
707 worker_id = %source_config.worker_id,
708 source_id = %source_config.id,
709 ?stash_flavor,
710 "rendering upsert source (btreemap backend)"
711 );
712
713 upsert_continual_feedback_v2::upsert_inner(
714 stash_flavor,
715 thin_input,
716 upsert_envelope.key_indices,
717 resume_upper,
718 previous_ok,
719 previous_err,
720 previous_token,
721 upsert_metrics,
722 source_config,
723 )
724}
725
726fn upsert_operator<'scope, T, FromTime, F, Fut, US>(
729 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
730 key_indices: Vec<usize>,
731 resume_upper: Antichain<T>,
732 persist_ok: VecCollection<'scope, T, Row, Diff>,
733 persist_err: VecCollection<'scope, T, DataflowError, Diff>,
734 persist_token: Option<Vec<PressOnDropButton>>,
735 upsert_metrics: UpsertMetrics,
736 source_config: crate::source::SourceExportCreationConfig,
737 state: F,
738 upsert_config: UpsertConfig,
739 _storage_configuration: &StorageConfiguration,
740 prevent_snapshot_buffering: bool,
741 snapshot_buffering_max: Option<usize>,
742) -> (
743 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
744 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
745 StreamVec<'scope, T, Infallible>,
746 PressOnDropButton,
747)
748where
749 T: Timestamp + TotalOrder + Sync,
750 T: Refines<mz_repr::Timestamp> + TotalOrder + Sync,
751 F: FnOnce() -> Fut + 'static,
752 Fut: std::future::Future<Output = US>,
753 US: UpsertStateBackend<T, FromTime>,
754 FromTime: Debug + timely::ExchangeData + Clone + Ord + Sync,
755{
756 let use_continual_feedback_upsert = true;
760
761 tracing::info!(id = %source_config.id, %use_continual_feedback_upsert, "upsert operator implementation");
762
763 if use_continual_feedback_upsert {
764 upsert_continual_feedback::upsert_inner(
765 input,
766 key_indices,
767 resume_upper,
768 persist_ok,
769 persist_err,
770 persist_token,
771 upsert_metrics,
772 source_config,
773 state,
774 upsert_config,
775 prevent_snapshot_buffering,
776 snapshot_buffering_max,
777 )
778 } else {
779 upsert_classic(
780 input,
781 key_indices,
782 resume_upper,
783 persist_ok,
784 persist_err,
785 persist_token,
786 upsert_metrics,
787 source_config,
788 state,
789 upsert_config,
790 prevent_snapshot_buffering,
791 snapshot_buffering_max,
792 )
793 }
794}
795
796fn upsert_thinning<'scope, T, K, V, FromTime>(
801 input: VecCollection<'scope, T, (K, V, FromTime), Diff>,
802) -> VecCollection<'scope, T, (K, V, FromTime), Diff>
803where
804 T: Timestamp + TotalOrder,
805 K: timely::ExchangeData + Clone + Eq + Ord,
806 V: timely::ExchangeData + Clone,
807 FromTime: Timestamp,
808{
809 input
810 .inner
811 .unary(Pipeline, "UpsertThinning", |_, _| {
812 let mut capability: Option<Capability<T>> = None;
814 let mut updates = Vec::new();
816 move |input, output| {
817 input.for_each(|cap, data| {
818 assert!(
819 data.iter().all(|(_, _, diff)| diff.is_positive()),
820 "invalid upsert input"
821 );
822 updates.append(data);
823 let cap = cap
826 .retain_least(0)
827 .expect("message stamped with no capabilities");
828 match capability.as_mut() {
829 Some(capability) => {
830 if cap.time() <= capability.time() {
831 *capability = cap;
832 }
833 }
834 None => capability = Some(cap),
835 }
836 });
837 if let Some(capability) = capability.take() {
838 updates.sort_unstable_by(|a, b| {
841 let ((key1, _, from_time1), time1, _) = a;
842 let ((key2, _, from_time2), time2, _) = b;
843 Ord::cmp(
844 &(key1, time1, Reverse(from_time1)),
845 &(key2, time2, Reverse(from_time2)),
846 )
847 });
848 let mut session = output.session(&capability);
849 session.give_iterator(updates.drain(..).dedup_by(|a, b| {
850 let ((key1, _, _), time1, _) = a;
851 let ((key2, _, _), time2, _) = b;
852 (key1, time1) == (key2, time2)
853 }))
854 }
855 }
856 })
857 .as_collection()
858}
859
860fn stage_input<T, FromTime>(
863 stash: &mut Vec<(T, UpsertKey, Reverse<FromTime>, Option<UpsertValue>)>,
864 data: &mut Vec<((UpsertKey, Option<UpsertValue>, FromTime), T, Diff)>,
865 input_upper: &Antichain<T>,
866 resume_upper: &Antichain<T>,
867 storage_shrink_upsert_unused_buffers_by_ratio: usize,
868) where
869 T: PartialOrder,
870 FromTime: Ord,
871{
872 if PartialOrder::less_equal(input_upper, resume_upper) {
873 data.retain(|(_, ts, _)| resume_upper.less_equal(ts));
874 }
875
876 stash.extend(data.drain(..).map(|((key, value, order), time, diff)| {
877 assert!(diff.is_positive(), "invalid upsert input");
878 (time, key, Reverse(order), value)
879 }));
880
881 if storage_shrink_upsert_unused_buffers_by_ratio > 0 {
882 let reduced_capacity = stash.capacity() / storage_shrink_upsert_unused_buffers_by_ratio;
883 if reduced_capacity > stash.len() {
884 stash.shrink_to(reduced_capacity);
885 }
886 }
887}
888
889#[derive(Debug)]
892enum DrainStyle<'a, T> {
893 ToUpper(&'a Antichain<T>),
894 AtTime(T),
895}
896
897async fn drain_staged_input<S, T, FromTime, E>(
900 stash: &mut Vec<(T, UpsertKey, Reverse<FromTime>, Option<UpsertValue>)>,
901 commands_state: &mut indexmap::IndexMap<UpsertKey, types::UpsertValueAndSize<T, FromTime>>,
902 output_updates: &mut Vec<(UpsertValue, T, Diff)>,
903 multi_get_scratch: &mut Vec<UpsertKey>,
904 drain_style: DrainStyle<'_, T>,
905 error_emitter: &mut E,
906 state: &mut UpsertState<'_, S, T, FromTime>,
907 source_config: &crate::source::SourceExportCreationConfig,
908) where
909 S: UpsertStateBackend<T, FromTime>,
910 T: PartialOrder + Ord + Clone + Send + Sync + Serialize + Debug + 'static,
911 FromTime: timely::ExchangeData + Clone + Ord + Sync,
912 E: UpsertErrorEmitter<T>,
913{
914 stash.sort_unstable();
915
916 let idx = stash.partition_point(|(ts, _, _, _)| match &drain_style {
918 DrainStyle::ToUpper(upper) => !upper.less_equal(ts),
919 DrainStyle::AtTime(time) => ts <= time,
920 });
921
922 tracing::trace!(?drain_style, updates = idx, "draining stash in upsert");
923
924 commands_state.clear();
927 for (_, key, _, _) in stash.iter().take(idx) {
928 commands_state.entry(*key).or_default();
929 }
930
931 multi_get_scratch.clear();
934 multi_get_scratch.extend(commands_state.iter().map(|(k, _)| *k));
935 match state
936 .multi_get(multi_get_scratch.drain(..), commands_state.values_mut())
937 .await
938 {
939 Ok(_) => {}
940 Err(e) => {
941 error_emitter
942 .emit("Failed to fetch records from state".to_string(), e)
943 .await;
944 }
945 }
946
947 let mut commands = stash.drain(..idx).dedup_by(|a, b| {
951 let ((a_ts, a_key, _, _), (b_ts, b_key, _, _)) = (a, b);
952 a_ts == b_ts && a_key == b_key
953 });
954
955 let bincode_opts = types::upsert_bincode_opts();
956 while let Some((ts, key, from_time, value)) = commands.next() {
969 let mut command_state = if let Entry::Occupied(command_state) = commands_state.entry(key) {
970 command_state
971 } else {
972 panic!("key missing from commands_state");
973 };
974
975 let existing_value = &mut command_state.get_mut().value;
976
977 if let Some(cs) = existing_value.as_mut() {
978 cs.ensure_decoded(bincode_opts, source_config.id, Some(&key));
979 }
980
981 let existing_order = existing_value
985 .as_ref()
986 .and_then(|cs| cs.provisional_order(&ts));
987 if existing_order >= Some(&from_time.0) {
988 continue;
993 }
994
995 match value {
996 Some(value) => {
997 if let Some(old_value) =
998 existing_value.replace(StateValue::finalized_value(value.clone()))
999 {
1000 if let Some(old_value) = old_value.into_decoded().finalized {
1001 output_updates.push((old_value, ts.clone(), Diff::MINUS_ONE));
1002 }
1003 }
1004 output_updates.push((value, ts, Diff::ONE));
1005 }
1006 None => {
1007 if let Some(old_value) = existing_value.take() {
1008 if let Some(old_value) = old_value.into_decoded().finalized {
1009 output_updates.push((old_value, ts, Diff::MINUS_ONE));
1010 }
1011 }
1012
1013 *existing_value = Some(StateValue::tombstone());
1015 }
1016 }
1017 }
1018
1019 match state
1020 .multi_put(
1021 true, commands_state.drain(..).map(|(k, cv)| {
1023 (
1024 k,
1025 types::PutValue {
1026 value: cv.value.map(|cv| cv.into_decoded()),
1027 previous_value_metadata: cv.metadata.map(|v| ValueMetadata {
1028 size: v.size.try_into().expect("less than i64 size"),
1029 is_tombstone: v.is_tombstone,
1030 }),
1031 },
1032 )
1033 }),
1034 )
1035 .await
1036 {
1037 Ok(_) => {}
1038 Err(e) => {
1039 error_emitter
1040 .emit("Failed to update records in state".to_string(), e)
1041 .await;
1042 }
1043 }
1044}
1045
1046#[cfg(feature = "fuzzing")]
1050struct PanicErrorEmitter;
1051
1052#[cfg(feature = "fuzzing")]
1053#[async_trait::async_trait(?Send)]
1054impl<T> UpsertErrorEmitter<T> for PanicErrorEmitter {
1055 async fn emit(&mut self, context: String, e: anyhow::Error) {
1056 panic!("unexpected upsert state error during fuzzing: {context}: {e}");
1057 }
1058}
1059
1060#[cfg(feature = "fuzzing")]
1067pub async fn fuzz_drain_staged_input(
1068 parts: &types::FuzzUpsertParts,
1069 source_config: &crate::source::SourceExportCreationConfig,
1070 commands: Vec<(u64, UpsertKey, u64, Option<UpsertValue>)>,
1071 drain_to: u64,
1072 all_keys: &[UpsertKey],
1073) -> (Vec<(UpsertValue, u64, Diff)>, Vec<Option<UpsertValue>>) {
1074 let mut state = parts.state();
1075 let mut stash: Vec<(u64, UpsertKey, Reverse<u64>, Option<UpsertValue>)> = commands
1076 .into_iter()
1077 .map(|(ts, key, order, value)| (ts, key, Reverse(order), value))
1078 .collect();
1079 let mut commands_state = indexmap::IndexMap::new();
1080 let mut output = Vec::new();
1081 let mut multi_get_scratch = Vec::new();
1082 let mut emitter = PanicErrorEmitter;
1083
1084 drain_staged_input(
1085 &mut stash,
1086 &mut commands_state,
1087 &mut output,
1088 &mut multi_get_scratch,
1089 DrainStyle::ToUpper(&Antichain::from_elem(drain_to)),
1090 &mut emitter,
1091 &mut state,
1092 source_config,
1093 )
1094 .await;
1095
1096 let bincode_opts = types::upsert_bincode_opts();
1097 let mut results = vec![types::UpsertValueAndSize::default(); all_keys.len()];
1098 state
1099 .multi_get(all_keys.iter().copied(), results.iter_mut())
1100 .await
1101 .expect("multi_get in fuzz hook should not error");
1102 let final_state = results
1103 .into_iter()
1104 .map(|r| match r.value {
1105 None => None,
1106 Some(mut sv) => {
1107 sv.ensure_decoded(bincode_opts, GlobalId::User(0), None);
1108 sv.into_decoded().finalized
1109 }
1110 })
1111 .collect();
1112
1113 (output, final_state)
1114}
1115
1116pub(crate) struct UpsertConfig {
1119 pub shrink_upsert_unused_buffers_by_ratio: usize,
1120}
1121
1122fn upsert_classic<'scope, T, FromTime, F, Fut, US>(
1123 input: VecCollection<'scope, T, (UpsertKey, Option<UpsertValue>, FromTime), Diff>,
1124 key_indices: Vec<usize>,
1125 resume_upper: Antichain<T>,
1126 previous_ok: VecCollection<'scope, T, Row, Diff>,
1127 previous_err: VecCollection<'scope, T, DataflowError, Diff>,
1128 previous_token: Option<Vec<PressOnDropButton>>,
1129 upsert_metrics: UpsertMetrics,
1130 source_config: crate::source::SourceExportCreationConfig,
1131 state: F,
1132 upsert_config: UpsertConfig,
1133 prevent_snapshot_buffering: bool,
1134 snapshot_buffering_max: Option<usize>,
1135) -> (
1136 VecCollection<'scope, T, Result<Row, DataflowError>, Diff>,
1137 StreamVec<'scope, T, (Option<GlobalId>, HealthStatusUpdate)>,
1138 StreamVec<'scope, T, Infallible>,
1139 PressOnDropButton,
1140)
1141where
1142 T: Timestamp + TotalOrder + Sync,
1143 F: FnOnce() -> Fut + 'static,
1144 Fut: std::future::Future<Output = US>,
1145 US: UpsertStateBackend<T, FromTime>,
1146 FromTime: timely::ExchangeData + Clone + Ord + Sync,
1147{
1148 let mut builder = AsyncOperatorBuilder::new("Upsert".to_string(), input.scope());
1149
1150 let previous = key_persist_feedback(previous_ok, previous_err, key_indices);
1151 let (output_handle, output) = builder.new_output();
1152
1153 let (_snapshot_handle, snapshot_stream) =
1156 builder.new_output::<CapacityContainerBuilder<Vec<Infallible>>>();
1157
1158 let (mut health_output, health_stream) = builder.new_output();
1159 let mut input = builder.new_input_for(
1160 input.inner,
1161 Exchange::new(move |((key, _, _), _, _)| UpsertKey::hashed(key)),
1162 &output_handle,
1163 );
1164
1165 let mut previous = builder.new_input_for(
1166 previous.inner,
1167 Exchange::new(|((key, _), _, _)| UpsertKey::hashed(key)),
1168 &output_handle,
1169 );
1170
1171 let upsert_shared_metrics = Arc::clone(&upsert_metrics.shared);
1172 let shutdown_button = builder.build(move |caps| async move {
1173 let [mut output_cap, mut snapshot_cap, health_cap]: [_; 3] = caps.try_into().unwrap();
1174
1175 let mut state = UpsertState::<_, _, FromTime>::new(
1176 state().await,
1177 upsert_shared_metrics,
1178 &upsert_metrics,
1179 source_config.source_statistics.clone(),
1180 upsert_config.shrink_upsert_unused_buffers_by_ratio,
1181 );
1182 let mut events = vec![];
1183 let mut snapshot_upper = Antichain::from_elem(Timestamp::minimum());
1184
1185 let mut stash = vec![];
1186
1187 let mut error_emitter = (&mut health_output, &health_cap);
1188
1189 tracing::info!(
1190 ?resume_upper,
1191 ?snapshot_upper,
1192 "timely-{} upsert source {} starting rehydration",
1193 source_config.worker_id,
1194 source_config.id
1195 );
1196 while !PartialOrder::less_equal(&resume_upper, &snapshot_upper) {
1199 previous.ready().await;
1200 while let Some(event) = previous.next_sync() {
1201 match event {
1202 AsyncEvent::Data(_cap, data) => {
1203 events.extend(data.into_iter().filter_map(|((key, value), ts, diff)| {
1204 if !resume_upper.less_equal(&ts) {
1205 Some((key, value, diff))
1206 } else {
1207 None
1208 }
1209 }))
1210 }
1211 AsyncEvent::Progress(upper) => {
1212 snapshot_upper = upper;
1213 }
1214 };
1215 }
1216
1217 match state
1218 .consolidate_chunk(
1219 events.drain(..),
1220 PartialOrder::less_equal(&resume_upper, &snapshot_upper),
1221 )
1222 .await
1223 {
1224 Ok(_) => {
1225 if let Some(ts) = snapshot_upper.clone().into_option() {
1226 if !resume_upper.less_equal(&ts) {
1230 snapshot_cap.downgrade(&ts);
1231 output_cap.downgrade(&ts);
1232 }
1233 }
1234 }
1235 Err(e) => {
1236 UpsertErrorEmitter::<T>::emit(
1237 &mut error_emitter,
1238 "Failed to rehydrate state".to_string(),
1239 e,
1240 )
1241 .await;
1242 }
1243 }
1244 }
1245
1246 drop(events);
1247 drop(previous_token);
1248 drop(snapshot_cap);
1249
1250 while let Some(_event) = previous.next().await {}
1256
1257 if let Some(ts) = resume_upper.as_option() {
1259 output_cap.downgrade(ts);
1260 }
1261
1262 tracing::info!(
1263 "timely-{} upsert source {} finished rehydration",
1264 source_config.worker_id,
1265 source_config.id
1266 );
1267
1268 let mut commands_state: indexmap::IndexMap<_, types::UpsertValueAndSize<T, FromTime>> =
1271 indexmap::IndexMap::new();
1272 let mut multi_get_scratch = Vec::new();
1273
1274 let mut output_updates = vec![];
1276 let mut input_upper = Antichain::from_elem(Timestamp::minimum());
1277
1278 while let Some(event) = input.next().await {
1279 let events = [event]
1282 .into_iter()
1283 .chain(std::iter::from_fn(|| input.next().now_or_never().flatten()))
1284 .enumerate();
1285
1286 let mut partial_drain_time = None;
1287 for (i, event) in events {
1288 match event {
1289 AsyncEvent::Data(cap, mut data) => {
1290 let cap = sole_capability(&cap).clone();
1291 tracing::trace!(
1292 time=?cap.time(),
1293 updates=%data.len(),
1294 "received data in upsert"
1295 );
1296 stage_input(
1297 &mut stash,
1298 &mut data,
1299 &input_upper,
1300 &resume_upper,
1301 upsert_config.shrink_upsert_unused_buffers_by_ratio,
1302 );
1303
1304 let event_time = cap.time();
1305 if prevent_snapshot_buffering && output_cap.time() == event_time {
1312 partial_drain_time = Some(event_time.clone());
1313 }
1314 }
1315 AsyncEvent::Progress(upper) => {
1316 tracing::trace!(?upper, "received progress in upsert");
1317 if PartialOrder::less_than(&upper, &resume_upper) {
1320 continue;
1321 }
1322
1323 partial_drain_time = None;
1326 drain_staged_input::<_, _, _, _>(
1327 &mut stash,
1328 &mut commands_state,
1329 &mut output_updates,
1330 &mut multi_get_scratch,
1331 DrainStyle::ToUpper(&upper),
1332 &mut error_emitter,
1333 &mut state,
1334 &source_config,
1335 )
1336 .await;
1337
1338 output_handle.give_container(&output_cap, &mut output_updates);
1339
1340 if let Some(ts) = upper.as_option() {
1341 output_cap.downgrade(ts);
1342 }
1343 input_upper = upper;
1344 }
1345 }
1346 let events_processed = i + 1;
1347 if let Some(max) = snapshot_buffering_max {
1348 if events_processed >= max {
1349 break;
1350 }
1351 }
1352 }
1353
1354 if let Some(partial_drain_time) = partial_drain_time {
1363 drain_staged_input::<_, _, _, _>(
1364 &mut stash,
1365 &mut commands_state,
1366 &mut output_updates,
1367 &mut multi_get_scratch,
1368 DrainStyle::AtTime(partial_drain_time),
1369 &mut error_emitter,
1370 &mut state,
1371 &source_config,
1372 )
1373 .await;
1374
1375 output_handle.give_container(&output_cap, &mut output_updates);
1376 }
1377 }
1378 });
1379
1380 (
1381 output.as_collection().map(|result| match result {
1382 Ok(ok) => Ok(ok),
1383 Err(err) => Err(DataflowError::from(EnvelopeError::Upsert(*err))),
1384 }),
1385 health_stream,
1386 snapshot_stream,
1387 shutdown_button.press_on_drop(),
1388 )
1389}
1390
1391#[async_trait::async_trait(?Send)]
1392pub(crate) trait UpsertErrorEmitter<T> {
1393 async fn emit(&mut self, context: String, e: anyhow::Error);
1394}
1395
1396#[async_trait::async_trait(?Send)]
1397impl<T: Timestamp> UpsertErrorEmitter<T>
1398 for (
1399 &mut AsyncOutputHandle<
1400 T,
1401 CapacityContainerBuilder<Vec<(Option<GlobalId>, HealthStatusUpdate)>>,
1402 >,
1403 &Capability<T>,
1404 )
1405{
1406 async fn emit(&mut self, context: String, e: anyhow::Error) {
1407 process_upsert_state_error::<T>(context, e, self.0, self.1).await
1408 }
1409}
1410
1411async fn process_upsert_state_error<T: Timestamp>(
1413 context: String,
1414 e: anyhow::Error,
1415 health_output: &AsyncOutputHandle<
1416 T,
1417 CapacityContainerBuilder<Vec<(Option<GlobalId>, HealthStatusUpdate)>>,
1418 >,
1419 health_cap: &Capability<T>,
1420) {
1421 let update = HealthStatusUpdate::halting(e.context(context).to_string_with_causes(), None);
1422 health_output.give(health_cap, (None, update));
1423 std::future::pending::<()>().await;
1424 unreachable!("pending future never returns");
1425}