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