1#![cfg_attr(
16 not(feature = "fuzzing"),
17 warn(missing_docs, missing_debug_implementations)
18)]
19#![allow(ungated_async_fn_track_caller)]
23
24use std::fmt::Debug;
25use std::marker::PhantomData;
26use std::sync::Arc;
27
28use differential_dataflow::difference::Monoid;
29use differential_dataflow::lattice::Lattice;
30use itertools::Itertools;
31use mz_build_info::{BuildInfo, build_info};
32use mz_dyncfg::ConfigSet;
33use mz_ore::instrument;
34use mz_persist::location::{Blob, Consensus, ExternalError};
35use mz_persist_types::schema::SchemaId;
36use mz_persist_types::{Codec, Codec64};
37use mz_proto::{IntoRustIfSome, ProtoType};
38use semver::Version;
39use timely::order::TotalOrder;
40use timely::progress::{Antichain, Timestamp};
41
42use crate::async_runtime::IsolatedRuntime;
43use crate::batch::{BATCH_DELETE_ENABLED, Batch, BatchBuilder, ProtoBatch};
44use crate::cache::{PersistClientCache, StateCache};
45use crate::cfg::PersistConfig;
46use crate::critical::{CriticalReaderId, Opaque, SinceHandle};
47use crate::error::InvalidUsage;
48use crate::fetch::{BatchFetcher, BatchFetcherConfig};
49use crate::internal::compact::{CompactConfig, Compactor};
50use crate::internal::encoding::parse_id;
51use crate::internal::gc::GarbageCollector;
52use crate::internal::machine::{Machine, retry_external};
53use crate::internal::state_versions::StateVersions;
54use crate::metrics::Metrics;
55use crate::read::{
56 Cursor, LazyPartStats, LeasedReaderId, READER_LEASE_DURATION, ReadHandle, Since,
57};
58use crate::rpc::PubSubSender;
59use crate::schema::CaESchema;
60use crate::write::{WriteHandle, WriterId};
61
62pub mod async_runtime;
63pub mod batch;
64pub mod cache;
65pub mod cfg;
66pub mod cli {
67 pub mod admin;
69 pub mod args;
70 pub mod bench;
71 pub mod inspect;
72}
73pub mod critical;
74pub mod error;
75pub mod fetch;
76pub mod internals_bench;
77pub mod iter;
78pub mod metrics {
79 pub use crate::internal::metrics::{
81 Metrics, SinkMetrics, SinkWorkerMetrics, UpdateDelta, encode_ts_metric,
82 };
83}
84pub mod operators {
85 use mz_dyncfg::{Config, ParameterScope};
88
89 pub mod shard_source;
90
91 pub(crate) const STORAGE_SOURCE_DECODE_FUEL: Config<usize> = Config::new(
93 "storage_source_decode_fuel",
94 100_000,
95 "\
96 The maximum amount of work to do in the persist_source mfp_and_decode \
97 operator before yielding.",
98 ParameterScope::Environment,
99 );
100}
101pub mod read;
102pub mod rpc;
103pub mod schema;
104pub mod stats;
105pub mod usage;
106pub mod write;
107
108#[cfg(feature = "fuzzing")]
113pub mod fuzz_exports {
114 pub use crate::internal::encoding::Rollup;
115 pub use crate::internal::state::{ProtoRollup, ProtoStateDiff};
116 pub use crate::internal::state_diff::StateDiff;
117}
118
119mod internal {
121 pub mod apply;
122 pub mod cache;
123 pub mod compact;
124 pub mod encoding;
125 pub mod gc;
126 pub mod machine;
127 pub mod maintenance;
128 pub mod merge;
129 pub mod metrics;
130 pub mod paths;
131 pub mod restore;
132 pub mod service;
133 pub mod state;
134 pub mod state_diff;
135 pub mod state_versions;
136 pub mod trace;
137 pub mod watch;
138
139 #[cfg(test)]
140 pub mod datadriven;
141}
142
143pub const BUILD_INFO: BuildInfo = build_info!();
145
146pub use mz_persist_types::{PersistLocation, ShardId};
148
149pub use crate::internal::encoding::Schemas;
150
151#[derive(Clone, Debug)]
154pub struct Diagnostics {
155 pub shard_name: String,
157 pub handle_purpose: String,
159}
160
161impl Diagnostics {
162 pub fn from_purpose(handle_purpose: &str) -> Self {
164 Self {
165 shard_name: "unknown".to_string(),
166 handle_purpose: handle_purpose.to_string(),
167 }
168 }
169
170 pub fn for_tests() -> Self {
172 Self {
173 shard_name: "test-shard-name".to_string(),
174 handle_purpose: "test-purpose".to_string(),
175 }
176 }
177}
178
179#[derive(Debug, Clone)]
200pub struct PersistClient {
201 cfg: PersistConfig,
202 blob: Arc<dyn Blob>,
203 consensus: Arc<dyn Consensus>,
204 metrics: Arc<Metrics>,
205 isolated_runtime: Arc<IsolatedRuntime>,
206 shared_states: Arc<StateCache>,
207 pubsub_sender: Arc<dyn PubSubSender>,
208}
209
210impl PersistClient {
211 pub fn new(
217 cfg: PersistConfig,
218 blob: Arc<dyn Blob>,
219 consensus: Arc<dyn Consensus>,
220 metrics: Arc<Metrics>,
221 isolated_runtime: Arc<IsolatedRuntime>,
222 shared_states: Arc<StateCache>,
223 pubsub_sender: Arc<dyn PubSubSender>,
224 ) -> Result<Self, ExternalError> {
225 Ok(PersistClient {
228 cfg,
229 blob,
230 consensus,
231 metrics,
232 isolated_runtime,
233 shared_states,
234 pubsub_sender,
235 })
236 }
237
238 pub async fn new_for_tests() -> Self {
240 let cache = PersistClientCache::new_no_metrics();
241 cache
242 .open(PersistLocation::new_in_mem())
243 .await
244 .expect("in-mem location is valid")
245 }
246
247 pub fn dyncfgs(&self) -> &ConfigSet {
249 &self.cfg.configs
250 }
251
252 async fn make_machine<K, V, T, D>(
253 &self,
254 shard_id: ShardId,
255 diagnostics: Diagnostics,
256 ) -> Result<Machine<K, V, T, D>, InvalidUsage<T>>
257 where
258 K: Debug + Codec,
259 V: Debug + Codec,
260 T: Timestamp + Lattice + Codec64 + Sync,
261 D: Monoid + Codec64 + Send + Sync,
262 {
263 let state_versions = StateVersions::new(
264 self.cfg.clone(),
265 Arc::clone(&self.consensus),
266 Arc::clone(&self.blob),
267 Arc::clone(&self.metrics),
268 );
269 let machine = Machine::<K, V, T, D>::new(
270 self.cfg.clone(),
271 shard_id,
272 Arc::clone(&self.metrics),
273 Arc::new(state_versions),
274 Arc::clone(&self.shared_states),
275 Arc::clone(&self.pubsub_sender),
276 Arc::clone(&self.isolated_runtime),
277 diagnostics.clone(),
278 )
279 .await?;
280 Ok(machine)
281 }
282
283 #[instrument(level = "debug", fields(shard = %shard_id))]
301 pub async fn open<K, V, T, D>(
302 &self,
303 shard_id: ShardId,
304 key_schema: Arc<K::Schema>,
305 val_schema: Arc<V::Schema>,
306 diagnostics: Diagnostics,
307 use_critical_since: bool,
308 ) -> Result<(WriteHandle<K, V, T, D>, ReadHandle<K, V, T, D>), InvalidUsage<T>>
309 where
310 K: Debug + Codec,
311 V: Debug + Codec,
312 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
313 D: Monoid + Ord + Codec64 + Send + Sync,
314 {
315 Ok((
316 self.open_writer(
317 shard_id,
318 Arc::clone(&key_schema),
319 Arc::clone(&val_schema),
320 diagnostics.clone(),
321 )
322 .await?,
323 self.open_leased_reader(
324 shard_id,
325 key_schema,
326 val_schema,
327 diagnostics,
328 use_critical_since,
329 )
330 .await?,
331 ))
332 }
333
334 #[instrument(level = "debug", fields(shard = %shard_id))]
343 pub async fn open_leased_reader<K, V, T, D>(
344 &self,
345 shard_id: ShardId,
346 key_schema: Arc<K::Schema>,
347 val_schema: Arc<V::Schema>,
348 diagnostics: Diagnostics,
349 use_critical_since: bool,
350 ) -> Result<ReadHandle<K, V, T, D>, InvalidUsage<T>>
351 where
352 K: Debug + Codec,
353 V: Debug + Codec,
354 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
355 D: Monoid + Codec64 + Send + Sync,
356 {
357 let machine = self.make_machine(shard_id, diagnostics.clone()).await?;
358 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
359
360 let reader_id = LeasedReaderId::new();
361 let (reader_state, maintenance) = machine
362 .register_leased_reader(
363 &reader_id,
364 &diagnostics.handle_purpose,
365 READER_LEASE_DURATION.get(&self.cfg),
366 use_critical_since,
367 )
368 .await;
369 maintenance.start_performing(&machine, &gc);
370 let schemas = Schemas {
371 id: None,
372 key: key_schema,
373 val: val_schema,
374 };
375 let reader = ReadHandle::new(
376 self.cfg.clone(),
377 Arc::clone(&self.metrics),
378 machine,
379 gc,
380 Arc::clone(&self.blob),
381 reader_id,
382 schemas,
383 reader_state,
384 )
385 .await;
386
387 Ok(reader)
388 }
389
390 #[instrument(level = "debug", fields(shard = %shard_id))]
392 pub async fn create_batch_fetcher<K, V, T, D>(
393 &self,
394 shard_id: ShardId,
395 key_schema: Arc<K::Schema>,
396 val_schema: Arc<V::Schema>,
397 is_transient: bool,
398 diagnostics: Diagnostics,
399 ) -> Result<BatchFetcher<K, V, T, D>, InvalidUsage<T>>
400 where
401 K: Debug + Codec,
402 V: Debug + Codec,
403 T: Timestamp + Lattice + Codec64 + Sync,
404 D: Monoid + Codec64 + Send + Sync,
405 {
406 let machine = self.make_machine(shard_id, diagnostics.clone()).await?;
407 let read_schemas = Schemas {
408 id: None,
409 key: key_schema,
410 val: val_schema,
411 };
412 let schema_cache = machine.applier.schema_cache();
413 let fetcher = BatchFetcher {
414 cfg: BatchFetcherConfig::new(&self.cfg),
415 blob: Arc::clone(&self.blob),
416 metrics: Arc::clone(&self.metrics),
417 shard_metrics: Arc::clone(&machine.applier.shard_metrics),
418 shard_id,
419 read_schemas,
420 schema_cache,
421 is_transient,
422 _phantom: PhantomData,
423 };
424
425 Ok(fetcher)
426 }
427
428 pub const CONTROLLER_CRITICAL_SINCE: CriticalReaderId =
450 CriticalReaderId([0, 0, 0, 0, 17, 17, 34, 34, 51, 51, 68, 68, 68, 68, 68, 68]);
451
452 #[instrument(level = "debug", fields(shard = %shard_id))]
473 pub async fn open_critical_since<K, V, T, D>(
474 &self,
475 shard_id: ShardId,
476 reader_id: CriticalReaderId,
477 default_opaque: Opaque,
478 diagnostics: Diagnostics,
479 ) -> Result<SinceHandle<K, V, T, D>, InvalidUsage<T>>
480 where
481 K: Debug + Codec,
482 V: Debug + Codec,
483 T: Timestamp + Lattice + Codec64 + Sync,
484 D: Monoid + Codec64 + Send + Sync,
485 {
486 let machine = self.make_machine(shard_id, diagnostics.clone()).await?;
487 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
488
489 let (state, maintenance) = machine
490 .register_critical_reader(&reader_id, default_opaque, &diagnostics.handle_purpose)
491 .await;
492 maintenance.start_performing(&machine, &gc);
493 let handle = SinceHandle::new(machine, gc, reader_id, state.since, state.opaque);
494
495 Ok(handle)
496 }
497
498 #[instrument(level = "debug", fields(shard = %shard_id))]
503 pub async fn open_writer<K, V, T, D>(
504 &self,
505 shard_id: ShardId,
506 key_schema: Arc<K::Schema>,
507 val_schema: Arc<V::Schema>,
508 diagnostics: Diagnostics,
509 ) -> Result<WriteHandle<K, V, T, D>, InvalidUsage<T>>
510 where
511 K: Debug + Codec,
512 V: Debug + Codec,
513 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
514 D: Monoid + Ord + Codec64 + Send + Sync,
515 {
516 let machine = self.make_machine(shard_id, diagnostics.clone()).await?;
517 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
518
519 let schema_id = machine.find_schema(&*key_schema, &*val_schema);
524
525 let writer_id = WriterId::new();
526 let schemas = Schemas {
527 id: schema_id,
528 key: key_schema,
529 val: val_schema,
530 };
531 let writer = WriteHandle::new(
532 self.cfg.clone(),
533 Arc::clone(&self.metrics),
534 machine,
535 gc,
536 Arc::clone(&self.blob),
537 writer_id,
538 &diagnostics.handle_purpose,
539 schemas,
540 );
541 Ok(writer)
542 }
543
544 #[instrument(level = "debug", fields(shard = %shard_id))]
554 pub async fn batch_builder<K, V, T, D>(
555 &self,
556 shard_id: ShardId,
557 write_schemas: Schemas<K, V>,
558 lower: Antichain<T>,
559 max_runs: Option<usize>,
560 ) -> BatchBuilder<K, V, T, D>
561 where
562 K: Debug + Codec,
563 V: Debug + Codec,
564 T: Timestamp + Lattice + Codec64 + TotalOrder + Sync,
565 D: Monoid + Ord + Codec64 + Send + Sync,
566 {
567 let mut compact_cfg = CompactConfig::new(&self.cfg, shard_id);
568 compact_cfg.batch.max_runs = max_runs;
569 WriteHandle::builder_inner(
570 &self.cfg,
571 compact_cfg,
572 Arc::clone(&self.metrics),
573 self.metrics.shards.shard(&shard_id, "peek_stash"),
574 &self.metrics.user,
575 Arc::clone(&self.isolated_runtime),
576 Arc::clone(&self.blob),
577 shard_id,
578 write_schemas,
579 lower,
580 )
581 }
582
583 pub fn batch_from_transmittable_batch<K, V, T, D>(
592 &self,
593 shard_id: &ShardId,
594 batch: ProtoBatch,
595 ) -> Batch<K, V, T, D>
596 where
597 K: Debug + Codec,
598 V: Debug + Codec,
599 T: Timestamp + Lattice + Codec64 + Sync,
600 D: Monoid + Ord + Codec64 + Send + Sync,
601 {
602 let batch_shard_id: ShardId = batch
603 .shard_id
604 .into_rust()
605 .expect("valid transmittable batch");
606 assert_eq!(&batch_shard_id, shard_id);
607
608 let shard_metrics = self.metrics.shards.shard(shard_id, "peek_stash");
609
610 let ret = Batch {
611 batch_delete_enabled: BATCH_DELETE_ENABLED.get(&self.cfg),
612 metrics: Arc::clone(&self.metrics),
613 shard_metrics,
614 version: Version::parse(&batch.version).expect("valid transmittable batch"),
615 schemas: (batch.key_schema, batch.val_schema),
616 batch: batch
617 .batch
618 .into_rust_if_some("ProtoBatch::batch")
619 .expect("valid transmittable batch"),
620 blob: Arc::clone(&self.blob),
621 _phantom: std::marker::PhantomData,
622 };
623
624 assert_eq!(&ret.shard_id(), shard_id);
625 ret
626 }
627
628 #[allow(clippy::unused_async)]
643 pub async fn read_batches_consolidated<K, V, T, D>(
644 &mut self,
645 shard_id: ShardId,
646 as_of: Antichain<T>,
647 read_schemas: Schemas<K, V>,
648 batches: Vec<Batch<K, V, T, D>>,
649 should_fetch_part: impl for<'a> Fn(Option<&'a LazyPartStats>) -> bool,
650 memory_budget_bytes: usize,
651 ) -> Result<Cursor<K, V, T, D, Vec<Batch<K, V, T, D>>>, Since<T>>
652 where
653 K: Debug + Codec + Ord,
654 V: Debug + Codec + Ord,
655 T: Timestamp + Lattice + Codec64 + TotalOrder + Sync,
656 D: Monoid + Ord + Codec64 + Send + Sync,
657 {
658 let shard_metrics = self.metrics.shards.shard(&shard_id, "peek_stash");
659
660 let hollow_batches = batches.iter().map(|b| b.batch.clone()).collect_vec();
661
662 ReadHandle::read_batches_consolidated(
663 &self.cfg,
664 Arc::clone(&self.metrics),
665 shard_metrics,
666 self.metrics.read.snapshot.clone(),
667 Arc::clone(&self.blob),
668 shard_id,
669 as_of,
670 read_schemas,
671 &hollow_batches,
672 batches,
673 should_fetch_part,
674 memory_budget_bytes,
675 )
676 }
677
678 pub async fn get_schema<K, V, T, D>(
680 &self,
681 shard_id: ShardId,
682 schema_id: SchemaId,
683 diagnostics: Diagnostics,
684 ) -> Result<Option<(K::Schema, V::Schema)>, InvalidUsage<T>>
685 where
686 K: Debug + Codec,
687 V: Debug + Codec,
688 T: Timestamp + Lattice + Codec64 + Sync,
689 D: Monoid + Codec64 + Send + Sync,
690 {
691 let machine = self
692 .make_machine::<K, V, T, D>(shard_id, diagnostics)
693 .await?;
694 Ok(machine.get_schema(schema_id))
695 }
696
697 pub async fn latest_schema<K, V, T, D>(
699 &self,
700 shard_id: ShardId,
701 diagnostics: Diagnostics,
702 ) -> Result<Option<(SchemaId, K::Schema, V::Schema)>, InvalidUsage<T>>
703 where
704 K: Debug + Codec,
705 V: Debug + Codec,
706 T: Timestamp + Lattice + Codec64 + Sync,
707 D: Monoid + Codec64 + Send + Sync,
708 {
709 let machine = self
710 .make_machine::<K, V, T, D>(shard_id, diagnostics)
711 .await?;
712 Ok(machine.latest_schema())
713 }
714
715 pub async fn recent_upper<K, V, T, D>(
726 &self,
727 shard_id: ShardId,
728 diagnostics: Diagnostics,
729 ) -> Result<Antichain<T>, InvalidUsage<T>>
730 where
731 K: Debug + Codec,
732 V: Debug + Codec,
733 T: Timestamp + Lattice + Codec64 + Sync,
734 D: Monoid + Codec64 + Send + Sync,
735 {
736 let machine = self
737 .make_machine::<K, V, T, D>(shard_id, diagnostics)
738 .await?;
739 Ok(machine.applier.fetch_upper(|upper| upper.clone()).await)
740 }
741
742 pub async fn register_schema<K, V, T, D>(
754 &self,
755 shard_id: ShardId,
756 key_schema: &K::Schema,
757 val_schema: &V::Schema,
758 diagnostics: Diagnostics,
759 ) -> Result<Option<SchemaId>, InvalidUsage<T>>
760 where
761 K: Debug + Codec,
762 V: Debug + Codec,
763 T: Timestamp + Lattice + Codec64 + Sync,
764 D: Monoid + Codec64 + Send + Sync,
765 {
766 let machine = self
767 .make_machine::<K, V, T, D>(shard_id, diagnostics)
768 .await?;
769 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
770
771 let (schema_id, maintenance) = machine.register_schema(key_schema, val_schema).await;
772 maintenance.start_performing(&machine, &gc);
773
774 Ok(schema_id)
775 }
776
777 pub async fn compare_and_evolve_schema<K, V, T, D>(
791 &self,
792 shard_id: ShardId,
793 expected: SchemaId,
794 key_schema: &K::Schema,
795 val_schema: &V::Schema,
796 diagnostics: Diagnostics,
797 ) -> Result<CaESchema<K, V>, InvalidUsage<T>>
798 where
799 K: Debug + Codec,
800 V: Debug + Codec,
801 T: Timestamp + Lattice + Codec64 + Sync,
802 D: Monoid + Codec64 + Send + Sync,
803 {
804 let machine = self
805 .make_machine::<K, V, T, D>(shard_id, diagnostics)
806 .await?;
807 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
808 let (res, maintenance) = machine
809 .compare_and_evolve_schema(expected, key_schema, val_schema)
810 .await;
811 maintenance.start_performing(&machine, &gc);
812 Ok(res)
813 }
814
815 pub async fn is_finalized<K, V, T, D>(
819 &self,
820 shard_id: ShardId,
821 diagnostics: Diagnostics,
822 ) -> Result<bool, InvalidUsage<T>>
823 where
824 K: Debug + Codec,
825 V: Debug + Codec,
826 T: Timestamp + Lattice + Codec64 + Sync,
827 D: Monoid + Codec64 + Send + Sync,
828 {
829 let machine = self
830 .make_machine::<K, V, T, D>(shard_id, diagnostics)
831 .await?;
832 Ok(machine.is_finalized())
833 }
834
835 #[instrument(level = "debug", fields(shard = %shard_id))]
846 pub async fn finalize_shard<K, V, T, D>(
847 &self,
848 shard_id: ShardId,
849 diagnostics: Diagnostics,
850 ) -> Result<(), InvalidUsage<T>>
851 where
852 K: Debug + Codec,
853 V: Debug + Codec,
854 T: Timestamp + Lattice + Codec64 + Sync,
855 D: Monoid + Codec64 + Send + Sync,
856 {
857 let machine = self
858 .make_machine::<K, V, T, D>(shard_id, diagnostics)
859 .await?;
860
861 let maintenance = machine.become_tombstone().await?;
862 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
863
864 let () = maintenance.perform(&machine, &gc).await;
865
866 Ok(())
867 }
868
869 pub async fn upgrade_version<K, V, T, D>(
872 &self,
873 shard_id: ShardId,
874 diagnostics: Diagnostics,
875 ) -> Result<(), InvalidUsage<T>>
876 where
877 K: Debug + Codec,
878 V: Debug + Codec,
879 T: Timestamp + Lattice + Codec64 + Sync,
880 D: Monoid + Codec64 + Send + Sync,
881 {
882 let machine = self
883 .make_machine::<K, V, T, D>(shard_id, diagnostics)
884 .await?;
885
886 match machine.upgrade_version().await {
887 Ok(maintenance) => {
888 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
889 let () = maintenance.perform(&machine, &gc).await;
890 Ok(())
891 }
892 Err(version) => Err(InvalidUsage::IncompatibleVersion { version }),
893 }
894 }
895
896 pub async fn inspect_shard<T: Timestamp + Lattice + Codec64>(
902 &self,
903 shard_id: &ShardId,
904 ) -> Result<impl serde::Serialize, anyhow::Error> {
905 let state_versions = StateVersions::new(
906 self.cfg.clone(),
907 Arc::clone(&self.consensus),
908 Arc::clone(&self.blob),
909 Arc::clone(&self.metrics),
910 );
911 let versions = state_versions.fetch_all_live_diffs(shard_id).await;
915 if versions.is_empty() {
916 return Err(anyhow::anyhow!("{} does not exist", shard_id));
917 }
918 let state = state_versions
919 .fetch_current_state::<T>(shard_id, versions)
920 .await;
921 let state = state.check_ts_codec(shard_id)?;
922 Ok(state)
923 }
924
925 #[cfg(test)]
927 #[track_caller]
928 pub async fn expect_open<K, V, T, D>(
929 &self,
930 shard_id: ShardId,
931 ) -> (WriteHandle<K, V, T, D>, ReadHandle<K, V, T, D>)
932 where
933 K: Debug + Codec,
934 V: Debug + Codec,
935 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
936 D: Monoid + Ord + Codec64 + Send + Sync,
937 K::Schema: Default,
938 V::Schema: Default,
939 {
940 self.open(
941 shard_id,
942 Arc::new(K::Schema::default()),
943 Arc::new(V::Schema::default()),
944 Diagnostics::for_tests(),
945 true,
946 )
947 .await
948 .expect("codec mismatch")
949 }
950
951 pub fn metrics(&self) -> &Arc<Metrics> {
955 &self.metrics
956 }
957}
958
959#[cfg(test)]
960mod tests {
961 use std::future::Future;
962 use std::pin::Pin;
963 use std::task::Context;
964 use std::time::Duration;
965
966 use differential_dataflow::consolidation::consolidate_updates;
967 use differential_dataflow::lattice::Lattice;
968 use futures_task::noop_waker;
969 use mz_dyncfg::ConfigUpdates;
970 use mz_ore::assert_ok;
971 use mz_persist::indexed::encoding::BlobTraceBatchPart;
972 use mz_persist::workload::DataGenerator;
973 use mz_persist_types::codec_impls::{StringSchema, VecU8Schema};
974 use mz_proto::protobuf_roundtrip;
975 use proptest::prelude::*;
976 use timely::order::PartialOrder;
977 use timely::progress::Antichain;
978
979 use crate::batch::BLOB_TARGET_SIZE;
980 use crate::cache::PersistClientCache;
981 use crate::cfg::BATCH_BUILDER_MAX_OUTSTANDING_PARTS;
982 use crate::critical::Opaque;
983 use crate::error::{CodecConcreteType, CodecMismatch, UpperMismatch};
984 use crate::internal::paths::BlobKey;
985 use crate::read::ListenEvent;
986
987 use super::*;
988
989 pub fn new_test_client_cache(dyncfgs: &ConfigUpdates) -> PersistClientCache {
990 let mut cache = PersistClientCache::new_no_metrics();
993 cache.cfg.set_config(&BLOB_TARGET_SIZE, 10);
994 cache
995 .cfg
996 .set_config(&BATCH_BUILDER_MAX_OUTSTANDING_PARTS, 1);
997 dyncfgs.apply(cache.cfg());
998
999 cache.cfg.compaction_enabled = true;
1001 cache
1002 }
1003
1004 pub async fn new_test_client(dyncfgs: &ConfigUpdates) -> PersistClient {
1005 let cache = new_test_client_cache(dyncfgs);
1006 cache
1007 .open(PersistLocation::new_in_mem())
1008 .await
1009 .expect("client construction failed")
1010 }
1011
1012 pub fn all_ok<'a, K, V, T, D, I>(iter: I, as_of: T) -> Vec<((K, V), T, D)>
1013 where
1014 K: Ord + Clone + 'a,
1015 V: Ord + Clone + 'a,
1016 T: Timestamp + Lattice + Clone + 'a,
1017 D: Monoid + Clone + 'a,
1018 I: IntoIterator<Item = &'a ((K, V), T, D)>,
1019 {
1020 let as_of = Antichain::from_elem(as_of);
1021 let mut ret = iter
1022 .into_iter()
1023 .map(|((k, v), t, d)| {
1024 let mut t = t.clone();
1025 t.advance_by(as_of.borrow());
1026 ((k.clone(), v.clone()), t, d.clone())
1027 })
1028 .collect();
1029 consolidate_updates(&mut ret);
1030 ret
1031 }
1032
1033 pub async fn expect_fetch_part<K, V, T, D>(
1034 blob: &dyn Blob,
1035 key: &BlobKey,
1036 metrics: &Metrics,
1037 read_schemas: &Schemas<K, V>,
1038 ) -> (BlobTraceBatchPart<T>, Vec<((K, V), T, D)>)
1039 where
1040 K: Codec + Clone,
1041 V: Codec + Clone,
1042 T: Timestamp + Codec64,
1043 D: Codec64,
1044 {
1045 let value = blob
1046 .get(key)
1047 .await
1048 .expect("failed to fetch part")
1049 .expect("missing part");
1050 let mut part =
1051 BlobTraceBatchPart::decode(&value, &metrics.columnar).expect("failed to decode part");
1052 let structured = part
1053 .updates
1054 .into_part::<K, V>(&*read_schemas.key, &*read_schemas.val);
1055 let updates = structured
1056 .decode_iter::<K, V, T, D>(&*read_schemas.key, &*read_schemas.val)
1057 .expect("structured data")
1058 .collect();
1059 (part, updates)
1060 }
1061
1062 #[mz_persist_proc::test(tokio::test)]
1063 #[cfg_attr(miri, ignore)] async fn sanity_check(dyncfgs: ConfigUpdates) {
1065 let data = [
1066 (("1".to_owned(), "one".to_owned()), 1, 1),
1067 (("2".to_owned(), "two".to_owned()), 2, 1),
1068 (("3".to_owned(), "three".to_owned()), 3, 1),
1069 ];
1070
1071 let (mut write, mut read) = new_test_client(&dyncfgs)
1072 .await
1073 .expect_open::<String, String, u64, i64>(ShardId::new())
1074 .await;
1075 assert_eq!(write.upper(), &Antichain::from_elem(u64::minimum()));
1076 assert_eq!(read.since(), &Antichain::from_elem(u64::minimum()));
1077
1078 write
1080 .expect_append(&data[..2], write.upper().clone(), vec![3])
1081 .await;
1082 assert_eq!(write.upper(), &Antichain::from_elem(3));
1083
1084 assert_eq!(
1086 read.expect_snapshot_and_fetch(1).await,
1087 all_ok(&data[..1], 1)
1088 );
1089
1090 let mut listen = read.clone("").await.expect_listen(1).await;
1091
1092 write
1094 .expect_append(&data[2..], write.upper().clone(), vec![4])
1095 .await;
1096 assert_eq!(write.upper(), &Antichain::from_elem(4));
1097
1098 assert_eq!(
1100 listen.read_until(&4).await,
1101 (all_ok(&data[1..], 1), Antichain::from_elem(4))
1102 );
1103
1104 read.downgrade_since(&Antichain::from_elem(2)).await;
1106 assert_eq!(read.since(), &Antichain::from_elem(2));
1107 }
1108
1109 #[mz_persist_proc::test(tokio::test)]
1111 #[cfg_attr(miri, ignore)] async fn open_reader_writer(dyncfgs: ConfigUpdates) {
1113 let data = vec![
1114 (("1".to_owned(), "one".to_owned()), 1, 1),
1115 (("2".to_owned(), "two".to_owned()), 2, 1),
1116 (("3".to_owned(), "three".to_owned()), 3, 1),
1117 ];
1118
1119 let shard_id = ShardId::new();
1120 let client = new_test_client(&dyncfgs).await;
1121 let mut write1 = client
1122 .open_writer::<String, String, u64, i64>(
1123 shard_id,
1124 Arc::new(StringSchema),
1125 Arc::new(StringSchema),
1126 Diagnostics::for_tests(),
1127 )
1128 .await
1129 .expect("codec mismatch");
1130 let mut read1 = client
1131 .open_leased_reader::<String, String, u64, i64>(
1132 shard_id,
1133 Arc::new(StringSchema),
1134 Arc::new(StringSchema),
1135 Diagnostics::for_tests(),
1136 true,
1137 )
1138 .await
1139 .expect("codec mismatch");
1140 let mut read2 = client
1141 .open_leased_reader::<String, String, u64, i64>(
1142 shard_id,
1143 Arc::new(StringSchema),
1144 Arc::new(StringSchema),
1145 Diagnostics::for_tests(),
1146 true,
1147 )
1148 .await
1149 .expect("codec mismatch");
1150 let mut write2 = client
1151 .open_writer::<String, String, u64, i64>(
1152 shard_id,
1153 Arc::new(StringSchema),
1154 Arc::new(StringSchema),
1155 Diagnostics::for_tests(),
1156 )
1157 .await
1158 .expect("codec mismatch");
1159
1160 write2.expect_compare_and_append(&data[..1], 0, 2).await;
1161 assert_eq!(
1162 read2.expect_snapshot_and_fetch(1).await,
1163 all_ok(&data[..1], 1)
1164 );
1165 write1.expect_compare_and_append(&data[1..], 2, 4).await;
1166 assert_eq!(read1.expect_snapshot_and_fetch(3).await, all_ok(&data, 3));
1167 }
1168
1169 #[mz_persist_proc::test(tokio::test)]
1170 #[cfg_attr(miri, ignore)] async fn invalid_usage(dyncfgs: ConfigUpdates) {
1172 let data = vec![
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 shard_id0 = "s00000000-0000-0000-0000-000000000000"
1179 .parse::<ShardId>()
1180 .expect("invalid shard id");
1181 let mut client = new_test_client(&dyncfgs).await;
1182
1183 let (mut write0, mut read0) = client
1184 .expect_open::<String, String, u64, i64>(shard_id0)
1185 .await;
1186
1187 write0.expect_compare_and_append(&data, 0, 4).await;
1188
1189 {
1191 fn codecs(
1192 k: &str,
1193 v: &str,
1194 t: &str,
1195 d: &str,
1196 ) -> (String, String, String, String, Option<CodecConcreteType>) {
1197 (k.to_owned(), v.to_owned(), t.to_owned(), d.to_owned(), None)
1198 }
1199
1200 client.shared_states = Arc::new(StateCache::new_no_metrics());
1201 assert_eq!(
1202 client
1203 .open::<Vec<u8>, String, u64, i64>(
1204 shard_id0,
1205 Arc::new(VecU8Schema),
1206 Arc::new(StringSchema),
1207 Diagnostics::for_tests(),
1208 true,
1209 )
1210 .await
1211 .unwrap_err(),
1212 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1213 requested: codecs("Vec<u8>", "String", "u64", "i64"),
1214 actual: codecs("String", "String", "u64", "i64"),
1215 }))
1216 );
1217 assert_eq!(
1218 client
1219 .open::<String, Vec<u8>, u64, i64>(
1220 shard_id0,
1221 Arc::new(StringSchema),
1222 Arc::new(VecU8Schema),
1223 Diagnostics::for_tests(),
1224 true,
1225 )
1226 .await
1227 .unwrap_err(),
1228 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1229 requested: codecs("String", "Vec<u8>", "u64", "i64"),
1230 actual: codecs("String", "String", "u64", "i64"),
1231 }))
1232 );
1233 assert_eq!(
1234 client
1235 .open::<String, String, i64, i64>(
1236 shard_id0,
1237 Arc::new(StringSchema),
1238 Arc::new(StringSchema),
1239 Diagnostics::for_tests(),
1240 true,
1241 )
1242 .await
1243 .unwrap_err(),
1244 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1245 requested: codecs("String", "String", "i64", "i64"),
1246 actual: codecs("String", "String", "u64", "i64"),
1247 }))
1248 );
1249 assert_eq!(
1250 client
1251 .open::<String, String, u64, u64>(
1252 shard_id0,
1253 Arc::new(StringSchema),
1254 Arc::new(StringSchema),
1255 Diagnostics::for_tests(),
1256 true,
1257 )
1258 .await
1259 .unwrap_err(),
1260 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1261 requested: codecs("String", "String", "u64", "u64"),
1262 actual: codecs("String", "String", "u64", "i64"),
1263 }))
1264 );
1265
1266 assert_eq!(
1270 client
1271 .open_leased_reader::<Vec<u8>, String, u64, i64>(
1272 shard_id0,
1273 Arc::new(VecU8Schema),
1274 Arc::new(StringSchema),
1275 Diagnostics::for_tests(),
1276 true,
1277 )
1278 .await
1279 .unwrap_err(),
1280 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1281 requested: codecs("Vec<u8>", "String", "u64", "i64"),
1282 actual: codecs("String", "String", "u64", "i64"),
1283 }))
1284 );
1285 assert_eq!(
1286 client
1287 .open_writer::<Vec<u8>, String, u64, i64>(
1288 shard_id0,
1289 Arc::new(VecU8Schema),
1290 Arc::new(StringSchema),
1291 Diagnostics::for_tests(),
1292 )
1293 .await
1294 .unwrap_err(),
1295 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1296 requested: codecs("Vec<u8>", "String", "u64", "i64"),
1297 actual: codecs("String", "String", "u64", "i64"),
1298 }))
1299 );
1300 }
1301
1302 {
1304 let snap = read0
1305 .snapshot(Antichain::from_elem(3))
1306 .await
1307 .expect("cannot serve requested as_of");
1308
1309 let shard_id1 = "s11111111-1111-1111-1111-111111111111"
1310 .parse::<ShardId>()
1311 .expect("invalid shard id");
1312 let mut fetcher1 = client
1313 .create_batch_fetcher::<String, String, u64, i64>(
1314 shard_id1,
1315 Default::default(),
1316 Default::default(),
1317 false,
1318 Diagnostics::for_tests(),
1319 )
1320 .await
1321 .unwrap();
1322 for part in snap {
1323 let (part, _lease) = part.into_exchangeable_part();
1324 let res = fetcher1.fetch_leased_part(part).await;
1325 assert_eq!(
1326 res.unwrap_err(),
1327 InvalidUsage::BatchNotFromThisShard {
1328 batch_shard: shard_id0,
1329 handle_shard: shard_id1,
1330 }
1331 );
1332 }
1333 }
1334
1335 {
1337 let ts3 = &data[2];
1338 assert_eq!(ts3.1, 3);
1339 let ts3 = vec![ts3.clone()];
1340
1341 assert_eq!(
1344 write0
1345 .append(&ts3, Antichain::from_elem(4), Antichain::from_elem(5))
1346 .await
1347 .unwrap_err(),
1348 InvalidUsage::UpdateNotBeyondLower {
1349 ts: 3,
1350 lower: Antichain::from_elem(4),
1351 },
1352 );
1353 assert_eq!(
1354 write0
1355 .append(&ts3, Antichain::from_elem(2), Antichain::from_elem(3))
1356 .await
1357 .unwrap_err(),
1358 InvalidUsage::UpdateBeyondUpper {
1359 ts: 3,
1360 expected_upper: Antichain::from_elem(3),
1361 },
1362 );
1363 assert_eq!(
1365 write0
1366 .append(&data[..0], Antichain::from_elem(3), Antichain::from_elem(2))
1367 .await
1368 .unwrap_err(),
1369 InvalidUsage::InvalidBounds {
1370 lower: Antichain::from_elem(3),
1371 upper: Antichain::from_elem(2),
1372 },
1373 );
1374
1375 assert_eq!(
1377 write0
1378 .builder(Antichain::from_elem(3))
1379 .finish(Antichain::from_elem(2))
1380 .await
1381 .unwrap_err(),
1382 InvalidUsage::InvalidBounds {
1383 lower: Antichain::from_elem(3),
1384 upper: Antichain::from_elem(2)
1385 },
1386 );
1387 let batch = write0
1388 .batch(&ts3, Antichain::from_elem(3), Antichain::from_elem(4))
1389 .await
1390 .expect("invalid usage");
1391 assert_eq!(
1392 write0
1393 .append_batch(batch, Antichain::from_elem(4), Antichain::from_elem(5))
1394 .await
1395 .unwrap_err(),
1396 InvalidUsage::InvalidBatchBounds {
1397 batch_lower: Antichain::from_elem(3),
1398 batch_upper: Antichain::from_elem(4),
1399 append_lower: Antichain::from_elem(4),
1400 append_upper: Antichain::from_elem(5),
1401 },
1402 );
1403 let batch = write0
1404 .batch(&ts3, Antichain::from_elem(3), Antichain::from_elem(4))
1405 .await
1406 .expect("invalid usage");
1407 assert_eq!(
1408 write0
1409 .append_batch(batch, Antichain::from_elem(2), Antichain::from_elem(3))
1410 .await
1411 .unwrap_err(),
1412 InvalidUsage::InvalidBatchBounds {
1413 batch_lower: Antichain::from_elem(3),
1414 batch_upper: Antichain::from_elem(4),
1415 append_lower: Antichain::from_elem(2),
1416 append_upper: Antichain::from_elem(3),
1417 },
1418 );
1419 let batch = write0
1420 .batch(&ts3, Antichain::from_elem(3), Antichain::from_elem(4))
1421 .await
1422 .expect("invalid usage");
1423 assert!(matches!(
1426 write0
1427 .append_batch(batch, Antichain::from_elem(3), Antichain::from_elem(3))
1428 .await
1429 .unwrap_err(),
1430 InvalidUsage::InvalidEmptyTimeInterval { .. }
1431 ));
1432 }
1433 }
1434
1435 #[mz_persist_proc::test(tokio::test)]
1436 #[cfg_attr(miri, ignore)] async fn multiple_shards(dyncfgs: ConfigUpdates) {
1438 let data1 = [
1439 (("1".to_owned(), "one".to_owned()), 1, 1),
1440 (("2".to_owned(), "two".to_owned()), 2, 1),
1441 ];
1442
1443 let data2 = [(("1".to_owned(), ()), 1, 1), (("2".to_owned(), ()), 2, 1)];
1444
1445 let client = new_test_client(&dyncfgs).await;
1446
1447 let (mut write1, mut read1) = client
1448 .expect_open::<String, String, u64, i64>(ShardId::new())
1449 .await;
1450
1451 let (mut write2, mut read2) = client
1454 .expect_open::<String, (), u64, i64>(ShardId::new())
1455 .await;
1456
1457 write1
1458 .expect_compare_and_append(&data1[..], u64::minimum(), 3)
1459 .await;
1460
1461 write2
1462 .expect_compare_and_append(&data2[..], u64::minimum(), 3)
1463 .await;
1464
1465 assert_eq!(
1466 read1.expect_snapshot_and_fetch(2).await,
1467 all_ok(&data1[..], 2)
1468 );
1469
1470 assert_eq!(
1471 read2.expect_snapshot_and_fetch(2).await,
1472 all_ok(&data2[..], 2)
1473 );
1474 }
1475
1476 #[mz_persist_proc::test(tokio::test)]
1477 #[cfg_attr(miri, ignore)] async fn fetch_upper(dyncfgs: ConfigUpdates) {
1479 let data = [
1480 (("1".to_owned(), "one".to_owned()), 1, 1),
1481 (("2".to_owned(), "two".to_owned()), 2, 1),
1482 ];
1483
1484 let client = new_test_client(&dyncfgs).await;
1485
1486 let shard_id = ShardId::new();
1487
1488 let (mut write1, _read1) = client
1489 .expect_open::<String, String, u64, i64>(shard_id)
1490 .await;
1491
1492 let (mut write2, _read2) = client
1493 .expect_open::<String, String, u64, i64>(shard_id)
1494 .await;
1495
1496 write1
1497 .expect_append(&data[..], write1.upper().clone(), vec![3])
1498 .await;
1499
1500 assert_eq!(write2.fetch_recent_upper().await, &Antichain::from_elem(3));
1502
1503 assert_eq!(write2.upper(), &Antichain::from_elem(3));
1506 }
1507
1508 #[mz_persist_proc::test(tokio::test)]
1509 #[cfg_attr(miri, ignore)] async fn append_with_invalid_upper(dyncfgs: ConfigUpdates) {
1511 let data = [
1512 (("1".to_owned(), "one".to_owned()), 1, 1),
1513 (("2".to_owned(), "two".to_owned()), 2, 1),
1514 ];
1515
1516 let client = new_test_client(&dyncfgs).await;
1517
1518 let shard_id = ShardId::new();
1519
1520 let (mut write, _read) = client
1521 .expect_open::<String, String, u64, i64>(shard_id)
1522 .await;
1523
1524 write
1525 .expect_append(&data[..], write.upper().clone(), vec![3])
1526 .await;
1527
1528 let data = [
1529 (("5".to_owned(), "fünf".to_owned()), 5, 1),
1530 (("6".to_owned(), "sechs".to_owned()), 6, 1),
1531 ];
1532 let res = write
1533 .append(
1534 data.iter(),
1535 Antichain::from_elem(5),
1536 Antichain::from_elem(7),
1537 )
1538 .await;
1539 assert_eq!(
1540 res,
1541 Ok(Err(UpperMismatch {
1542 expected: Antichain::from_elem(5),
1543 current: Antichain::from_elem(3)
1544 }))
1545 );
1546
1547 assert_eq!(write.upper(), &Antichain::from_elem(3));
1549 }
1550
1551 #[allow(unused)]
1554 async fn sync_send(dyncfgs: ConfigUpdates) {
1555 mz_ore::test::init_logging();
1556
1557 fn is_send_sync<T: Send + Sync>(_x: T) -> bool {
1558 true
1559 }
1560
1561 let client = new_test_client(&dyncfgs).await;
1562
1563 let (write, read) = client
1564 .expect_open::<String, String, u64, i64>(ShardId::new())
1565 .await;
1566
1567 assert!(is_send_sync(client));
1568 assert!(is_send_sync(write));
1569 assert!(is_send_sync(read));
1570 }
1571
1572 #[mz_persist_proc::test(tokio::test)]
1573 #[cfg_attr(miri, ignore)] async fn compare_and_append(dyncfgs: ConfigUpdates) {
1575 let data = vec![
1576 (("1".to_owned(), "one".to_owned()), 1, 1),
1577 (("2".to_owned(), "two".to_owned()), 2, 1),
1578 (("3".to_owned(), "three".to_owned()), 3, 1),
1579 ];
1580
1581 let id = ShardId::new();
1582 let client = new_test_client(&dyncfgs).await;
1583 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1584
1585 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1586
1587 assert_eq!(write1.upper(), &Antichain::from_elem(u64::minimum()));
1588 assert_eq!(write2.upper(), &Antichain::from_elem(u64::minimum()));
1589 assert_eq!(read.since(), &Antichain::from_elem(u64::minimum()));
1590
1591 write1
1593 .expect_compare_and_append(&data[..2], u64::minimum(), 3)
1594 .await;
1595 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1596
1597 assert_eq!(
1598 read.expect_snapshot_and_fetch(2).await,
1599 all_ok(&data[..2], 2)
1600 );
1601
1602 let res = write2
1604 .compare_and_append(
1605 &data[..2],
1606 Antichain::from_elem(u64::minimum()),
1607 Antichain::from_elem(3),
1608 )
1609 .await;
1610 assert_eq!(
1611 res,
1612 Ok(Err(UpperMismatch {
1613 expected: Antichain::from_elem(u64::minimum()),
1614 current: Antichain::from_elem(3)
1615 }))
1616 );
1617
1618 assert_eq!(write2.upper(), &Antichain::from_elem(3));
1620
1621 write2.expect_compare_and_append(&data[2..], 3, 4).await;
1623
1624 assert_eq!(write2.upper(), &Antichain::from_elem(4));
1625
1626 assert_eq!(read.expect_snapshot_and_fetch(3).await, all_ok(&data, 3));
1627 }
1628
1629 #[mz_persist_proc::test(tokio::test)]
1630 #[cfg_attr(miri, ignore)] async fn overlapping_append(dyncfgs: ConfigUpdates) {
1632 mz_ore::test::init_logging_default("info");
1633
1634 let data = vec![
1635 (("1".to_owned(), "one".to_owned()), 1, 1),
1636 (("2".to_owned(), "two".to_owned()), 2, 1),
1637 (("3".to_owned(), "three".to_owned()), 3, 1),
1638 (("4".to_owned(), "vier".to_owned()), 4, 1),
1639 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1640 ];
1641
1642 let id = ShardId::new();
1643 let client = new_test_client(&dyncfgs).await;
1644
1645 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1646
1647 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1648
1649 let mut listen = read.clone("").await.expect_listen(0).await;
1651
1652 write1
1654 .expect_append(&data[..2], write1.upper().clone(), vec![3])
1655 .await;
1656 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1657
1658 write2
1660 .expect_append(&data[..4], write2.upper().clone(), vec![5])
1661 .await;
1662 assert_eq!(write2.upper(), &Antichain::from_elem(5));
1663
1664 write1
1666 .expect_append(&data[2..5], write1.upper().clone(), vec![6])
1667 .await;
1668 assert_eq!(write1.upper(), &Antichain::from_elem(6));
1669
1670 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1671
1672 assert_eq!(
1673 listen.read_until(&6).await,
1674 (all_ok(&data[..], 1), Antichain::from_elem(6))
1675 );
1676 }
1677
1678 #[mz_persist_proc::test(tokio::test)]
1681 #[cfg_attr(miri, ignore)] async fn contiguous_append(dyncfgs: ConfigUpdates) {
1683 let data = vec![
1684 (("1".to_owned(), "one".to_owned()), 1, 1),
1685 (("2".to_owned(), "two".to_owned()), 2, 1),
1686 (("3".to_owned(), "three".to_owned()), 3, 1),
1687 (("4".to_owned(), "vier".to_owned()), 4, 1),
1688 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1689 ];
1690
1691 let id = ShardId::new();
1692 let client = new_test_client(&dyncfgs).await;
1693
1694 let (mut write, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1695
1696 write
1698 .expect_append(&data[..2], write.upper().clone(), vec![3])
1699 .await;
1700 assert_eq!(write.upper(), &Antichain::from_elem(3));
1701
1702 let result = write
1705 .append(
1706 &data[4..5],
1707 Antichain::from_elem(5),
1708 Antichain::from_elem(6),
1709 )
1710 .await;
1711 assert_eq!(
1712 result,
1713 Ok(Err(UpperMismatch {
1714 expected: Antichain::from_elem(5),
1715 current: Antichain::from_elem(3)
1716 }))
1717 );
1718
1719 write.expect_append(&data[2..5], vec![3], vec![6]).await;
1721 assert_eq!(write.upper(), &Antichain::from_elem(6));
1722
1723 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1724 }
1725
1726 #[mz_persist_proc::test(tokio::test)]
1729 #[cfg_attr(miri, ignore)] async fn noncontiguous_append_per_writer(dyncfgs: ConfigUpdates) {
1731 let data = vec![
1732 (("1".to_owned(), "one".to_owned()), 1, 1),
1733 (("2".to_owned(), "two".to_owned()), 2, 1),
1734 (("3".to_owned(), "three".to_owned()), 3, 1),
1735 (("4".to_owned(), "vier".to_owned()), 4, 1),
1736 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1737 ];
1738
1739 let id = ShardId::new();
1740 let client = new_test_client(&dyncfgs).await;
1741
1742 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1743
1744 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1745
1746 write1
1748 .expect_append(&data[..2], write1.upper().clone(), vec![3])
1749 .await;
1750 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1751
1752 write2.upper = Antichain::from_elem(3);
1754 write2
1755 .expect_append(&data[2..4], write2.upper().clone(), vec![5])
1756 .await;
1757 assert_eq!(write2.upper(), &Antichain::from_elem(5));
1758
1759 write1.upper = Antichain::from_elem(5);
1761 write1
1762 .expect_append(&data[4..5], write1.upper().clone(), vec![6])
1763 .await;
1764 assert_eq!(write1.upper(), &Antichain::from_elem(6));
1765
1766 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1767 }
1768
1769 #[mz_persist_proc::test(tokio::test)]
1772 #[cfg_attr(miri, ignore)] async fn contiguous_compare_and_append(dyncfgs: ConfigUpdates) {
1774 let data = vec![
1775 (("1".to_owned(), "one".to_owned()), 1, 1),
1776 (("2".to_owned(), "two".to_owned()), 2, 1),
1777 (("3".to_owned(), "three".to_owned()), 3, 1),
1778 (("4".to_owned(), "vier".to_owned()), 4, 1),
1779 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1780 ];
1781
1782 let id = ShardId::new();
1783 let client = new_test_client(&dyncfgs).await;
1784
1785 let (mut write, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1786
1787 write.expect_compare_and_append(&data[..2], 0, 3).await;
1789 assert_eq!(write.upper(), &Antichain::from_elem(3));
1790
1791 let result = write
1794 .compare_and_append(
1795 &data[4..5],
1796 Antichain::from_elem(5),
1797 Antichain::from_elem(6),
1798 )
1799 .await;
1800 assert_eq!(
1801 result,
1802 Ok(Err(UpperMismatch {
1803 expected: Antichain::from_elem(5),
1804 current: Antichain::from_elem(3)
1805 }))
1806 );
1807
1808 write.expect_compare_and_append(&data[2..5], 3, 6).await;
1811 assert_eq!(write.upper(), &Antichain::from_elem(6));
1812
1813 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1814 }
1815
1816 #[mz_persist_proc::test(tokio::test)]
1819 #[cfg_attr(miri, ignore)] async fn noncontiguous_compare_and_append_per_writer(dyncfgs: ConfigUpdates) {
1821 let data = vec![
1822 (("1".to_owned(), "one".to_owned()), 1, 1),
1823 (("2".to_owned(), "two".to_owned()), 2, 1),
1824 (("3".to_owned(), "three".to_owned()), 3, 1),
1825 (("4".to_owned(), "vier".to_owned()), 4, 1),
1826 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1827 ];
1828
1829 let id = ShardId::new();
1830 let client = new_test_client(&dyncfgs).await;
1831
1832 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1833
1834 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1835
1836 write1.expect_compare_and_append(&data[..2], 0, 3).await;
1838 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1839
1840 write2.expect_compare_and_append(&data[2..4], 3, 5).await;
1842 assert_eq!(write2.upper(), &Antichain::from_elem(5));
1843
1844 write1.expect_compare_and_append(&data[4..5], 5, 6).await;
1846 assert_eq!(write1.upper(), &Antichain::from_elem(6));
1847
1848 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1849 }
1850
1851 #[mz_ore::test]
1852 fn fmt_ids() {
1853 assert_eq!(
1854 format!("{}", LeasedReaderId([0u8; 16])),
1855 "r00000000-0000-0000-0000-000000000000"
1856 );
1857 assert_eq!(
1858 format!("{:?}", LeasedReaderId([0u8; 16])),
1859 "LeasedReaderId(00000000-0000-0000-0000-000000000000)"
1860 );
1861 }
1862
1863 #[mz_persist_proc::test(tokio::test(flavor = "multi_thread"))]
1864 #[cfg_attr(miri, ignore)] async fn concurrency(dyncfgs: ConfigUpdates) {
1866 let data = DataGenerator::small();
1867
1868 const NUM_WRITERS: usize = 2;
1869 let id = ShardId::new();
1870 let client = new_test_client(&dyncfgs).await;
1871 let mut handles = Vec::<mz_ore::task::JoinHandle<()>>::new();
1872 for idx in 0..NUM_WRITERS {
1873 let (data, client) = (data.clone(), client.clone());
1874
1875 let (batch_tx, mut batch_rx) = tokio::sync::mpsc::channel(1);
1876
1877 let client1 = client.clone();
1878 let handle = mz_ore::task::spawn(|| format!("writer-{}", idx), async move {
1879 let (write, _) = client1.expect_open::<Vec<u8>, Vec<u8>, u64, i64>(id).await;
1880 let mut current_upper = 0;
1881 for batch in data.batches() {
1882 let new_upper = match batch.get(batch.len() - 1) {
1883 Some((_, max_ts, _)) => u64::decode(max_ts) + 1,
1884 None => continue,
1885 };
1886 if PartialOrder::less_equal(&Antichain::from_elem(new_upper), write.upper()) {
1901 continue;
1902 }
1903
1904 let current_upper_chain = Antichain::from_elem(current_upper);
1905 current_upper = new_upper;
1906 let new_upper_chain = Antichain::from_elem(new_upper);
1907 let mut builder = write.builder(current_upper_chain);
1908
1909 for ((k, v), t, d) in batch.iter() {
1910 builder
1911 .add(&k.to_vec(), &v.to_vec(), &u64::decode(t), &i64::decode(d))
1912 .await
1913 .expect("invalid usage");
1914 }
1915
1916 let batch = builder
1917 .finish(new_upper_chain)
1918 .await
1919 .expect("invalid usage");
1920
1921 match batch_tx.send(batch).await {
1922 Ok(_) => (),
1923 Err(e) => panic!("send error: {}", e),
1924 }
1925 }
1926 });
1927 handles.push(handle);
1928
1929 let handle = mz_ore::task::spawn(|| format!("appender-{}", idx), async move {
1930 let (mut write, _) = client.expect_open::<Vec<u8>, Vec<u8>, u64, i64>(id).await;
1931
1932 while let Some(batch) = batch_rx.recv().await {
1933 let lower = batch.lower().clone();
1934 let upper = batch.upper().clone();
1935 write
1936 .append_batch(batch, lower, upper)
1937 .await
1938 .expect("invalid usage")
1939 .expect("unexpected upper");
1940 }
1941 });
1942 handles.push(handle);
1943 }
1944
1945 for handle in handles {
1946 let () = handle.await;
1947 }
1948
1949 let expected = data.records().collect::<Vec<_>>();
1950 let max_ts = expected.last().map(|(_, t, _)| *t).unwrap_or_default();
1951 let (_, mut read) = client.expect_open::<Vec<u8>, Vec<u8>, u64, i64>(id).await;
1952 assert_eq!(
1953 read.expect_snapshot_and_fetch(max_ts).await,
1954 all_ok(expected.iter(), max_ts)
1955 );
1956 }
1957
1958 #[mz_persist_proc::test(tokio::test)]
1962 #[cfg_attr(miri, ignore)] async fn regression_blocking_reads(dyncfgs: ConfigUpdates) {
1964 let waker = noop_waker();
1965 let mut cx = Context::from_waker(&waker);
1966
1967 let data = [
1968 (("1".to_owned(), "one".to_owned()), 1, 1),
1969 (("2".to_owned(), "two".to_owned()), 2, 1),
1970 (("3".to_owned(), "three".to_owned()), 3, 1),
1971 ];
1972
1973 let id = ShardId::new();
1974 let client = new_test_client(&dyncfgs).await;
1975 let (mut write, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1976
1977 let mut listen = read.clone("").await.expect_listen(1).await;
1979 let mut listen_next = Box::pin(listen.fetch_next());
1980 for _ in 0..100 {
1984 assert!(
1985 Pin::new(&mut listen_next).poll(&mut cx).is_pending(),
1986 "listen::next unexpectedly ready"
1987 );
1988 }
1989
1990 write
1992 .expect_compare_and_append(&data[..2], u64::minimum(), 3)
1993 .await;
1994
1995 assert_eq!(
1998 listen_next.await,
1999 vec![
2000 ListenEvent::Updates(vec![(("2".to_owned(), "two".to_owned()), 2, 1)]),
2001 ListenEvent::Progress(Antichain::from_elem(3)),
2002 ]
2003 );
2004
2005 let mut snap = Box::pin(read.expect_snapshot_and_fetch(3));
2019 for _ in 0..100 {
2020 assert!(
2021 Pin::new(&mut snap).poll(&mut cx).is_pending(),
2022 "snapshot unexpectedly ready"
2023 );
2024 }
2025
2026 write.expect_compare_and_append(&data[2..], 3, 4).await;
2028
2029 assert_eq!(snap.await, all_ok(&data[..], 3));
2031 }
2032
2033 #[mz_persist_proc::test(tokio::test)]
2034 #[cfg_attr(miri, ignore)] async fn heartbeat_task_shutdown(dyncfgs: ConfigUpdates) {
2036 let mut cache = new_test_client_cache(&dyncfgs);
2039 cache
2040 .cfg
2041 .set_config(&READER_LEASE_DURATION, Duration::from_millis(1));
2042 cache.cfg.writer_lease_duration = Duration::from_millis(1);
2043 let (_write, mut read) = cache
2044 .open(PersistLocation::new_in_mem())
2045 .await
2046 .expect("client construction failed")
2047 .expect_open::<(), (), u64, i64>(ShardId::new())
2048 .await;
2049 let read_unexpired_state = read
2050 .unexpired_state
2051 .take()
2052 .expect("handle should have unexpired state");
2053 read.expire().await;
2054 read_unexpired_state.heartbeat_task.await
2055 }
2056
2057 #[mz_persist_proc::test(tokio::test)]
2060 #[cfg_attr(miri, ignore)] async fn finalize_empty_shard(dyncfgs: ConfigUpdates) {
2062 let persist_client = new_test_client(&dyncfgs).await;
2063
2064 let shard_id = ShardId::new();
2065 pub const CRITICAL_SINCE: CriticalReaderId =
2066 CriticalReaderId([0, 0, 0, 0, 17, 17, 34, 34, 51, 51, 68, 68, 68, 68, 68, 68]);
2067
2068 let (mut write, mut read) = persist_client
2069 .expect_open::<(), (), u64, i64>(shard_id)
2070 .await;
2071
2072 let () = read.downgrade_since(&Antichain::new()).await;
2075 let () = write.advance_upper(&Antichain::new()).await;
2076
2077 let mut since_handle: SinceHandle<(), (), u64, i64> = persist_client
2078 .open_critical_since(
2079 shard_id,
2080 CRITICAL_SINCE,
2081 Opaque::encode(&0u64),
2082 Diagnostics::for_tests(),
2083 )
2084 .await
2085 .expect("invalid persist usage");
2086
2087 let epoch = since_handle.opaque().clone();
2088 let new_since = Antichain::new();
2089 let downgrade = since_handle
2090 .compare_and_downgrade_since(&epoch, (&epoch, &new_since))
2091 .await;
2092
2093 assert!(
2094 downgrade.is_ok(),
2095 "downgrade of critical handle must succeed"
2096 );
2097
2098 let finalize = persist_client
2099 .finalize_shard::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2100 .await;
2101
2102 assert_ok!(finalize, "finalization must succeed");
2103
2104 let is_finalized = persist_client
2105 .is_finalized::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2106 .await
2107 .expect("invalid persist usage");
2108 assert!(is_finalized, "shard must still be finalized");
2109 }
2110
2111 #[mz_persist_proc::test(tokio::test)]
2115 #[cfg_attr(miri, ignore)] async fn finalize_shard(dyncfgs: ConfigUpdates) {
2117 const DATA: &[(((), ()), u64, i64)] = &[(((), ()), 0, 1)];
2118 let persist_client = new_test_client(&dyncfgs).await;
2119
2120 let shard_id = ShardId::new();
2121 pub const CRITICAL_SINCE: CriticalReaderId =
2122 CriticalReaderId([0, 0, 0, 0, 17, 17, 34, 34, 51, 51, 68, 68, 68, 68, 68, 68]);
2123
2124 let (mut write, mut read) = persist_client
2125 .expect_open::<(), (), u64, i64>(shard_id)
2126 .await;
2127
2128 let () = write
2130 .compare_and_append(DATA, Antichain::from_elem(0), Antichain::from_elem(1))
2131 .await
2132 .expect("usage should be valid")
2133 .expect("upper should match");
2134
2135 let () = read.downgrade_since(&Antichain::new()).await;
2138 let () = write.advance_upper(&Antichain::new()).await;
2139
2140 let mut since_handle: SinceHandle<(), (), u64, i64> = persist_client
2141 .open_critical_since(
2142 shard_id,
2143 CRITICAL_SINCE,
2144 Opaque::encode(&0u64),
2145 Diagnostics::for_tests(),
2146 )
2147 .await
2148 .expect("invalid persist usage");
2149
2150 let epoch = since_handle.opaque().clone();
2151 let new_since = Antichain::new();
2152 let downgrade = since_handle
2153 .compare_and_downgrade_since(&epoch, (&epoch, &new_since))
2154 .await;
2155
2156 assert!(
2157 downgrade.is_ok(),
2158 "downgrade of critical handle must succeed"
2159 );
2160
2161 let finalize = persist_client
2162 .finalize_shard::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2163 .await;
2164
2165 assert_ok!(finalize, "finalization must succeed");
2166
2167 let is_finalized = persist_client
2168 .is_finalized::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2169 .await
2170 .expect("invalid persist usage");
2171 assert!(is_finalized, "shard must still be finalized");
2172 }
2173
2174 proptest! {
2175 #![proptest_config(ProptestConfig::with_cases(4096))]
2176
2177 #[mz_ore::test]
2178 #[cfg_attr(miri, ignore)] fn shard_id_protobuf_roundtrip(expect in any::<ShardId>() ) {
2180 let actual = protobuf_roundtrip::<_, String>(&expect);
2181 assert_ok!(actual);
2182 assert_eq!(actual.unwrap(), expect);
2183 }
2184 }
2185}