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 BackpressureMetrics, 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))]
557 pub fn batch_builder<K, V, T, D>(
558 &self,
559 shard_id: ShardId,
560 shard_name: &str,
561 write_schemas: Schemas<K, V>,
562 lower: Antichain<T>,
563 max_runs: Option<usize>,
564 ) -> BatchBuilder<K, V, T, D>
565 where
566 K: Debug + Codec,
567 V: Debug + Codec,
568 T: Timestamp + Lattice + Codec64 + TotalOrder + Sync,
569 D: Monoid + Ord + Codec64 + Send + Sync,
570 {
571 let mut compact_cfg = CompactConfig::new(&self.cfg, shard_id);
572 if let Some(max_runs) = max_runs {
573 compact_cfg.batch.max_runs = Some(max_runs);
574 }
575 WriteHandle::builder_inner(
576 &self.cfg,
577 compact_cfg,
578 Arc::clone(&self.metrics),
579 self.metrics.shards.shard(&shard_id, shard_name),
580 &self.metrics.user,
581 Arc::clone(&self.isolated_runtime),
582 Arc::clone(&self.blob),
583 shard_id,
584 write_schemas,
585 lower,
586 )
587 }
588
589 pub fn batch_from_transmittable_batch<K, V, T, D>(
598 &self,
599 shard_id: &ShardId,
600 batch: ProtoBatch,
601 ) -> Batch<K, V, T, D>
602 where
603 K: Debug + Codec,
604 V: Debug + Codec,
605 T: Timestamp + Lattice + Codec64 + Sync,
606 D: Monoid + Ord + Codec64 + Send + Sync,
607 {
608 let batch_shard_id: ShardId = batch
609 .shard_id
610 .into_rust()
611 .expect("valid transmittable batch");
612 assert_eq!(&batch_shard_id, shard_id);
613
614 let shard_metrics = self.metrics.shards.shard(shard_id, "peek_stash");
615
616 let ret = Batch {
617 batch_delete_enabled: BATCH_DELETE_ENABLED.get(&self.cfg),
618 metrics: Arc::clone(&self.metrics),
619 shard_metrics,
620 version: Version::parse(&batch.version).expect("valid transmittable batch"),
621 schemas: (batch.key_schema, batch.val_schema),
622 batch: batch
623 .batch
624 .into_rust_if_some("ProtoBatch::batch")
625 .expect("valid transmittable batch"),
626 blob: Arc::clone(&self.blob),
627 _phantom: std::marker::PhantomData,
628 };
629
630 assert_eq!(&ret.shard_id(), shard_id);
631 ret
632 }
633
634 #[allow(clippy::unused_async)]
649 pub async fn read_batches_consolidated<K, V, T, D>(
650 &mut self,
651 shard_id: ShardId,
652 as_of: Antichain<T>,
653 read_schemas: Schemas<K, V>,
654 batches: Vec<Batch<K, V, T, D>>,
655 should_fetch_part: impl for<'a> Fn(Option<&'a LazyPartStats>) -> bool,
656 memory_budget_bytes: usize,
657 ) -> Result<Cursor<K, V, T, D, Vec<Batch<K, V, T, D>>>, Since<T>>
658 where
659 K: Debug + Codec + Ord,
660 V: Debug + Codec + Ord,
661 T: Timestamp + Lattice + Codec64 + TotalOrder + Sync,
662 D: Monoid + Ord + Codec64 + Send + Sync,
663 {
664 let shard_metrics = self.metrics.shards.shard(&shard_id, "peek_stash");
665
666 let hollow_batches = batches.iter().map(|b| b.batch.clone()).collect_vec();
667
668 ReadHandle::read_batches_consolidated(
669 &self.cfg,
670 Arc::clone(&self.metrics),
671 shard_metrics,
672 self.metrics.read.snapshot.clone(),
673 Arc::clone(&self.blob),
674 shard_id,
675 as_of,
676 read_schemas,
677 &hollow_batches,
678 batches,
679 should_fetch_part,
680 memory_budget_bytes,
681 )
682 }
683
684 pub async fn get_schema<K, V, T, D>(
686 &self,
687 shard_id: ShardId,
688 schema_id: SchemaId,
689 diagnostics: Diagnostics,
690 ) -> Result<Option<(K::Schema, V::Schema)>, InvalidUsage<T>>
691 where
692 K: Debug + Codec,
693 V: Debug + Codec,
694 T: Timestamp + Lattice + Codec64 + Sync,
695 D: Monoid + Codec64 + Send + Sync,
696 {
697 let machine = self
698 .make_machine::<K, V, T, D>(shard_id, diagnostics)
699 .await?;
700 Ok(machine.get_schema(schema_id))
701 }
702
703 pub async fn latest_schema<K, V, T, D>(
705 &self,
706 shard_id: ShardId,
707 diagnostics: Diagnostics,
708 ) -> Result<Option<(SchemaId, K::Schema, V::Schema)>, InvalidUsage<T>>
709 where
710 K: Debug + Codec,
711 V: Debug + Codec,
712 T: Timestamp + Lattice + Codec64 + Sync,
713 D: Monoid + Codec64 + Send + Sync,
714 {
715 let machine = self
716 .make_machine::<K, V, T, D>(shard_id, diagnostics)
717 .await?;
718 Ok(machine.latest_schema())
719 }
720
721 pub async fn recent_upper<K, V, T, D>(
732 &self,
733 shard_id: ShardId,
734 diagnostics: Diagnostics,
735 ) -> Result<Antichain<T>, InvalidUsage<T>>
736 where
737 K: Debug + Codec,
738 V: Debug + Codec,
739 T: Timestamp + Lattice + Codec64 + Sync,
740 D: Monoid + Codec64 + Send + Sync,
741 {
742 let machine = self
743 .make_machine::<K, V, T, D>(shard_id, diagnostics)
744 .await?;
745 Ok(machine.applier.fetch_upper(|upper| upper.clone()).await)
746 }
747
748 pub async fn register_schema<K, V, T, D>(
760 &self,
761 shard_id: ShardId,
762 key_schema: &K::Schema,
763 val_schema: &V::Schema,
764 diagnostics: Diagnostics,
765 ) -> Result<Option<SchemaId>, InvalidUsage<T>>
766 where
767 K: Debug + Codec,
768 V: Debug + Codec,
769 T: Timestamp + Lattice + Codec64 + Sync,
770 D: Monoid + Codec64 + Send + Sync,
771 {
772 let machine = self
773 .make_machine::<K, V, T, D>(shard_id, diagnostics)
774 .await?;
775 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
776
777 let (schema_id, maintenance) = machine.register_schema(key_schema, val_schema).await;
778 maintenance.start_performing(&machine, &gc);
779
780 Ok(schema_id)
781 }
782
783 pub async fn compare_and_evolve_schema<K, V, T, D>(
797 &self,
798 shard_id: ShardId,
799 expected: SchemaId,
800 key_schema: &K::Schema,
801 val_schema: &V::Schema,
802 diagnostics: Diagnostics,
803 ) -> Result<CaESchema<K, V>, InvalidUsage<T>>
804 where
805 K: Debug + Codec,
806 V: Debug + Codec,
807 T: Timestamp + Lattice + Codec64 + Sync,
808 D: Monoid + Codec64 + Send + Sync,
809 {
810 let machine = self
811 .make_machine::<K, V, T, D>(shard_id, diagnostics)
812 .await?;
813 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
814 let (res, maintenance) = machine
815 .compare_and_evolve_schema(expected, key_schema, val_schema)
816 .await;
817 maintenance.start_performing(&machine, &gc);
818 Ok(res)
819 }
820
821 pub async fn is_finalized<K, V, T, D>(
825 &self,
826 shard_id: ShardId,
827 diagnostics: Diagnostics,
828 ) -> Result<bool, InvalidUsage<T>>
829 where
830 K: Debug + Codec,
831 V: Debug + Codec,
832 T: Timestamp + Lattice + Codec64 + Sync,
833 D: Monoid + Codec64 + Send + Sync,
834 {
835 let machine = self
836 .make_machine::<K, V, T, D>(shard_id, diagnostics)
837 .await?;
838 Ok(machine.is_finalized())
839 }
840
841 #[instrument(level = "debug", fields(shard = %shard_id))]
852 pub async fn finalize_shard<K, V, T, D>(
853 &self,
854 shard_id: ShardId,
855 diagnostics: Diagnostics,
856 ) -> Result<(), InvalidUsage<T>>
857 where
858 K: Debug + Codec,
859 V: Debug + Codec,
860 T: Timestamp + Lattice + Codec64 + Sync,
861 D: Monoid + Codec64 + Send + Sync,
862 {
863 let machine = self
864 .make_machine::<K, V, T, D>(shard_id, diagnostics)
865 .await?;
866
867 let maintenance = machine.become_tombstone().await?;
868 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
869
870 let () = maintenance.perform(&machine, &gc).await;
871
872 Ok(())
873 }
874
875 pub async fn upgrade_version<K, V, T, D>(
878 &self,
879 shard_id: ShardId,
880 diagnostics: Diagnostics,
881 ) -> Result<(), InvalidUsage<T>>
882 where
883 K: Debug + Codec,
884 V: Debug + Codec,
885 T: Timestamp + Lattice + Codec64 + Sync,
886 D: Monoid + Codec64 + Send + Sync,
887 {
888 let machine = self
889 .make_machine::<K, V, T, D>(shard_id, diagnostics)
890 .await?;
891
892 match machine.upgrade_version().await {
893 Ok(maintenance) => {
894 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
895 let () = maintenance.perform(&machine, &gc).await;
896 Ok(())
897 }
898 Err(version) => Err(InvalidUsage::IncompatibleVersion { version }),
899 }
900 }
901
902 pub async fn inspect_shard<T: Timestamp + Lattice + Codec64>(
908 &self,
909 shard_id: &ShardId,
910 ) -> Result<impl serde::Serialize, anyhow::Error> {
911 let state_versions = StateVersions::new(
912 self.cfg.clone(),
913 Arc::clone(&self.consensus),
914 Arc::clone(&self.blob),
915 Arc::clone(&self.metrics),
916 );
917 let versions = state_versions.fetch_all_live_diffs(shard_id).await;
921 if versions.is_empty() {
922 return Err(anyhow::anyhow!("{} does not exist", shard_id));
923 }
924 let state = state_versions
925 .fetch_current_state::<T>(shard_id, versions)
926 .await;
927 let state = state.check_ts_codec(shard_id)?;
928 Ok(state)
929 }
930
931 #[cfg(test)]
933 #[track_caller]
934 pub async fn expect_open<K, V, T, D>(
935 &self,
936 shard_id: ShardId,
937 ) -> (WriteHandle<K, V, T, D>, ReadHandle<K, V, T, D>)
938 where
939 K: Debug + Codec,
940 V: Debug + Codec,
941 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
942 D: Monoid + Ord + Codec64 + Send + Sync,
943 K::Schema: Default,
944 V::Schema: Default,
945 {
946 self.open(
947 shard_id,
948 Arc::new(K::Schema::default()),
949 Arc::new(V::Schema::default()),
950 Diagnostics::for_tests(),
951 true,
952 )
953 .await
954 .expect("codec mismatch")
955 }
956
957 pub fn metrics(&self) -> &Arc<Metrics> {
961 &self.metrics
962 }
963}
964
965#[cfg(test)]
966mod tests {
967 use std::future::Future;
968 use std::pin::Pin;
969 use std::task::Context;
970 use std::time::Duration;
971
972 use differential_dataflow::consolidation::consolidate_updates;
973 use differential_dataflow::lattice::Lattice;
974 use futures_task::noop_waker;
975 use mz_dyncfg::ConfigUpdates;
976 use mz_ore::assert_ok;
977 use mz_persist::indexed::encoding::BlobTraceBatchPart;
978 use mz_persist::workload::DataGenerator;
979 use mz_persist_types::codec_impls::{StringSchema, VecU8Schema};
980 use mz_proto::protobuf_roundtrip;
981 use proptest::prelude::*;
982 use timely::order::PartialOrder;
983 use timely::progress::Antichain;
984
985 use crate::batch::BLOB_TARGET_SIZE;
986 use crate::cache::PersistClientCache;
987 use crate::cfg::{BATCH_BUILDER_MAX_OUTSTANDING_PARTS, GC_REMOVE_FINALIZED_ROLLUPS};
988 use crate::critical::Opaque;
989 use crate::error::{CodecConcreteType, CodecMismatch, UpperMismatch};
990 use crate::internal::paths::BlobKey;
991 use crate::read::ListenEvent;
992
993 use super::*;
994
995 pub fn new_test_client_cache(dyncfgs: &ConfigUpdates) -> PersistClientCache {
996 let mut cache = PersistClientCache::new_no_metrics();
999 cache.cfg.set_config(&BLOB_TARGET_SIZE, 10);
1000 cache
1001 .cfg
1002 .set_config(&BATCH_BUILDER_MAX_OUTSTANDING_PARTS, 1);
1003 cache.cfg.set_config(&GC_REMOVE_FINALIZED_ROLLUPS, true);
1005 dyncfgs.apply(cache.cfg());
1006
1007 cache.cfg.compaction_enabled = true;
1009 cache
1010 }
1011
1012 pub async fn new_test_client(dyncfgs: &ConfigUpdates) -> PersistClient {
1013 let cache = new_test_client_cache(dyncfgs);
1014 cache
1015 .open(PersistLocation::new_in_mem())
1016 .await
1017 .expect("client construction failed")
1018 }
1019
1020 pub fn all_ok<'a, K, V, T, D, I>(iter: I, as_of: T) -> Vec<((K, V), T, D)>
1021 where
1022 K: Ord + Clone + 'a,
1023 V: Ord + Clone + 'a,
1024 T: Timestamp + Lattice + Clone + 'a,
1025 D: Monoid + Clone + 'a,
1026 I: IntoIterator<Item = &'a ((K, V), T, D)>,
1027 {
1028 let as_of = Antichain::from_elem(as_of);
1029 let mut ret = iter
1030 .into_iter()
1031 .map(|((k, v), t, d)| {
1032 let mut t = t.clone();
1033 t.advance_by(as_of.borrow());
1034 ((k.clone(), v.clone()), t, d.clone())
1035 })
1036 .collect();
1037 consolidate_updates(&mut ret);
1038 ret
1039 }
1040
1041 pub async fn expect_fetch_part<K, V, T, D>(
1042 blob: &dyn Blob,
1043 key: &BlobKey,
1044 metrics: &Metrics,
1045 read_schemas: &Schemas<K, V>,
1046 ) -> (BlobTraceBatchPart<T>, Vec<((K, V), T, D)>)
1047 where
1048 K: Codec + Clone,
1049 V: Codec + Clone,
1050 T: Timestamp + Codec64,
1051 D: Codec64,
1052 {
1053 let value = blob
1054 .get(key)
1055 .await
1056 .expect("failed to fetch part")
1057 .expect("missing part");
1058 let mut part =
1059 BlobTraceBatchPart::decode(&value, &metrics.columnar).expect("failed to decode part");
1060 let structured = part
1061 .updates
1062 .into_part::<K, V>(&*read_schemas.key, &*read_schemas.val);
1063 let updates = structured
1064 .decode_iter::<K, V, T, D>(&*read_schemas.key, &*read_schemas.val)
1065 .expect("structured data")
1066 .collect();
1067 (part, updates)
1068 }
1069
1070 #[mz_persist_proc::test(tokio::test)]
1071 #[cfg_attr(miri, ignore)] async fn sanity_check(dyncfgs: ConfigUpdates) {
1073 let data = [
1074 (("1".to_owned(), "one".to_owned()), 1, 1),
1075 (("2".to_owned(), "two".to_owned()), 2, 1),
1076 (("3".to_owned(), "three".to_owned()), 3, 1),
1077 ];
1078
1079 let (mut write, mut read) = new_test_client(&dyncfgs)
1080 .await
1081 .expect_open::<String, String, u64, i64>(ShardId::new())
1082 .await;
1083 assert_eq!(write.upper(), &Antichain::from_elem(u64::minimum()));
1084 assert_eq!(read.since(), &Antichain::from_elem(u64::minimum()));
1085
1086 write
1088 .expect_append(&data[..2], write.upper().clone(), vec![3])
1089 .await;
1090 assert_eq!(write.upper(), &Antichain::from_elem(3));
1091
1092 assert_eq!(
1094 read.expect_snapshot_and_fetch(1).await,
1095 all_ok(&data[..1], 1)
1096 );
1097
1098 let mut listen = read.clone("").await.expect_listen(1).await;
1099
1100 write
1102 .expect_append(&data[2..], write.upper().clone(), vec![4])
1103 .await;
1104 assert_eq!(write.upper(), &Antichain::from_elem(4));
1105
1106 assert_eq!(
1108 listen.read_until(&4).await,
1109 (all_ok(&data[1..], 1), Antichain::from_elem(4))
1110 );
1111
1112 read.downgrade_since(&Antichain::from_elem(2)).await;
1114 assert_eq!(read.since(), &Antichain::from_elem(2));
1115 }
1116
1117 #[mz_persist_proc::test(tokio::test)]
1119 #[cfg_attr(miri, ignore)] async fn open_reader_writer(dyncfgs: ConfigUpdates) {
1121 let data = vec![
1122 (("1".to_owned(), "one".to_owned()), 1, 1),
1123 (("2".to_owned(), "two".to_owned()), 2, 1),
1124 (("3".to_owned(), "three".to_owned()), 3, 1),
1125 ];
1126
1127 let shard_id = ShardId::new();
1128 let client = new_test_client(&dyncfgs).await;
1129 let mut write1 = client
1130 .open_writer::<String, String, u64, i64>(
1131 shard_id,
1132 Arc::new(StringSchema),
1133 Arc::new(StringSchema),
1134 Diagnostics::for_tests(),
1135 )
1136 .await
1137 .expect("codec mismatch");
1138 let mut read1 = client
1139 .open_leased_reader::<String, String, u64, i64>(
1140 shard_id,
1141 Arc::new(StringSchema),
1142 Arc::new(StringSchema),
1143 Diagnostics::for_tests(),
1144 true,
1145 )
1146 .await
1147 .expect("codec mismatch");
1148 let mut read2 = client
1149 .open_leased_reader::<String, String, u64, i64>(
1150 shard_id,
1151 Arc::new(StringSchema),
1152 Arc::new(StringSchema),
1153 Diagnostics::for_tests(),
1154 true,
1155 )
1156 .await
1157 .expect("codec mismatch");
1158 let mut write2 = client
1159 .open_writer::<String, String, u64, i64>(
1160 shard_id,
1161 Arc::new(StringSchema),
1162 Arc::new(StringSchema),
1163 Diagnostics::for_tests(),
1164 )
1165 .await
1166 .expect("codec mismatch");
1167
1168 write2.expect_compare_and_append(&data[..1], 0, 2).await;
1169 assert_eq!(
1170 read2.expect_snapshot_and_fetch(1).await,
1171 all_ok(&data[..1], 1)
1172 );
1173 write1.expect_compare_and_append(&data[1..], 2, 4).await;
1174 assert_eq!(read1.expect_snapshot_and_fetch(3).await, all_ok(&data, 3));
1175 }
1176
1177 #[mz_persist_proc::test(tokio::test)]
1178 #[cfg_attr(miri, ignore)] async fn invalid_usage(dyncfgs: ConfigUpdates) {
1180 let data = vec![
1181 (("1".to_owned(), "one".to_owned()), 1, 1),
1182 (("2".to_owned(), "two".to_owned()), 2, 1),
1183 (("3".to_owned(), "three".to_owned()), 3, 1),
1184 ];
1185
1186 let shard_id0 = "s00000000-0000-0000-0000-000000000000"
1187 .parse::<ShardId>()
1188 .expect("invalid shard id");
1189 let mut client = new_test_client(&dyncfgs).await;
1190
1191 let (mut write0, mut read0) = client
1192 .expect_open::<String, String, u64, i64>(shard_id0)
1193 .await;
1194
1195 write0.expect_compare_and_append(&data, 0, 4).await;
1196
1197 {
1199 fn codecs(
1200 k: &str,
1201 v: &str,
1202 t: &str,
1203 d: &str,
1204 ) -> (String, String, String, String, Option<CodecConcreteType>) {
1205 (k.to_owned(), v.to_owned(), t.to_owned(), d.to_owned(), None)
1206 }
1207
1208 client.shared_states = Arc::new(StateCache::new_no_metrics());
1209 assert_eq!(
1210 client
1211 .open::<Vec<u8>, String, u64, i64>(
1212 shard_id0,
1213 Arc::new(VecU8Schema),
1214 Arc::new(StringSchema),
1215 Diagnostics::for_tests(),
1216 true,
1217 )
1218 .await
1219 .unwrap_err(),
1220 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1221 requested: codecs("Vec<u8>", "String", "u64", "i64"),
1222 actual: codecs("String", "String", "u64", "i64"),
1223 }))
1224 );
1225 assert_eq!(
1226 client
1227 .open::<String, Vec<u8>, u64, i64>(
1228 shard_id0,
1229 Arc::new(StringSchema),
1230 Arc::new(VecU8Schema),
1231 Diagnostics::for_tests(),
1232 true,
1233 )
1234 .await
1235 .unwrap_err(),
1236 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1237 requested: codecs("String", "Vec<u8>", "u64", "i64"),
1238 actual: codecs("String", "String", "u64", "i64"),
1239 }))
1240 );
1241 assert_eq!(
1242 client
1243 .open::<String, String, i64, i64>(
1244 shard_id0,
1245 Arc::new(StringSchema),
1246 Arc::new(StringSchema),
1247 Diagnostics::for_tests(),
1248 true,
1249 )
1250 .await
1251 .unwrap_err(),
1252 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1253 requested: codecs("String", "String", "i64", "i64"),
1254 actual: codecs("String", "String", "u64", "i64"),
1255 }))
1256 );
1257 assert_eq!(
1258 client
1259 .open::<String, String, u64, u64>(
1260 shard_id0,
1261 Arc::new(StringSchema),
1262 Arc::new(StringSchema),
1263 Diagnostics::for_tests(),
1264 true,
1265 )
1266 .await
1267 .unwrap_err(),
1268 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1269 requested: codecs("String", "String", "u64", "u64"),
1270 actual: codecs("String", "String", "u64", "i64"),
1271 }))
1272 );
1273
1274 assert_eq!(
1278 client
1279 .open_leased_reader::<Vec<u8>, String, u64, i64>(
1280 shard_id0,
1281 Arc::new(VecU8Schema),
1282 Arc::new(StringSchema),
1283 Diagnostics::for_tests(),
1284 true,
1285 )
1286 .await
1287 .unwrap_err(),
1288 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1289 requested: codecs("Vec<u8>", "String", "u64", "i64"),
1290 actual: codecs("String", "String", "u64", "i64"),
1291 }))
1292 );
1293 assert_eq!(
1294 client
1295 .open_writer::<Vec<u8>, String, u64, i64>(
1296 shard_id0,
1297 Arc::new(VecU8Schema),
1298 Arc::new(StringSchema),
1299 Diagnostics::for_tests(),
1300 )
1301 .await
1302 .unwrap_err(),
1303 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1304 requested: codecs("Vec<u8>", "String", "u64", "i64"),
1305 actual: codecs("String", "String", "u64", "i64"),
1306 }))
1307 );
1308 }
1309
1310 {
1312 let snap = read0
1313 .snapshot(Antichain::from_elem(3))
1314 .await
1315 .expect("cannot serve requested as_of");
1316
1317 let shard_id1 = "s11111111-1111-1111-1111-111111111111"
1318 .parse::<ShardId>()
1319 .expect("invalid shard id");
1320 let mut fetcher1 = client
1321 .create_batch_fetcher::<String, String, u64, i64>(
1322 shard_id1,
1323 Default::default(),
1324 Default::default(),
1325 false,
1326 Diagnostics::for_tests(),
1327 )
1328 .await
1329 .unwrap();
1330 for part in snap {
1331 let (part, _lease) = part.into_exchangeable_part();
1332 let res = fetcher1.fetch_leased_part(part).await;
1333 assert_eq!(
1334 res.unwrap_err(),
1335 InvalidUsage::BatchNotFromThisShard {
1336 batch_shard: shard_id0,
1337 handle_shard: shard_id1,
1338 }
1339 );
1340 }
1341 }
1342
1343 {
1345 let ts3 = &data[2];
1346 assert_eq!(ts3.1, 3);
1347 let ts3 = vec![ts3.clone()];
1348
1349 assert_eq!(
1352 write0
1353 .append(&ts3, Antichain::from_elem(4), Antichain::from_elem(5))
1354 .await
1355 .unwrap_err(),
1356 InvalidUsage::UpdateNotBeyondLower {
1357 ts: 3,
1358 lower: Antichain::from_elem(4),
1359 },
1360 );
1361 assert_eq!(
1362 write0
1363 .append(&ts3, Antichain::from_elem(2), Antichain::from_elem(3))
1364 .await
1365 .unwrap_err(),
1366 InvalidUsage::UpdateBeyondUpper {
1367 ts: 3,
1368 expected_upper: Antichain::from_elem(3),
1369 },
1370 );
1371 assert_eq!(
1373 write0
1374 .append(&data[..0], Antichain::from_elem(3), Antichain::from_elem(2))
1375 .await
1376 .unwrap_err(),
1377 InvalidUsage::InvalidBounds {
1378 lower: Antichain::from_elem(3),
1379 upper: Antichain::from_elem(2),
1380 },
1381 );
1382
1383 assert_eq!(
1385 write0
1386 .builder(Antichain::from_elem(3))
1387 .finish(Antichain::from_elem(2))
1388 .await
1389 .unwrap_err(),
1390 InvalidUsage::InvalidBounds {
1391 lower: Antichain::from_elem(3),
1392 upper: Antichain::from_elem(2)
1393 },
1394 );
1395 let batch = write0
1396 .batch(&ts3, Antichain::from_elem(3), Antichain::from_elem(4))
1397 .await
1398 .expect("invalid usage");
1399 assert_eq!(
1400 write0
1401 .append_batch(batch, Antichain::from_elem(4), Antichain::from_elem(5))
1402 .await
1403 .unwrap_err(),
1404 InvalidUsage::InvalidBatchBounds {
1405 batch_lower: Antichain::from_elem(3),
1406 batch_upper: Antichain::from_elem(4),
1407 append_lower: Antichain::from_elem(4),
1408 append_upper: Antichain::from_elem(5),
1409 },
1410 );
1411 let batch = write0
1412 .batch(&ts3, Antichain::from_elem(3), Antichain::from_elem(4))
1413 .await
1414 .expect("invalid usage");
1415 assert_eq!(
1416 write0
1417 .append_batch(batch, Antichain::from_elem(2), Antichain::from_elem(3))
1418 .await
1419 .unwrap_err(),
1420 InvalidUsage::InvalidBatchBounds {
1421 batch_lower: Antichain::from_elem(3),
1422 batch_upper: Antichain::from_elem(4),
1423 append_lower: Antichain::from_elem(2),
1424 append_upper: Antichain::from_elem(3),
1425 },
1426 );
1427 let batch = write0
1428 .batch(&ts3, Antichain::from_elem(3), Antichain::from_elem(4))
1429 .await
1430 .expect("invalid usage");
1431 assert!(matches!(
1434 write0
1435 .append_batch(batch, Antichain::from_elem(3), Antichain::from_elem(3))
1436 .await
1437 .unwrap_err(),
1438 InvalidUsage::InvalidEmptyTimeInterval { .. }
1439 ));
1440 }
1441 }
1442
1443 #[mz_persist_proc::test(tokio::test)]
1444 #[cfg_attr(miri, ignore)] async fn multiple_shards(dyncfgs: ConfigUpdates) {
1446 let data1 = [
1447 (("1".to_owned(), "one".to_owned()), 1, 1),
1448 (("2".to_owned(), "two".to_owned()), 2, 1),
1449 ];
1450
1451 let data2 = [(("1".to_owned(), ()), 1, 1), (("2".to_owned(), ()), 2, 1)];
1452
1453 let client = new_test_client(&dyncfgs).await;
1454
1455 let (mut write1, mut read1) = client
1456 .expect_open::<String, String, u64, i64>(ShardId::new())
1457 .await;
1458
1459 let (mut write2, mut read2) = client
1462 .expect_open::<String, (), u64, i64>(ShardId::new())
1463 .await;
1464
1465 write1
1466 .expect_compare_and_append(&data1[..], u64::minimum(), 3)
1467 .await;
1468
1469 write2
1470 .expect_compare_and_append(&data2[..], u64::minimum(), 3)
1471 .await;
1472
1473 assert_eq!(
1474 read1.expect_snapshot_and_fetch(2).await,
1475 all_ok(&data1[..], 2)
1476 );
1477
1478 assert_eq!(
1479 read2.expect_snapshot_and_fetch(2).await,
1480 all_ok(&data2[..], 2)
1481 );
1482 }
1483
1484 #[mz_persist_proc::test(tokio::test)]
1485 #[cfg_attr(miri, ignore)] async fn fetch_upper(dyncfgs: ConfigUpdates) {
1487 let data = [
1488 (("1".to_owned(), "one".to_owned()), 1, 1),
1489 (("2".to_owned(), "two".to_owned()), 2, 1),
1490 ];
1491
1492 let client = new_test_client(&dyncfgs).await;
1493
1494 let shard_id = ShardId::new();
1495
1496 let (mut write1, _read1) = client
1497 .expect_open::<String, String, u64, i64>(shard_id)
1498 .await;
1499
1500 let (mut write2, _read2) = client
1501 .expect_open::<String, String, u64, i64>(shard_id)
1502 .await;
1503
1504 write1
1505 .expect_append(&data[..], write1.upper().clone(), vec![3])
1506 .await;
1507
1508 assert_eq!(write2.fetch_recent_upper().await, &Antichain::from_elem(3));
1510
1511 assert_eq!(write2.upper(), &Antichain::from_elem(3));
1514 }
1515
1516 #[mz_persist_proc::test(tokio::test)]
1517 #[cfg_attr(miri, ignore)] async fn append_with_invalid_upper(dyncfgs: ConfigUpdates) {
1519 let data = [
1520 (("1".to_owned(), "one".to_owned()), 1, 1),
1521 (("2".to_owned(), "two".to_owned()), 2, 1),
1522 ];
1523
1524 let client = new_test_client(&dyncfgs).await;
1525
1526 let shard_id = ShardId::new();
1527
1528 let (mut write, _read) = client
1529 .expect_open::<String, String, u64, i64>(shard_id)
1530 .await;
1531
1532 write
1533 .expect_append(&data[..], write.upper().clone(), vec![3])
1534 .await;
1535
1536 let data = [
1537 (("5".to_owned(), "fünf".to_owned()), 5, 1),
1538 (("6".to_owned(), "sechs".to_owned()), 6, 1),
1539 ];
1540 let res = write
1541 .append(
1542 data.iter(),
1543 Antichain::from_elem(5),
1544 Antichain::from_elem(7),
1545 )
1546 .await;
1547 assert_eq!(
1548 res,
1549 Ok(Err(UpperMismatch {
1550 expected: Antichain::from_elem(5),
1551 current: Antichain::from_elem(3)
1552 }))
1553 );
1554
1555 assert_eq!(write.upper(), &Antichain::from_elem(3));
1557 }
1558
1559 #[allow(unused)]
1562 async fn sync_send(dyncfgs: ConfigUpdates) {
1563 mz_ore::test::init_logging();
1564
1565 fn is_send_sync<T: Send + Sync>(_x: T) -> bool {
1566 true
1567 }
1568
1569 let client = new_test_client(&dyncfgs).await;
1570
1571 let (write, read) = client
1572 .expect_open::<String, String, u64, i64>(ShardId::new())
1573 .await;
1574
1575 assert!(is_send_sync(client));
1576 assert!(is_send_sync(write));
1577 assert!(is_send_sync(read));
1578 }
1579
1580 #[mz_persist_proc::test(tokio::test)]
1581 #[cfg_attr(miri, ignore)] async fn compare_and_append(dyncfgs: ConfigUpdates) {
1583 let data = vec![
1584 (("1".to_owned(), "one".to_owned()), 1, 1),
1585 (("2".to_owned(), "two".to_owned()), 2, 1),
1586 (("3".to_owned(), "three".to_owned()), 3, 1),
1587 ];
1588
1589 let id = ShardId::new();
1590 let client = new_test_client(&dyncfgs).await;
1591 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1592
1593 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1594
1595 assert_eq!(write1.upper(), &Antichain::from_elem(u64::minimum()));
1596 assert_eq!(write2.upper(), &Antichain::from_elem(u64::minimum()));
1597 assert_eq!(read.since(), &Antichain::from_elem(u64::minimum()));
1598
1599 write1
1601 .expect_compare_and_append(&data[..2], u64::minimum(), 3)
1602 .await;
1603 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1604
1605 assert_eq!(
1606 read.expect_snapshot_and_fetch(2).await,
1607 all_ok(&data[..2], 2)
1608 );
1609
1610 let res = write2
1612 .compare_and_append(
1613 &data[..2],
1614 Antichain::from_elem(u64::minimum()),
1615 Antichain::from_elem(3),
1616 )
1617 .await;
1618 assert_eq!(
1619 res,
1620 Ok(Err(UpperMismatch {
1621 expected: Antichain::from_elem(u64::minimum()),
1622 current: Antichain::from_elem(3)
1623 }))
1624 );
1625
1626 assert_eq!(write2.upper(), &Antichain::from_elem(3));
1628
1629 write2.expect_compare_and_append(&data[2..], 3, 4).await;
1631
1632 assert_eq!(write2.upper(), &Antichain::from_elem(4));
1633
1634 assert_eq!(read.expect_snapshot_and_fetch(3).await, all_ok(&data, 3));
1635 }
1636
1637 #[mz_persist_proc::test(tokio::test)]
1638 #[cfg_attr(miri, ignore)] async fn overlapping_append(dyncfgs: ConfigUpdates) {
1640 mz_ore::test::init_logging_default("info");
1641
1642 let data = vec![
1643 (("1".to_owned(), "one".to_owned()), 1, 1),
1644 (("2".to_owned(), "two".to_owned()), 2, 1),
1645 (("3".to_owned(), "three".to_owned()), 3, 1),
1646 (("4".to_owned(), "vier".to_owned()), 4, 1),
1647 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1648 ];
1649
1650 let id = ShardId::new();
1651 let client = new_test_client(&dyncfgs).await;
1652
1653 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1654
1655 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1656
1657 let mut listen = read.clone("").await.expect_listen(0).await;
1659
1660 write1
1662 .expect_append(&data[..2], write1.upper().clone(), vec![3])
1663 .await;
1664 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1665
1666 write2
1668 .expect_append(&data[..4], write2.upper().clone(), vec![5])
1669 .await;
1670 assert_eq!(write2.upper(), &Antichain::from_elem(5));
1671
1672 write1
1674 .expect_append(&data[2..5], write1.upper().clone(), vec![6])
1675 .await;
1676 assert_eq!(write1.upper(), &Antichain::from_elem(6));
1677
1678 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1679
1680 assert_eq!(
1681 listen.read_until(&6).await,
1682 (all_ok(&data[..], 1), Antichain::from_elem(6))
1683 );
1684 }
1685
1686 #[mz_persist_proc::test(tokio::test)]
1689 #[cfg_attr(miri, ignore)] async fn contiguous_append(dyncfgs: ConfigUpdates) {
1691 let data = vec![
1692 (("1".to_owned(), "one".to_owned()), 1, 1),
1693 (("2".to_owned(), "two".to_owned()), 2, 1),
1694 (("3".to_owned(), "three".to_owned()), 3, 1),
1695 (("4".to_owned(), "vier".to_owned()), 4, 1),
1696 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1697 ];
1698
1699 let id = ShardId::new();
1700 let client = new_test_client(&dyncfgs).await;
1701
1702 let (mut write, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1703
1704 write
1706 .expect_append(&data[..2], write.upper().clone(), vec![3])
1707 .await;
1708 assert_eq!(write.upper(), &Antichain::from_elem(3));
1709
1710 let result = write
1713 .append(
1714 &data[4..5],
1715 Antichain::from_elem(5),
1716 Antichain::from_elem(6),
1717 )
1718 .await;
1719 assert_eq!(
1720 result,
1721 Ok(Err(UpperMismatch {
1722 expected: Antichain::from_elem(5),
1723 current: Antichain::from_elem(3)
1724 }))
1725 );
1726
1727 write.expect_append(&data[2..5], vec![3], vec![6]).await;
1729 assert_eq!(write.upper(), &Antichain::from_elem(6));
1730
1731 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1732 }
1733
1734 #[mz_persist_proc::test(tokio::test)]
1737 #[cfg_attr(miri, ignore)] async fn noncontiguous_append_per_writer(dyncfgs: ConfigUpdates) {
1739 let data = vec![
1740 (("1".to_owned(), "one".to_owned()), 1, 1),
1741 (("2".to_owned(), "two".to_owned()), 2, 1),
1742 (("3".to_owned(), "three".to_owned()), 3, 1),
1743 (("4".to_owned(), "vier".to_owned()), 4, 1),
1744 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1745 ];
1746
1747 let id = ShardId::new();
1748 let client = new_test_client(&dyncfgs).await;
1749
1750 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1751
1752 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1753
1754 write1
1756 .expect_append(&data[..2], write1.upper().clone(), vec![3])
1757 .await;
1758 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1759
1760 write2.upper = Antichain::from_elem(3);
1762 write2
1763 .expect_append(&data[2..4], write2.upper().clone(), vec![5])
1764 .await;
1765 assert_eq!(write2.upper(), &Antichain::from_elem(5));
1766
1767 write1.upper = Antichain::from_elem(5);
1769 write1
1770 .expect_append(&data[4..5], write1.upper().clone(), vec![6])
1771 .await;
1772 assert_eq!(write1.upper(), &Antichain::from_elem(6));
1773
1774 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1775 }
1776
1777 #[mz_persist_proc::test(tokio::test)]
1780 #[cfg_attr(miri, ignore)] async fn contiguous_compare_and_append(dyncfgs: ConfigUpdates) {
1782 let data = vec![
1783 (("1".to_owned(), "one".to_owned()), 1, 1),
1784 (("2".to_owned(), "two".to_owned()), 2, 1),
1785 (("3".to_owned(), "three".to_owned()), 3, 1),
1786 (("4".to_owned(), "vier".to_owned()), 4, 1),
1787 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1788 ];
1789
1790 let id = ShardId::new();
1791 let client = new_test_client(&dyncfgs).await;
1792
1793 let (mut write, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1794
1795 write.expect_compare_and_append(&data[..2], 0, 3).await;
1797 assert_eq!(write.upper(), &Antichain::from_elem(3));
1798
1799 let result = write
1802 .compare_and_append(
1803 &data[4..5],
1804 Antichain::from_elem(5),
1805 Antichain::from_elem(6),
1806 )
1807 .await;
1808 assert_eq!(
1809 result,
1810 Ok(Err(UpperMismatch {
1811 expected: Antichain::from_elem(5),
1812 current: Antichain::from_elem(3)
1813 }))
1814 );
1815
1816 write.expect_compare_and_append(&data[2..5], 3, 6).await;
1819 assert_eq!(write.upper(), &Antichain::from_elem(6));
1820
1821 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1822 }
1823
1824 #[mz_persist_proc::test(tokio::test)]
1827 #[cfg_attr(miri, ignore)] async fn noncontiguous_compare_and_append_per_writer(dyncfgs: ConfigUpdates) {
1829 let data = vec![
1830 (("1".to_owned(), "one".to_owned()), 1, 1),
1831 (("2".to_owned(), "two".to_owned()), 2, 1),
1832 (("3".to_owned(), "three".to_owned()), 3, 1),
1833 (("4".to_owned(), "vier".to_owned()), 4, 1),
1834 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1835 ];
1836
1837 let id = ShardId::new();
1838 let client = new_test_client(&dyncfgs).await;
1839
1840 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1841
1842 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1843
1844 write1.expect_compare_and_append(&data[..2], 0, 3).await;
1846 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1847
1848 write2.expect_compare_and_append(&data[2..4], 3, 5).await;
1850 assert_eq!(write2.upper(), &Antichain::from_elem(5));
1851
1852 write1.expect_compare_and_append(&data[4..5], 5, 6).await;
1854 assert_eq!(write1.upper(), &Antichain::from_elem(6));
1855
1856 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1857 }
1858
1859 #[mz_ore::test]
1860 fn fmt_ids() {
1861 assert_eq!(
1862 format!("{}", LeasedReaderId([0u8; 16])),
1863 "r00000000-0000-0000-0000-000000000000"
1864 );
1865 assert_eq!(
1866 format!("{:?}", LeasedReaderId([0u8; 16])),
1867 "LeasedReaderId(00000000-0000-0000-0000-000000000000)"
1868 );
1869 }
1870
1871 #[mz_persist_proc::test(tokio::test(flavor = "multi_thread"))]
1872 #[cfg_attr(miri, ignore)] async fn concurrency(dyncfgs: ConfigUpdates) {
1874 let data = DataGenerator::small();
1875
1876 const NUM_WRITERS: usize = 2;
1877 let id = ShardId::new();
1878 let client = new_test_client(&dyncfgs).await;
1879 let mut handles = Vec::<mz_ore::task::JoinHandle<()>>::new();
1880 for idx in 0..NUM_WRITERS {
1881 let (data, client) = (data.clone(), client.clone());
1882
1883 let (batch_tx, mut batch_rx) = tokio::sync::mpsc::channel(1);
1884
1885 let client1 = client.clone();
1886 let handle = mz_ore::task::spawn(|| format!("writer-{}", idx), async move {
1887 let (write, _) = client1.expect_open::<Vec<u8>, Vec<u8>, u64, i64>(id).await;
1888 let mut current_upper = 0;
1889 for batch in data.batches() {
1890 let new_upper = match batch.get(batch.len() - 1) {
1891 Some((_, max_ts, _)) => u64::decode(max_ts) + 1,
1892 None => continue,
1893 };
1894 if PartialOrder::less_equal(&Antichain::from_elem(new_upper), write.upper()) {
1909 continue;
1910 }
1911
1912 let current_upper_chain = Antichain::from_elem(current_upper);
1913 current_upper = new_upper;
1914 let new_upper_chain = Antichain::from_elem(new_upper);
1915 let mut builder = write.builder(current_upper_chain);
1916
1917 for ((k, v), t, d) in batch.iter() {
1918 builder
1919 .add(&k.to_vec(), &v.to_vec(), &u64::decode(t), &i64::decode(d))
1920 .await
1921 .expect("invalid usage");
1922 }
1923
1924 let batch = builder
1925 .finish(new_upper_chain)
1926 .await
1927 .expect("invalid usage");
1928
1929 match batch_tx.send(batch).await {
1930 Ok(_) => (),
1931 Err(e) => panic!("send error: {}", e),
1932 }
1933 }
1934 });
1935 handles.push(handle);
1936
1937 let handle = mz_ore::task::spawn(|| format!("appender-{}", idx), async move {
1938 let (mut write, _) = client.expect_open::<Vec<u8>, Vec<u8>, u64, i64>(id).await;
1939
1940 while let Some(batch) = batch_rx.recv().await {
1941 let lower = batch.lower().clone();
1942 let upper = batch.upper().clone();
1943 write
1944 .append_batch(batch, lower, upper)
1945 .await
1946 .expect("invalid usage")
1947 .expect("unexpected upper");
1948 }
1949 });
1950 handles.push(handle);
1951 }
1952
1953 for handle in handles {
1954 let () = handle.await;
1955 }
1956
1957 let expected = data.records().collect::<Vec<_>>();
1958 let max_ts = expected.last().map(|(_, t, _)| *t).unwrap_or_default();
1959 let (_, mut read) = client.expect_open::<Vec<u8>, Vec<u8>, u64, i64>(id).await;
1960 assert_eq!(
1961 read.expect_snapshot_and_fetch(max_ts).await,
1962 all_ok(expected.iter(), max_ts)
1963 );
1964 }
1965
1966 #[mz_persist_proc::test(tokio::test)]
1970 #[cfg_attr(miri, ignore)] async fn regression_blocking_reads(dyncfgs: ConfigUpdates) {
1972 let waker = noop_waker();
1973 let mut cx = Context::from_waker(&waker);
1974
1975 let data = [
1976 (("1".to_owned(), "one".to_owned()), 1, 1),
1977 (("2".to_owned(), "two".to_owned()), 2, 1),
1978 (("3".to_owned(), "three".to_owned()), 3, 1),
1979 ];
1980
1981 let id = ShardId::new();
1982 let client = new_test_client(&dyncfgs).await;
1983 let (mut write, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1984
1985 let mut listen = read.clone("").await.expect_listen(1).await;
1987 let mut listen_next = Box::pin(listen.fetch_next());
1988 for _ in 0..100 {
1992 assert!(
1993 Pin::new(&mut listen_next).poll(&mut cx).is_pending(),
1994 "listen::next unexpectedly ready"
1995 );
1996 }
1997
1998 write
2000 .expect_compare_and_append(&data[..2], u64::minimum(), 3)
2001 .await;
2002
2003 assert_eq!(
2006 listen_next.await,
2007 vec![
2008 ListenEvent::Updates(vec![(("2".to_owned(), "two".to_owned()), 2, 1)]),
2009 ListenEvent::Progress(Antichain::from_elem(3)),
2010 ]
2011 );
2012
2013 let mut snap = Box::pin(read.expect_snapshot_and_fetch(3));
2027 for _ in 0..100 {
2028 assert!(
2029 Pin::new(&mut snap).poll(&mut cx).is_pending(),
2030 "snapshot unexpectedly ready"
2031 );
2032 }
2033
2034 write.expect_compare_and_append(&data[2..], 3, 4).await;
2036
2037 assert_eq!(snap.await, all_ok(&data[..], 3));
2039 }
2040
2041 #[mz_persist_proc::test(tokio::test)]
2042 #[cfg_attr(miri, ignore)] async fn heartbeat_task_shutdown(dyncfgs: ConfigUpdates) {
2044 let mut cache = new_test_client_cache(&dyncfgs);
2047 cache
2048 .cfg
2049 .set_config(&READER_LEASE_DURATION, Duration::from_millis(1));
2050 cache.cfg.writer_lease_duration = Duration::from_millis(1);
2051 let (_write, mut read) = cache
2052 .open(PersistLocation::new_in_mem())
2053 .await
2054 .expect("client construction failed")
2055 .expect_open::<(), (), u64, i64>(ShardId::new())
2056 .await;
2057 let read_unexpired_state = read
2058 .unexpired_state
2059 .take()
2060 .expect("handle should have unexpired state");
2061 read.expire().await;
2062 read_unexpired_state.heartbeat_task.await
2063 }
2064
2065 #[mz_persist_proc::test(tokio::test)]
2068 #[cfg_attr(miri, ignore)] async fn finalize_empty_shard(dyncfgs: ConfigUpdates) {
2070 let persist_client = new_test_client(&dyncfgs).await;
2071
2072 let shard_id = ShardId::new();
2073 pub const CRITICAL_SINCE: CriticalReaderId =
2074 CriticalReaderId([0, 0, 0, 0, 17, 17, 34, 34, 51, 51, 68, 68, 68, 68, 68, 68]);
2075
2076 let (mut write, mut read) = persist_client
2077 .expect_open::<(), (), u64, i64>(shard_id)
2078 .await;
2079
2080 let () = read.downgrade_since(&Antichain::new()).await;
2083 let () = write.advance_upper(&Antichain::new()).await;
2084
2085 let mut since_handle: SinceHandle<(), (), u64, i64> = persist_client
2086 .open_critical_since(
2087 shard_id,
2088 CRITICAL_SINCE,
2089 Opaque::encode(&0u64),
2090 Diagnostics::for_tests(),
2091 )
2092 .await
2093 .expect("invalid persist usage");
2094
2095 let epoch = since_handle.opaque().clone();
2096 let new_since = Antichain::new();
2097 let downgrade = since_handle
2098 .compare_and_downgrade_since(&epoch, (&epoch, &new_since))
2099 .await;
2100
2101 assert!(
2102 downgrade.is_ok(),
2103 "downgrade of critical handle must succeed"
2104 );
2105
2106 let finalize = persist_client
2107 .finalize_shard::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2108 .await;
2109
2110 assert_ok!(finalize, "finalization must succeed");
2111
2112 let is_finalized = persist_client
2113 .is_finalized::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2114 .await
2115 .expect("invalid persist usage");
2116 assert!(is_finalized, "shard must still be finalized");
2117 }
2118
2119 #[mz_persist_proc::test(tokio::test)]
2123 #[cfg_attr(miri, ignore)] async fn finalize_shard(dyncfgs: ConfigUpdates) {
2125 const DATA: &[(((), ()), u64, i64)] = &[(((), ()), 0, 1)];
2126 let persist_client = new_test_client(&dyncfgs).await;
2127
2128 let shard_id = ShardId::new();
2129 pub const CRITICAL_SINCE: CriticalReaderId =
2130 CriticalReaderId([0, 0, 0, 0, 17, 17, 34, 34, 51, 51, 68, 68, 68, 68, 68, 68]);
2131
2132 let (mut write, mut read) = persist_client
2133 .expect_open::<(), (), u64, i64>(shard_id)
2134 .await;
2135
2136 let () = write
2138 .compare_and_append(DATA, Antichain::from_elem(0), Antichain::from_elem(1))
2139 .await
2140 .expect("usage should be valid")
2141 .expect("upper should match");
2142
2143 let () = read.downgrade_since(&Antichain::new()).await;
2146 let () = write.advance_upper(&Antichain::new()).await;
2147
2148 let mut since_handle: SinceHandle<(), (), u64, i64> = persist_client
2149 .open_critical_since(
2150 shard_id,
2151 CRITICAL_SINCE,
2152 Opaque::encode(&0u64),
2153 Diagnostics::for_tests(),
2154 )
2155 .await
2156 .expect("invalid persist usage");
2157
2158 let epoch = since_handle.opaque().clone();
2159 let new_since = Antichain::new();
2160 let downgrade = since_handle
2161 .compare_and_downgrade_since(&epoch, (&epoch, &new_since))
2162 .await;
2163
2164 assert!(
2165 downgrade.is_ok(),
2166 "downgrade of critical handle must succeed"
2167 );
2168
2169 let finalize = persist_client
2170 .finalize_shard::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2171 .await;
2172
2173 assert_ok!(finalize, "finalization must succeed");
2174
2175 let is_finalized = persist_client
2176 .is_finalized::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2177 .await
2178 .expect("invalid persist usage");
2179 assert!(is_finalized, "shard must still be finalized");
2180 }
2181
2182 proptest! {
2183 #![proptest_config(ProptestConfig::with_cases(4096))]
2184
2185 #[mz_ore::test]
2186 #[cfg_attr(miri, ignore)] fn shard_id_protobuf_roundtrip(expect in any::<ShardId>() ) {
2188 let actual = protobuf_roundtrip::<_, String>(&expect);
2189 assert_ok!(actual);
2190 assert_eq!(actual.unwrap(), expect);
2191 }
2192 }
2193}