1use std::borrow::Borrow;
13use std::fmt::Debug;
14use std::sync::Arc;
15
16use differential_dataflow::difference::Monoid;
17use differential_dataflow::lattice::Lattice;
18use differential_dataflow::trace::Description;
19use futures::StreamExt;
20use futures::stream::FuturesUnordered;
21use mz_dyncfg::Config;
22use mz_ore::task::RuntimeExt;
23use mz_ore::{instrument, soft_panic_or_log};
24use mz_persist::location::Blob;
25use mz_persist_types::schema::SchemaId;
26use mz_persist_types::{Codec, Codec64};
27use mz_proto::{IntoRustIfSome, ProtoType};
28use proptest_derive::Arbitrary;
29use semver::Version;
30use serde::{Deserialize, Serialize};
31use timely::PartialOrder;
32use timely::order::TotalOrder;
33use timely::progress::{Antichain, Timestamp};
34use tokio::runtime::Handle;
35use tracing::{Instrument, debug_span, error, info, warn};
36use uuid::Uuid;
37
38use crate::batch::{
39 Added, BATCH_DELETE_ENABLED, Batch, BatchBuilder, BatchBuilderConfig, BatchBuilderInternal,
40 BatchParts, ProtoBatch, validate_truncate_batch,
41};
42use crate::error::{InvalidUsage, UpperMismatch};
43use crate::fetch::{
44 EncodedPart, FetchBatchFilter, FetchedPart, PartDecodeFormat, VALIDATE_PART_BOUNDS_ON_READ,
45};
46use crate::internal::compact::{CompactConfig, Compactor};
47use crate::internal::encoding::{Schemas, assert_code_can_read_data};
48use crate::internal::machine::{
49 CompareAndAppendRes, ExpireFn, Machine, next_listen_batch_retry_params,
50};
51use crate::internal::metrics::{BatchWriteMetrics, Metrics, ShardMetrics};
52use crate::internal::state::{BatchPart, HandleDebugState, HollowBatch, RunOrder, RunPart};
53use crate::read::ReadHandle;
54use crate::schema::PartMigration;
55use crate::{GarbageCollector, IsolatedRuntime, PersistConfig, ShardId, parse_id};
56
57pub(crate) const COMBINE_INLINE_WRITES: Config<bool> = Config::new(
58 "persist_write_combine_inline_writes",
59 true,
60 "If set, re-encode inline writes if they don't fit into the batch metadata limits.",
61);
62
63pub(crate) const VALIDATE_PART_BOUNDS_ON_WRITE: Config<bool> = Config::new(
64 "persist_validate_part_bounds_on_write",
65 false,
66 "Validate the part lower <= the batch lower and the part upper <= batch upper,\
67 for the batch being appended.",
68);
69
70#[derive(
72 Arbitrary,
73 Clone,
74 PartialEq,
75 Eq,
76 PartialOrd,
77 Ord,
78 Hash,
79 Serialize,
80 Deserialize
81)]
82#[serde(try_from = "String", into = "String")]
83pub struct WriterId(pub(crate) [u8; 16]);
84
85impl std::fmt::Display for WriterId {
86 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87 write!(f, "w{}", Uuid::from_bytes(self.0))
88 }
89}
90
91impl std::fmt::Debug for WriterId {
92 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 write!(f, "WriterId({})", Uuid::from_bytes(self.0))
94 }
95}
96
97impl std::str::FromStr for WriterId {
98 type Err = String;
99
100 fn from_str(s: &str) -> Result<Self, Self::Err> {
101 parse_id("w", "WriterId", s).map(WriterId)
102 }
103}
104
105impl From<WriterId> for String {
106 fn from(writer_id: WriterId) -> Self {
107 writer_id.to_string()
108 }
109}
110
111impl TryFrom<String> for WriterId {
112 type Error = String;
113
114 fn try_from(s: String) -> Result<Self, Self::Error> {
115 s.parse()
116 }
117}
118
119impl WriterId {
120 pub(crate) fn new() -> Self {
121 WriterId(*Uuid::new_v4().as_bytes())
122 }
123}
124
125#[derive(Debug)]
141pub struct WriteHandle<K: Codec, V: Codec, T, D> {
142 pub(crate) cfg: PersistConfig,
143 pub(crate) metrics: Arc<Metrics>,
144 pub(crate) machine: Machine<K, V, T, D>,
145 pub(crate) gc: GarbageCollector<K, V, T, D>,
146 pub(crate) compact: Option<Compactor<K, V, T, D>>,
147 pub(crate) blob: Arc<dyn Blob>,
148 pub(crate) isolated_runtime: Arc<IsolatedRuntime>,
149 pub(crate) writer_id: WriterId,
150 pub(crate) debug_state: HandleDebugState,
151 pub(crate) write_schemas: Schemas<K, V>,
152
153 pub(crate) upper: Antichain<T>,
154 expire_fn: Option<ExpireFn>,
155}
156
157impl<K, V, T, D> WriteHandle<K, V, T, D>
158where
159 K: Debug + Codec,
160 V: Debug + Codec,
161 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
162 D: Monoid + Ord + Codec64 + Send + Sync,
163{
164 pub(crate) fn new(
165 cfg: PersistConfig,
166 metrics: Arc<Metrics>,
167 machine: Machine<K, V, T, D>,
168 gc: GarbageCollector<K, V, T, D>,
169 blob: Arc<dyn Blob>,
170 writer_id: WriterId,
171 purpose: &str,
172 write_schemas: Schemas<K, V>,
173 ) -> Self {
174 let isolated_runtime = Arc::clone(&machine.isolated_runtime);
175 let compact = cfg
176 .compaction_enabled
177 .then(|| Compactor::new(cfg.clone(), Arc::clone(&metrics), gc.clone()));
178 let debug_state = HandleDebugState {
179 hostname: cfg.hostname.to_owned(),
180 purpose: purpose.to_owned(),
181 };
182 let upper = machine.applier.clone_upper();
183 let expire_fn = Self::expire_fn(machine.clone(), gc.clone(), writer_id.clone());
184 WriteHandle {
185 cfg,
186 metrics,
187 machine,
188 gc,
189 compact,
190 blob,
191 isolated_runtime,
192 writer_id,
193 debug_state,
194 write_schemas,
195 upper,
196 expire_fn: Some(expire_fn),
197 }
198 }
199
200 pub fn from_read(read: &ReadHandle<K, V, T, D>, purpose: &str) -> Self {
203 Self::new(
204 read.cfg.clone(),
205 Arc::clone(&read.metrics),
206 read.machine.clone(),
207 read.gc.clone(),
208 Arc::clone(&read.blob),
209 WriterId::new(),
210 purpose,
211 read.read_schemas.clone(),
212 )
213 }
214
215 pub fn validate_part_bounds_on_write(&self) -> bool {
218 VALIDATE_PART_BOUNDS_ON_WRITE.get(&self.cfg) || VALIDATE_PART_BOUNDS_ON_READ.get(&self.cfg)
221 }
222
223 pub fn shard_id(&self) -> ShardId {
225 self.machine.shard_id()
226 }
227
228 pub fn schema_id(&self) -> Option<SchemaId> {
230 self.write_schemas.id
231 }
232
233 pub async fn try_register_schema(&mut self) -> Option<SchemaId> {
239 let Schemas { id, key, val } = &self.write_schemas;
240
241 if let Some(id) = id {
242 return Some(*id);
243 }
244
245 let (schema_id, maintenance) = self.machine.register_schema(key, val).await;
246 maintenance.start_performing(&self.machine, &self.gc);
247
248 self.write_schemas.id = schema_id;
249 schema_id
250 }
251
252 pub fn upper(&self) -> &Antichain<T> {
259 &self.upper
260 }
261
262 pub fn shared_upper(&self) -> Antichain<T> {
268 self.machine.applier.clone_upper()
269 }
270
271 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
277 pub async fn fetch_recent_upper(&mut self) -> &Antichain<T> {
278 self.machine
281 .applier
282 .fetch_upper(|current_upper| self.upper.clone_from(current_upper))
283 .await;
284 &self.upper
285 }
286
287 pub async fn advance_upper(&mut self, target: &Antichain<T>) {
295 let mut lower = self.shared_upper().clone();
298
299 while !PartialOrder::less_equal(target, &lower) {
300 let since = Antichain::from_elem(T::minimum());
301 let desc = Description::new(lower.clone(), target.clone(), since);
302 let batch = HollowBatch::empty(desc);
303
304 let res = self
305 .machine
306 .compare_and_append(&batch, &self.writer_id, &self.debug_state)
307 .await;
308
309 use CompareAndAppendRes::*;
310 let new_upper = match res {
311 Success(_seq_no, maintenance) => {
312 maintenance.start_performing(&self.machine, &self.gc, self.compact.as_ref());
313 batch.desc.upper().clone()
314 }
315 UpperMismatch(_seq_no, actual_upper) => actual_upper,
316 InvalidUsage(_invalid_usage) => unreachable!("batch bounds checked above"),
317 InlineBackpressure => unreachable!("batch was empty"),
318 };
319
320 self.upper.clone_from(&new_upper);
321 lower = new_upper;
322 }
323 }
324
325 #[instrument(level = "trace", fields(shard = %self.machine.shard_id()))]
355 pub async fn append<SB, KB, VB, TB, DB, I>(
356 &mut self,
357 updates: I,
358 lower: Antichain<T>,
359 upper: Antichain<T>,
360 ) -> Result<Result<(), UpperMismatch<T>>, InvalidUsage<T>>
361 where
362 SB: Borrow<((KB, VB), TB, DB)>,
363 KB: Borrow<K>,
364 VB: Borrow<V>,
365 TB: Borrow<T>,
366 DB: Borrow<D>,
367 I: IntoIterator<Item = SB>,
368 D: Send + Sync,
369 {
370 let batch = self.batch(updates, lower.clone(), upper.clone()).await?;
371 self.append_batch(batch, lower, upper).await
372 }
373
374 #[instrument(level = "trace", fields(shard = %self.machine.shard_id()))]
403 pub async fn compare_and_append<SB, KB, VB, TB, DB, I>(
404 &mut self,
405 updates: I,
406 expected_upper: Antichain<T>,
407 new_upper: Antichain<T>,
408 ) -> Result<Result<(), UpperMismatch<T>>, InvalidUsage<T>>
409 where
410 SB: Borrow<((KB, VB), TB, DB)>,
411 KB: Borrow<K>,
412 VB: Borrow<V>,
413 TB: Borrow<T>,
414 DB: Borrow<D>,
415 I: IntoIterator<Item = SB>,
416 D: Send + Sync,
417 {
418 let mut batch = self
419 .batch(updates, expected_upper.clone(), new_upper.clone())
420 .await?;
421 match self
422 .compare_and_append_batch(&mut [&mut batch], expected_upper, new_upper, true)
423 .await
424 {
425 ok @ Ok(Ok(())) => ok,
426 err => {
427 batch.delete().await;
432 err
433 }
434 }
435 }
436
437 #[instrument(level = "trace", fields(shard = %self.machine.shard_id()))]
463 pub async fn append_batch(
464 &mut self,
465 mut batch: Batch<K, V, T, D>,
466 mut lower: Antichain<T>,
467 upper: Antichain<T>,
468 ) -> Result<Result<(), UpperMismatch<T>>, InvalidUsage<T>>
469 where
470 D: Send + Sync,
471 {
472 loop {
473 let res = self
474 .compare_and_append_batch(&mut [&mut batch], lower.clone(), upper.clone(), true)
475 .await?;
476 match res {
477 Ok(()) => {
478 self.upper = upper;
479 return Ok(Ok(()));
480 }
481 Err(mismatch) => {
482 if PartialOrder::less_than(&mismatch.current, &lower) {
484 self.upper.clone_from(&mismatch.current);
485
486 batch.delete().await;
487
488 return Ok(Err(mismatch));
489 } else if PartialOrder::less_than(&mismatch.current, &upper) {
490 lower = mismatch.current;
497 } else {
498 self.upper = mismatch.current;
500
501 batch.delete().await;
505
506 return Ok(Ok(()));
507 }
508 }
509 }
510 }
511 }
512
513 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
547 pub async fn compare_and_append_batch(
548 &mut self,
549 batches: &mut [&mut Batch<K, V, T, D>],
550 expected_upper: Antichain<T>,
551 new_upper: Antichain<T>,
552 validate_part_bounds_on_write: bool,
553 ) -> Result<Result<(), UpperMismatch<T>>, InvalidUsage<T>>
554 where
555 D: Send + Sync,
556 {
557 let schema_id = self.try_register_schema().await;
561
562 for batch in batches.iter() {
563 if self.machine.shard_id() != batch.shard_id() {
564 return Err(InvalidUsage::BatchNotFromThisShard {
565 batch_shard: batch.shard_id(),
566 handle_shard: self.machine.shard_id(),
567 });
568 }
569 assert_code_can_read_data(&self.cfg.build_version, &batch.version);
570 if self.cfg.build_version > batch.version {
571 info!(
572 shard_id =? self.machine.shard_id(),
573 batch_version =? batch.version,
574 writer_version =? self.cfg.build_version,
575 "Appending batch from the past. This is fine but should be rare. \
576 TODO: Error on very old versions once the leaked blob detector exists."
577 )
578 }
579 fn assert_schema<A: Codec>(writer_schema: &A::Schema, batch_schema: &bytes::Bytes) {
580 if batch_schema.is_empty() {
581 return;
583 }
584 let batch_schema: A::Schema = A::decode_schema(batch_schema);
585 if *writer_schema != batch_schema {
586 error!(
587 ?writer_schema,
588 ?batch_schema,
589 "writer and batch schemas should be identical"
590 );
591 soft_panic_or_log!("writer and batch schemas should be identical");
592 }
593 }
594 assert_schema::<K>(&*self.write_schemas.key, &batch.schemas.0);
595 assert_schema::<V>(&*self.write_schemas.val, &batch.schemas.1);
596 }
597
598 let lower = expected_upper.clone();
599 let upper = new_upper;
600 let since = Antichain::from_elem(T::minimum());
601 let desc = Description::new(lower, upper, since);
602
603 let mut received_inline_backpressure = false;
604 let mut inline_batch_builder: Option<(_, BatchBuilder<K, V, T, D>)> = None;
611 let maintenance = loop {
612 let any_batch_rewrite = batches
613 .iter()
614 .any(|x| x.batch.parts.iter().any(|x| x.ts_rewrite().is_some()));
615 let (mut parts, mut num_updates, mut run_splits, mut run_metas) =
616 (vec![], 0, vec![], vec![]);
617 let mut key_storage = None;
618 let mut val_storage = None;
619 for batch in batches.iter() {
620 let () = validate_truncate_batch(
621 &batch.batch,
622 &desc,
623 any_batch_rewrite,
624 validate_part_bounds_on_write,
625 )?;
626 for (run_meta, run) in batch.batch.runs() {
627 let start_index = parts.len();
628 for part in run {
629 if let (
630 RunPart::Single(
631 batch_part @ BatchPart::Inline {
632 updates,
633 ts_rewrite,
634 schema_id: _,
635 deprecated_schema_id: _,
636 },
637 ),
638 Some((schema_cache, builder)),
639 ) = (part, &mut inline_batch_builder)
640 {
641 let schema_migration = PartMigration::new(
642 batch_part,
643 self.write_schemas.clone(),
644 schema_cache,
645 )
646 .await
647 .expect("schemas for inline user part");
648
649 let encoded_part = EncodedPart::from_inline(
650 &crate::fetch::FetchConfig::from_persist_config(&self.cfg),
651 &*self.metrics,
652 self.metrics.read.compaction.clone(),
653 desc.clone(),
654 updates,
655 ts_rewrite.as_ref(),
656 );
657 let mut fetched_part = FetchedPart::new(
658 Arc::clone(&self.metrics),
659 encoded_part,
660 schema_migration,
661 FetchBatchFilter::Compaction {
662 since: desc.since().clone(),
663 },
664 false,
665 PartDecodeFormat::Arrow,
666 None,
667 );
668
669 while let Some(((k, v), t, d)) =
670 fetched_part.next_with_storage(&mut key_storage, &mut val_storage)
671 {
672 builder
673 .add(&k, &v, &t, &d)
674 .await
675 .expect("re-encoding just-decoded data");
676 }
677 } else {
678 parts.push(part.clone())
679 }
680 }
681
682 let end_index = parts.len();
683
684 if start_index == end_index {
685 continue;
686 }
687
688 if start_index != 0 {
690 run_splits.push(start_index);
691 }
692 run_metas.push(run_meta.clone());
693 }
694 num_updates += batch.batch.len;
695 }
696
697 let mut flushed_inline_batch = if let Some((_, builder)) = inline_batch_builder.take() {
698 let mut finished = builder
699 .finish(desc.upper().clone())
700 .await
701 .expect("invalid usage");
702 let cfg = BatchBuilderConfig::new(&self.cfg, self.shard_id());
703 finished
704 .flush_to_blob(
705 &cfg,
706 &self.metrics.inline.backpressure,
707 &self.isolated_runtime,
708 &self.write_schemas,
709 )
710 .await;
711 Some(finished)
712 } else {
713 None
714 };
715
716 if let Some(batch) = &flushed_inline_batch {
717 for (run_meta, run) in batch.batch.runs() {
718 assert!(run.len() > 0);
719 let start_index = parts.len();
720 if start_index != 0 {
721 run_splits.push(start_index);
722 }
723 run_metas.push(run_meta.clone());
724 parts.extend(run.iter().cloned())
725 }
726 }
727
728 let mut combined_batch =
729 HollowBatch::new(desc.clone(), parts, num_updates, run_metas, run_splits);
730
731 match schema_id {
735 Some(schema_id) => {
736 ensure_batch_schema(&mut combined_batch, self.shard_id(), schema_id);
737 }
738 None => {
739 assert!(
740 self.fetch_recent_upper().await.is_empty(),
741 "fetching a schema id should only fail when the shard is tombstoned"
742 )
743 }
744 }
745
746 let res = self
747 .machine
748 .compare_and_append(&combined_batch, &self.writer_id, &self.debug_state)
749 .await;
750
751 match res {
752 CompareAndAppendRes::Success(_seqno, maintenance) => {
753 self.upper.clone_from(desc.upper());
754 for batch in batches.iter_mut() {
755 batch.mark_consumed();
756 }
757 if let Some(batch) = &mut flushed_inline_batch {
758 batch.mark_consumed();
759 }
760 break maintenance;
761 }
762 CompareAndAppendRes::InvalidUsage(invalid_usage) => {
763 if let Some(batch) = flushed_inline_batch.take() {
764 batch.delete().await;
765 }
766 return Err(invalid_usage);
767 }
768 CompareAndAppendRes::UpperMismatch(_seqno, current_upper) => {
769 if let Some(batch) = flushed_inline_batch.take() {
770 batch.delete().await;
771 }
772 self.upper.clone_from(¤t_upper);
775 return Ok(Err(UpperMismatch {
776 current: current_upper,
777 expected: expected_upper,
778 }));
779 }
780 CompareAndAppendRes::InlineBackpressure => {
781 assert_eq!(received_inline_backpressure, false);
784 received_inline_backpressure = true;
785 if COMBINE_INLINE_WRITES.get(&self.cfg) {
786 inline_batch_builder = Some((
787 self.machine.applier.schema_cache(),
788 self.builder(desc.lower().clone()),
789 ));
790 continue;
791 }
792
793 let cfg = BatchBuilderConfig::new(&self.cfg, self.shard_id());
794 let flush_batches = batches
797 .iter_mut()
798 .map(|batch| async {
799 batch
800 .flush_to_blob(
801 &cfg,
802 &self.metrics.inline.backpressure,
803 &self.isolated_runtime,
804 &self.write_schemas,
805 )
806 .await
807 })
808 .collect::<FuturesUnordered<_>>();
809 let () = flush_batches.collect::<()>().await;
810
811 for batch in batches.iter() {
812 assert_eq!(batch.batch.inline_bytes(), 0);
813 }
814
815 continue;
816 }
817 }
818 };
819
820 maintenance.start_performing(&self.machine, &self.gc, self.compact.as_ref());
821
822 Ok(Ok(()))
823 }
824
825 pub fn batch_from_transmittable_batch(&self, batch: ProtoBatch) -> Batch<K, V, T, D> {
828 let shard_id: ShardId = batch
829 .shard_id
830 .into_rust()
831 .expect("valid transmittable batch");
832 assert_eq!(shard_id, self.machine.shard_id());
833
834 let ret = Batch {
835 batch_delete_enabled: BATCH_DELETE_ENABLED.get(&self.cfg),
836 metrics: Arc::clone(&self.metrics),
837 shard_metrics: Arc::clone(&self.machine.applier.shard_metrics),
838 version: Version::parse(&batch.version).expect("valid transmittable batch"),
839 schemas: (batch.key_schema, batch.val_schema),
840 batch: batch
841 .batch
842 .into_rust_if_some("ProtoBatch::batch")
843 .expect("valid transmittable batch"),
844 blob: Arc::clone(&self.blob),
845 _phantom: std::marker::PhantomData,
846 };
847 assert_eq!(ret.shard_id(), self.machine.shard_id());
848 ret
849 }
850
851 pub fn builder(&self, lower: Antichain<T>) -> BatchBuilder<K, V, T, D> {
864 Self::builder_inner(
865 &self.cfg,
866 CompactConfig::new(&self.cfg, self.shard_id()),
867 Arc::clone(&self.metrics),
868 Arc::clone(&self.machine.applier.shard_metrics),
869 &self.metrics.user,
870 Arc::clone(&self.isolated_runtime),
871 Arc::clone(&self.blob),
872 self.shard_id(),
873 self.write_schemas.clone(),
874 lower,
875 )
876 }
877
878 pub(crate) fn builder_inner(
881 persist_cfg: &PersistConfig,
882 compact_cfg: CompactConfig,
883 metrics: Arc<Metrics>,
884 shard_metrics: Arc<ShardMetrics>,
885 user_batch_metrics: &BatchWriteMetrics,
886 isolated_runtime: Arc<IsolatedRuntime>,
887 blob: Arc<dyn Blob>,
888 shard_id: ShardId,
889 schemas: Schemas<K, V>,
890 lower: Antichain<T>,
891 ) -> BatchBuilder<K, V, T, D> {
892 let parts = if let Some(max_runs) = compact_cfg.batch.max_runs {
893 BatchParts::new_compacting::<K, V, D>(
894 compact_cfg,
895 Description::new(
896 lower.clone(),
897 Antichain::new(),
898 Antichain::from_elem(T::minimum()),
899 ),
900 max_runs,
901 Arc::clone(&metrics),
902 shard_metrics,
903 shard_id,
904 Arc::clone(&blob),
905 isolated_runtime,
906 user_batch_metrics,
907 schemas.clone(),
908 )
909 } else {
910 BatchParts::new_ordered::<D>(
911 compact_cfg.batch,
912 RunOrder::Unordered,
913 Arc::clone(&metrics),
914 shard_metrics,
915 shard_id,
916 Arc::clone(&blob),
917 isolated_runtime,
918 user_batch_metrics,
919 )
920 };
921 let builder = BatchBuilderInternal::new(
922 BatchBuilderConfig::new(persist_cfg, shard_id),
923 parts,
924 metrics,
925 schemas,
926 blob,
927 shard_id,
928 persist_cfg.build_version.clone(),
929 );
930 BatchBuilder::new(
931 builder,
932 Description::new(lower, Antichain::new(), Antichain::from_elem(T::minimum())),
933 )
934 }
935
936 #[instrument(level = "trace", fields(shard = %self.machine.shard_id()))]
939 pub async fn batch<SB, KB, VB, TB, DB, I>(
940 &mut self,
941 updates: I,
942 lower: Antichain<T>,
943 upper: Antichain<T>,
944 ) -> Result<Batch<K, V, T, D>, InvalidUsage<T>>
945 where
946 SB: Borrow<((KB, VB), TB, DB)>,
947 KB: Borrow<K>,
948 VB: Borrow<V>,
949 TB: Borrow<T>,
950 DB: Borrow<D>,
951 I: IntoIterator<Item = SB>,
952 {
953 let iter = updates.into_iter();
954
955 let mut builder = self.builder(lower.clone());
956
957 for update in iter {
958 let ((k, v), t, d) = update.borrow();
959 let (k, v, t, d) = (k.borrow(), v.borrow(), t.borrow(), d.borrow());
960 match builder.add(k, v, t, d).await {
961 Ok(Added::Record | Added::RecordAndParts) => (),
962 Err(invalid_usage) => return Err(invalid_usage),
963 }
964 }
965
966 builder.finish(upper.clone()).await
967 }
968
969 pub async fn wait_for_upper_past(&mut self, frontier: &Antichain<T>) {
971 let mut watch = self.machine.applier.watch();
972 self.machine
973 .wait_for_upper_past(
974 frontier,
975 &mut watch,
976 None,
977 &self.metrics.retries.next_listen_batch, next_listen_batch_retry_params(&self.cfg),
979 )
980 .await;
981 let upper = self.machine.applier.clone_upper();
982 if PartialOrder::less_than(&self.upper, &upper) {
983 self.upper.clone_from(&upper);
984 }
985 assert!(PartialOrder::less_than(frontier, &self.upper));
986 }
987
988 #[instrument(level = "debug", fields(shard = %self.machine.shard_id()))]
997 pub async fn expire(mut self) {
998 let Some(expire_fn) = self.expire_fn.take() else {
999 return;
1000 };
1001 expire_fn.0().await;
1002 }
1003
1004 fn expire_fn(
1005 machine: Machine<K, V, T, D>,
1006 gc: GarbageCollector<K, V, T, D>,
1007 writer_id: WriterId,
1008 ) -> ExpireFn {
1009 ExpireFn(Box::new(move || {
1010 Box::pin(async move {
1011 let (_, maintenance) = machine.expire_writer(&writer_id).await;
1012 maintenance.start_performing(&machine, &gc);
1013 })
1014 }))
1015 }
1016
1017 #[cfg(test)]
1019 #[track_caller]
1020 pub async fn expect_append<L, U>(&mut self, updates: &[((K, V), T, D)], lower: L, new_upper: U)
1021 where
1022 L: Into<Antichain<T>>,
1023 U: Into<Antichain<T>>,
1024 D: Send + Sync,
1025 {
1026 self.append(updates.iter(), lower.into(), new_upper.into())
1027 .await
1028 .expect("invalid usage")
1029 .expect("unexpected upper");
1030 }
1031
1032 #[cfg(test)]
1035 #[track_caller]
1036 pub async fn expect_compare_and_append(
1037 &mut self,
1038 updates: &[((K, V), T, D)],
1039 expected_upper: T,
1040 new_upper: T,
1041 ) where
1042 D: Send + Sync,
1043 {
1044 self.compare_and_append(
1045 updates.iter().map(|((k, v), t, d)| ((k, v), t, d)),
1046 Antichain::from_elem(expected_upper),
1047 Antichain::from_elem(new_upper),
1048 )
1049 .await
1050 .expect("invalid usage")
1051 .expect("unexpected upper")
1052 }
1053
1054 #[cfg(test)]
1057 #[track_caller]
1058 pub async fn expect_compare_and_append_batch(
1059 &mut self,
1060 batches: &mut [&mut Batch<K, V, T, D>],
1061 expected_upper: T,
1062 new_upper: T,
1063 ) {
1064 self.compare_and_append_batch(
1065 batches,
1066 Antichain::from_elem(expected_upper),
1067 Antichain::from_elem(new_upper),
1068 true,
1069 )
1070 .await
1071 .expect("invalid usage")
1072 .expect("unexpected upper")
1073 }
1074
1075 #[cfg(test)]
1077 #[track_caller]
1078 pub async fn expect_batch(
1079 &mut self,
1080 updates: &[((K, V), T, D)],
1081 lower: T,
1082 upper: T,
1083 ) -> Batch<K, V, T, D> {
1084 self.batch(
1085 updates.iter(),
1086 Antichain::from_elem(lower),
1087 Antichain::from_elem(upper),
1088 )
1089 .await
1090 .expect("invalid usage")
1091 }
1092}
1093
1094impl<K: Codec, V: Codec, T, D> Drop for WriteHandle<K, V, T, D> {
1095 fn drop(&mut self) {
1096 let Some(expire_fn) = self.expire_fn.take() else {
1097 return;
1098 };
1099 let handle = match Handle::try_current() {
1100 Ok(x) => x,
1101 Err(_) => {
1102 warn!(
1103 "WriteHandle {} dropped without being explicitly expired, falling back to lease timeout",
1104 self.writer_id
1105 );
1106 return;
1107 }
1108 };
1109 let expire_span = debug_span!("drop::expire");
1115 handle.spawn_named(
1116 || format!("WriteHandle::expire ({})", self.writer_id),
1117 expire_fn.0().instrument(expire_span),
1118 );
1119 }
1120}
1121
1122fn ensure_batch_schema<T>(batch: &mut HollowBatch<T>, shard_id: ShardId, schema_id: SchemaId)
1127where
1128 T: Timestamp + Lattice + Codec64,
1129{
1130 let ensure = |id: &mut Option<SchemaId>| match id {
1131 Some(id) => assert_eq!(*id, schema_id, "schema ID mismatch; shard={shard_id}"),
1132 None => *id = Some(schema_id),
1133 };
1134
1135 for run_meta in &mut batch.run_meta {
1136 ensure(&mut run_meta.schema);
1137 }
1138 for part in &mut batch.parts {
1139 match part {
1140 RunPart::Single(BatchPart::Hollow(part)) => ensure(&mut part.schema_id),
1141 RunPart::Single(BatchPart::Inline { schema_id, .. }) => ensure(schema_id),
1142 RunPart::Many(_hollow_run_ref) => {
1143 }
1147 }
1148 }
1149}
1150
1151#[cfg(test)]
1152mod tests {
1153 use std::str::FromStr;
1154 use std::sync::mpsc;
1155
1156 use differential_dataflow::consolidation::consolidate_updates;
1157 use futures_util::FutureExt;
1158 use mz_dyncfg::ConfigUpdates;
1159 use mz_ore::collections::CollectionExt;
1160 use mz_ore::task;
1161 use serde_json::json;
1162
1163 use crate::cache::PersistClientCache;
1164 use crate::tests::{all_ok, new_test_client};
1165 use crate::{PersistLocation, ShardId};
1166
1167 use super::*;
1168
1169 #[mz_persist_proc::test(tokio::test)]
1170 #[cfg_attr(miri, ignore)] async fn empty_batches(dyncfgs: ConfigUpdates) {
1172 let data = [
1173 (("1".to_owned(), "one".to_owned()), 1, 1),
1174 (("2".to_owned(), "two".to_owned()), 2, 1),
1175 (("3".to_owned(), "three".to_owned()), 3, 1),
1176 ];
1177
1178 let (mut write, _) = new_test_client(&dyncfgs)
1179 .await
1180 .expect_open::<String, String, u64, i64>(ShardId::new())
1181 .await;
1182 let blob = Arc::clone(&write.blob);
1183
1184 let mut upper = 3;
1186 write.expect_append(&data[..2], vec![0], vec![upper]).await;
1187
1188 let mut count_before = 0;
1190 blob.list_keys_and_metadata("", &mut |_| {
1191 count_before += 1;
1192 })
1193 .await
1194 .expect("list_keys failed");
1195 for _ in 0..5 {
1196 let new_upper = upper + 1;
1197 write.expect_compare_and_append(&[], upper, new_upper).await;
1198 upper = new_upper;
1199 }
1200 let mut count_after = 0;
1201 blob.list_keys_and_metadata("", &mut |_| {
1202 count_after += 1;
1203 })
1204 .await
1205 .expect("list_keys failed");
1206 assert_eq!(count_after, count_before);
1207 }
1208
1209 #[mz_persist_proc::test(tokio::test)]
1210 #[cfg_attr(miri, ignore)] async fn compare_and_append_batch_multi(dyncfgs: ConfigUpdates) {
1212 let data0 = vec![
1213 (("1".to_owned(), "one".to_owned()), 1, 1),
1214 (("2".to_owned(), "two".to_owned()), 2, 1),
1215 (("4".to_owned(), "four".to_owned()), 4, 1),
1216 ];
1217 let data1 = vec![
1218 (("1".to_owned(), "one".to_owned()), 1, 1),
1219 (("2".to_owned(), "two".to_owned()), 2, 1),
1220 (("3".to_owned(), "three".to_owned()), 3, 1),
1221 ];
1222
1223 let (mut write, mut read) = new_test_client(&dyncfgs)
1224 .await
1225 .expect_open::<String, String, u64, i64>(ShardId::new())
1226 .await;
1227
1228 let mut batch0 = write.expect_batch(&data0, 0, 5).await;
1229 let mut batch1 = write.expect_batch(&data1, 0, 4).await;
1230
1231 write
1232 .expect_compare_and_append_batch(&mut [&mut batch0, &mut batch1], 0, 4)
1233 .await;
1234
1235 let batch = write
1236 .machine
1237 .unleased_snapshot(&Antichain::from_elem(3))
1238 .await
1239 .expect("just wrote this")
1240 .into_element();
1241
1242 assert!(batch.runs().count() >= 2);
1243
1244 let expected = vec![
1245 (("1".to_owned(), "one".to_owned()), 1, 2),
1246 (("2".to_owned(), "two".to_owned()), 2, 2),
1247 (("3".to_owned(), "three".to_owned()), 3, 1),
1248 ];
1249 let mut actual = read.expect_snapshot_and_fetch(3).await;
1250 consolidate_updates(&mut actual);
1251 assert_eq!(actual, all_ok(&expected, 3));
1252 }
1253
1254 #[mz_ore::test]
1255 fn writer_id_human_readable_serde() {
1256 #[derive(Debug, Serialize, Deserialize)]
1257 struct Container {
1258 writer_id: WriterId,
1259 }
1260
1261 let id = WriterId::from_str("w00000000-1234-5678-0000-000000000000").expect("valid id");
1263 assert_eq!(
1264 id,
1265 serde_json::from_value(serde_json::to_value(id.clone()).expect("serializable"))
1266 .expect("deserializable")
1267 );
1268
1269 assert_eq!(
1271 id,
1272 serde_json::from_str("\"w00000000-1234-5678-0000-000000000000\"")
1273 .expect("deserializable")
1274 );
1275
1276 let json = json!({ "writer_id": id });
1278 assert_eq!(
1279 "{\"writer_id\":\"w00000000-1234-5678-0000-000000000000\"}",
1280 &json.to_string()
1281 );
1282 let container: Container = serde_json::from_value(json).expect("deserializable");
1283 assert_eq!(container.writer_id, id);
1284 }
1285
1286 #[mz_persist_proc::test(tokio::test)]
1287 #[cfg_attr(miri, ignore)] async fn hollow_batch_roundtrip(dyncfgs: ConfigUpdates) {
1289 let data = vec![
1290 (("1".to_owned(), "one".to_owned()), 1, 1),
1291 (("2".to_owned(), "two".to_owned()), 2, 1),
1292 (("3".to_owned(), "three".to_owned()), 3, 1),
1293 ];
1294
1295 let (mut write, mut read) = new_test_client(&dyncfgs)
1296 .await
1297 .expect_open::<String, String, u64, i64>(ShardId::new())
1298 .await;
1299
1300 let batch = write.expect_batch(&data, 0, 4).await;
1305 let hollow_batch = batch.into_transmittable_batch();
1306 let mut rehydrated_batch = write.batch_from_transmittable_batch(hollow_batch);
1307
1308 write
1309 .expect_compare_and_append_batch(&mut [&mut rehydrated_batch], 0, 4)
1310 .await;
1311
1312 let expected = vec![
1313 (("1".to_owned(), "one".to_owned()), 1, 1),
1314 (("2".to_owned(), "two".to_owned()), 2, 1),
1315 (("3".to_owned(), "three".to_owned()), 3, 1),
1316 ];
1317 let mut actual = read.expect_snapshot_and_fetch(3).await;
1318 consolidate_updates(&mut actual);
1319 assert_eq!(actual, all_ok(&expected, 3));
1320 }
1321
1322 #[mz_persist_proc::test(tokio::test)]
1323 #[cfg_attr(miri, ignore)] async fn wait_for_upper_past(dyncfgs: ConfigUpdates) {
1325 let client = new_test_client(&dyncfgs).await;
1326 let (mut write, _) = client.expect_open::<(), (), u64, i64>(ShardId::new()).await;
1327 let five = Antichain::from_elem(5);
1328
1329 assert_eq!(write.wait_for_upper_past(&five).now_or_never(), None);
1331
1332 write
1334 .expect_compare_and_append(&[(((), ()), 1, 1)], 0, 5)
1335 .await;
1336 assert_eq!(write.wait_for_upper_past(&five).now_or_never(), None);
1337
1338 write
1340 .expect_compare_and_append(&[(((), ()), 5, 1)], 5, 7)
1341 .await;
1342 assert_eq!(write.wait_for_upper_past(&five).now_or_never(), Some(()));
1343 assert_eq!(write.upper(), &Antichain::from_elem(7));
1344
1345 assert_eq!(
1348 write
1349 .wait_for_upper_past(&Antichain::from_elem(2))
1350 .now_or_never(),
1351 Some(())
1352 );
1353 assert_eq!(write.upper(), &Antichain::from_elem(7));
1354 }
1355
1356 #[mz_ore::test(tokio::test(flavor = "multi_thread"))]
1357 #[cfg_attr(miri, ignore)] async fn fetch_recent_upper_linearized() {
1359 type Timestamp = u64;
1360 let max_upper = 1000;
1361
1362 let shard_id = ShardId::new();
1363 let mut clients = PersistClientCache::new_no_metrics();
1364 let upper_writer_client = clients.open(PersistLocation::new_in_mem()).await.unwrap();
1365 let (mut upper_writer, _) = upper_writer_client
1366 .expect_open::<(), (), Timestamp, i64>(shard_id)
1367 .await;
1368 clients.clear_state_cache();
1371 let upper_reader_client = clients.open(PersistLocation::new_in_mem()).await.unwrap();
1372 let (mut upper_reader, _) = upper_reader_client
1373 .expect_open::<(), (), Timestamp, i64>(shard_id)
1374 .await;
1375 let (tx, rx) = mpsc::channel();
1376
1377 let task = task::spawn(|| "upper-reader", async move {
1378 let mut upper = Timestamp::MIN;
1379
1380 while upper < max_upper {
1381 while let Ok(new_upper) = rx.try_recv() {
1382 upper = new_upper;
1383 }
1384
1385 let recent_upper = upper_reader
1386 .fetch_recent_upper()
1387 .await
1388 .as_option()
1389 .cloned()
1390 .expect("u64 is totally ordered and the shard is not finalized");
1391 assert!(
1392 recent_upper >= upper,
1393 "recent upper {recent_upper:?} is less than known upper {upper:?}"
1394 );
1395 }
1396 });
1397
1398 for upper in Timestamp::MIN..max_upper {
1399 let next_upper = upper + 1;
1400 upper_writer
1401 .expect_compare_and_append(&[], upper, next_upper)
1402 .await;
1403 tx.send(next_upper).expect("send failed");
1404 }
1405
1406 task.await;
1407 }
1408}