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 heartbeat_ts = (self.cfg.now)();
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 heartbeat_ts,
367 use_critical_since,
368 )
369 .await;
370 maintenance.start_performing(&machine, &gc);
371 let schemas = Schemas {
372 id: None,
373 key: key_schema,
374 val: val_schema,
375 };
376 let reader = ReadHandle::new(
377 self.cfg.clone(),
378 Arc::clone(&self.metrics),
379 machine,
380 gc,
381 Arc::clone(&self.blob),
382 reader_id,
383 schemas,
384 reader_state,
385 )
386 .await;
387
388 Ok(reader)
389 }
390
391 #[instrument(level = "debug", fields(shard = %shard_id))]
393 pub async fn create_batch_fetcher<K, V, T, D>(
394 &self,
395 shard_id: ShardId,
396 key_schema: Arc<K::Schema>,
397 val_schema: Arc<V::Schema>,
398 is_transient: bool,
399 diagnostics: Diagnostics,
400 ) -> Result<BatchFetcher<K, V, T, D>, InvalidUsage<T>>
401 where
402 K: Debug + Codec,
403 V: Debug + Codec,
404 T: Timestamp + Lattice + Codec64 + Sync,
405 D: Monoid + Codec64 + Send + Sync,
406 {
407 let machine = self.make_machine(shard_id, diagnostics.clone()).await?;
408 let read_schemas = Schemas {
409 id: None,
410 key: key_schema,
411 val: val_schema,
412 };
413 let schema_cache = machine.applier.schema_cache();
414 let fetcher = BatchFetcher {
415 cfg: BatchFetcherConfig::new(&self.cfg),
416 blob: Arc::clone(&self.blob),
417 metrics: Arc::clone(&self.metrics),
418 shard_metrics: Arc::clone(&machine.applier.shard_metrics),
419 shard_id,
420 read_schemas,
421 schema_cache,
422 is_transient,
423 _phantom: PhantomData,
424 };
425
426 Ok(fetcher)
427 }
428
429 pub const CONTROLLER_CRITICAL_SINCE: CriticalReaderId =
451 CriticalReaderId([0, 0, 0, 0, 17, 17, 34, 34, 51, 51, 68, 68, 68, 68, 68, 68]);
452
453 #[instrument(level = "debug", fields(shard = %shard_id))]
474 pub async fn open_critical_since<K, V, T, D>(
475 &self,
476 shard_id: ShardId,
477 reader_id: CriticalReaderId,
478 default_opaque: Opaque,
479 diagnostics: Diagnostics,
480 ) -> Result<SinceHandle<K, V, T, D>, InvalidUsage<T>>
481 where
482 K: Debug + Codec,
483 V: Debug + Codec,
484 T: Timestamp + Lattice + Codec64 + Sync,
485 D: Monoid + Codec64 + Send + Sync,
486 {
487 let machine = self.make_machine(shard_id, diagnostics.clone()).await?;
488 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
489
490 let (state, maintenance) = machine
491 .register_critical_reader(&reader_id, default_opaque, &diagnostics.handle_purpose)
492 .await;
493 maintenance.start_performing(&machine, &gc);
494 let handle = SinceHandle::new(machine, gc, reader_id, state.since, state.opaque);
495
496 Ok(handle)
497 }
498
499 #[instrument(level = "debug", fields(shard = %shard_id))]
504 pub async fn open_writer<K, V, T, D>(
505 &self,
506 shard_id: ShardId,
507 key_schema: Arc<K::Schema>,
508 val_schema: Arc<V::Schema>,
509 diagnostics: Diagnostics,
510 ) -> Result<WriteHandle<K, V, T, D>, InvalidUsage<T>>
511 where
512 K: Debug + Codec,
513 V: Debug + Codec,
514 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
515 D: Monoid + Ord + Codec64 + Send + Sync,
516 {
517 let machine = self.make_machine(shard_id, diagnostics.clone()).await?;
518 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
519
520 let schema_id = machine.find_schema(&*key_schema, &*val_schema);
525
526 let writer_id = WriterId::new();
527 let schemas = Schemas {
528 id: schema_id,
529 key: key_schema,
530 val: val_schema,
531 };
532 let writer = WriteHandle::new(
533 self.cfg.clone(),
534 Arc::clone(&self.metrics),
535 machine,
536 gc,
537 Arc::clone(&self.blob),
538 writer_id,
539 &diagnostics.handle_purpose,
540 schemas,
541 );
542 Ok(writer)
543 }
544
545 #[instrument(level = "debug", fields(shard = %shard_id))]
555 pub async fn batch_builder<K, V, T, D>(
556 &self,
557 shard_id: ShardId,
558 write_schemas: Schemas<K, V>,
559 lower: Antichain<T>,
560 max_runs: Option<usize>,
561 ) -> BatchBuilder<K, V, T, D>
562 where
563 K: Debug + Codec,
564 V: Debug + Codec,
565 T: Timestamp + Lattice + Codec64 + TotalOrder + Sync,
566 D: Monoid + Ord + Codec64 + Send + Sync,
567 {
568 let mut compact_cfg = CompactConfig::new(&self.cfg, shard_id);
569 compact_cfg.batch.max_runs = max_runs;
570 WriteHandle::builder_inner(
571 &self.cfg,
572 compact_cfg,
573 Arc::clone(&self.metrics),
574 self.metrics.shards.shard(&shard_id, "peek_stash"),
575 &self.metrics.user,
576 Arc::clone(&self.isolated_runtime),
577 Arc::clone(&self.blob),
578 shard_id,
579 write_schemas,
580 lower,
581 )
582 }
583
584 pub fn batch_from_transmittable_batch<K, V, T, D>(
593 &self,
594 shard_id: &ShardId,
595 batch: ProtoBatch,
596 ) -> Batch<K, V, T, D>
597 where
598 K: Debug + Codec,
599 V: Debug + Codec,
600 T: Timestamp + Lattice + Codec64 + Sync,
601 D: Monoid + Ord + Codec64 + Send + Sync,
602 {
603 let batch_shard_id: ShardId = batch
604 .shard_id
605 .into_rust()
606 .expect("valid transmittable batch");
607 assert_eq!(&batch_shard_id, shard_id);
608
609 let shard_metrics = self.metrics.shards.shard(shard_id, "peek_stash");
610
611 let ret = Batch {
612 batch_delete_enabled: BATCH_DELETE_ENABLED.get(&self.cfg),
613 metrics: Arc::clone(&self.metrics),
614 shard_metrics,
615 version: Version::parse(&batch.version).expect("valid transmittable batch"),
616 schemas: (batch.key_schema, batch.val_schema),
617 batch: batch
618 .batch
619 .into_rust_if_some("ProtoBatch::batch")
620 .expect("valid transmittable batch"),
621 blob: Arc::clone(&self.blob),
622 _phantom: std::marker::PhantomData,
623 };
624
625 assert_eq!(&ret.shard_id(), shard_id);
626 ret
627 }
628
629 #[allow(clippy::unused_async)]
644 pub async fn read_batches_consolidated<K, V, T, D>(
645 &mut self,
646 shard_id: ShardId,
647 as_of: Antichain<T>,
648 read_schemas: Schemas<K, V>,
649 batches: Vec<Batch<K, V, T, D>>,
650 should_fetch_part: impl for<'a> Fn(Option<&'a LazyPartStats>) -> bool,
651 memory_budget_bytes: usize,
652 ) -> Result<Cursor<K, V, T, D, Vec<Batch<K, V, T, D>>>, Since<T>>
653 where
654 K: Debug + Codec + Ord,
655 V: Debug + Codec + Ord,
656 T: Timestamp + Lattice + Codec64 + TotalOrder + Sync,
657 D: Monoid + Ord + Codec64 + Send + Sync,
658 {
659 let shard_metrics = self.metrics.shards.shard(&shard_id, "peek_stash");
660
661 let hollow_batches = batches.iter().map(|b| b.batch.clone()).collect_vec();
662
663 ReadHandle::read_batches_consolidated(
664 &self.cfg,
665 Arc::clone(&self.metrics),
666 shard_metrics,
667 self.metrics.read.snapshot.clone(),
668 Arc::clone(&self.blob),
669 shard_id,
670 as_of,
671 read_schemas,
672 &hollow_batches,
673 batches,
674 should_fetch_part,
675 memory_budget_bytes,
676 )
677 }
678
679 pub async fn get_schema<K, V, T, D>(
681 &self,
682 shard_id: ShardId,
683 schema_id: SchemaId,
684 diagnostics: Diagnostics,
685 ) -> Result<Option<(K::Schema, V::Schema)>, InvalidUsage<T>>
686 where
687 K: Debug + Codec,
688 V: Debug + Codec,
689 T: Timestamp + Lattice + Codec64 + Sync,
690 D: Monoid + Codec64 + Send + Sync,
691 {
692 let machine = self
693 .make_machine::<K, V, T, D>(shard_id, diagnostics)
694 .await?;
695 Ok(machine.get_schema(schema_id))
696 }
697
698 pub async fn latest_schema<K, V, T, D>(
700 &self,
701 shard_id: ShardId,
702 diagnostics: Diagnostics,
703 ) -> Result<Option<(SchemaId, K::Schema, V::Schema)>, InvalidUsage<T>>
704 where
705 K: Debug + Codec,
706 V: Debug + Codec,
707 T: Timestamp + Lattice + Codec64 + Sync,
708 D: Monoid + Codec64 + Send + Sync,
709 {
710 let machine = self
711 .make_machine::<K, V, T, D>(shard_id, diagnostics)
712 .await?;
713 Ok(machine.latest_schema())
714 }
715
716 pub async fn register_schema<K, V, T, D>(
728 &self,
729 shard_id: ShardId,
730 key_schema: &K::Schema,
731 val_schema: &V::Schema,
732 diagnostics: Diagnostics,
733 ) -> Result<Option<SchemaId>, InvalidUsage<T>>
734 where
735 K: Debug + Codec,
736 V: Debug + Codec,
737 T: Timestamp + Lattice + Codec64 + Sync,
738 D: Monoid + Codec64 + Send + Sync,
739 {
740 let machine = self
741 .make_machine::<K, V, T, D>(shard_id, diagnostics)
742 .await?;
743 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
744
745 let (schema_id, maintenance) = machine.register_schema(key_schema, val_schema).await;
746 maintenance.start_performing(&machine, &gc);
747
748 Ok(schema_id)
749 }
750
751 pub async fn compare_and_evolve_schema<K, V, T, D>(
762 &self,
763 shard_id: ShardId,
764 expected: SchemaId,
765 key_schema: &K::Schema,
766 val_schema: &V::Schema,
767 diagnostics: Diagnostics,
768 ) -> Result<CaESchema<K, V>, InvalidUsage<T>>
769 where
770 K: Debug + Codec,
771 V: Debug + Codec,
772 T: Timestamp + Lattice + Codec64 + Sync,
773 D: Monoid + Codec64 + Send + Sync,
774 {
775 let machine = self
776 .make_machine::<K, V, T, D>(shard_id, diagnostics)
777 .await?;
778 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
779 let (res, maintenance) = machine
780 .compare_and_evolve_schema(expected, key_schema, val_schema)
781 .await;
782 maintenance.start_performing(&machine, &gc);
783 Ok(res)
784 }
785
786 pub async fn is_finalized<K, V, T, D>(
790 &self,
791 shard_id: ShardId,
792 diagnostics: Diagnostics,
793 ) -> Result<bool, 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 Ok(machine.is_finalized())
804 }
805
806 #[instrument(level = "debug", fields(shard = %shard_id))]
817 pub async fn finalize_shard<K, V, T, D>(
818 &self,
819 shard_id: ShardId,
820 diagnostics: Diagnostics,
821 ) -> Result<(), InvalidUsage<T>>
822 where
823 K: Debug + Codec,
824 V: Debug + Codec,
825 T: Timestamp + Lattice + Codec64 + Sync,
826 D: Monoid + Codec64 + Send + Sync,
827 {
828 let machine = self
829 .make_machine::<K, V, T, D>(shard_id, diagnostics)
830 .await?;
831
832 let maintenance = machine.become_tombstone().await?;
833 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
834
835 let () = maintenance.perform(&machine, &gc).await;
836
837 Ok(())
838 }
839
840 pub async fn upgrade_version<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 match machine.upgrade_version().await {
858 Ok(maintenance) => {
859 let gc = GarbageCollector::new(machine.clone(), Arc::clone(&self.isolated_runtime));
860 let () = maintenance.perform(&machine, &gc).await;
861 Ok(())
862 }
863 Err(version) => Err(InvalidUsage::IncompatibleVersion { version }),
864 }
865 }
866
867 pub async fn inspect_shard<T: Timestamp + Lattice + Codec64>(
873 &self,
874 shard_id: &ShardId,
875 ) -> Result<impl serde::Serialize, anyhow::Error> {
876 let state_versions = StateVersions::new(
877 self.cfg.clone(),
878 Arc::clone(&self.consensus),
879 Arc::clone(&self.blob),
880 Arc::clone(&self.metrics),
881 );
882 let versions = state_versions.fetch_all_live_diffs(shard_id).await;
886 if versions.is_empty() {
887 return Err(anyhow::anyhow!("{} does not exist", shard_id));
888 }
889 let state = state_versions
890 .fetch_current_state::<T>(shard_id, versions)
891 .await;
892 let state = state.check_ts_codec(shard_id)?;
893 Ok(state)
894 }
895
896 #[cfg(test)]
898 #[track_caller]
899 pub async fn expect_open<K, V, T, D>(
900 &self,
901 shard_id: ShardId,
902 ) -> (WriteHandle<K, V, T, D>, ReadHandle<K, V, T, D>)
903 where
904 K: Debug + Codec,
905 V: Debug + Codec,
906 T: Timestamp + TotalOrder + Lattice + Codec64 + Sync,
907 D: Monoid + Ord + Codec64 + Send + Sync,
908 K::Schema: Default,
909 V::Schema: Default,
910 {
911 self.open(
912 shard_id,
913 Arc::new(K::Schema::default()),
914 Arc::new(V::Schema::default()),
915 Diagnostics::for_tests(),
916 true,
917 )
918 .await
919 .expect("codec mismatch")
920 }
921
922 pub fn metrics(&self) -> &Arc<Metrics> {
926 &self.metrics
927 }
928}
929
930#[cfg(test)]
931mod tests {
932 use std::future::Future;
933 use std::pin::Pin;
934 use std::task::Context;
935 use std::time::Duration;
936
937 use differential_dataflow::consolidation::consolidate_updates;
938 use differential_dataflow::lattice::Lattice;
939 use futures_task::noop_waker;
940 use mz_dyncfg::ConfigUpdates;
941 use mz_ore::assert_ok;
942 use mz_persist::indexed::encoding::BlobTraceBatchPart;
943 use mz_persist::workload::DataGenerator;
944 use mz_persist_types::codec_impls::{StringSchema, VecU8Schema};
945 use mz_proto::protobuf_roundtrip;
946 use proptest::prelude::*;
947 use timely::order::PartialOrder;
948 use timely::progress::Antichain;
949
950 use crate::batch::BLOB_TARGET_SIZE;
951 use crate::cache::PersistClientCache;
952 use crate::cfg::BATCH_BUILDER_MAX_OUTSTANDING_PARTS;
953 use crate::critical::Opaque;
954 use crate::error::{CodecConcreteType, CodecMismatch, UpperMismatch};
955 use crate::internal::paths::BlobKey;
956 use crate::read::ListenEvent;
957
958 use super::*;
959
960 pub fn new_test_client_cache(dyncfgs: &ConfigUpdates) -> PersistClientCache {
961 let mut cache = PersistClientCache::new_no_metrics();
964 cache.cfg.set_config(&BLOB_TARGET_SIZE, 10);
965 cache
966 .cfg
967 .set_config(&BATCH_BUILDER_MAX_OUTSTANDING_PARTS, 1);
968 dyncfgs.apply(cache.cfg());
969
970 cache.cfg.compaction_enabled = true;
972 cache
973 }
974
975 pub async fn new_test_client(dyncfgs: &ConfigUpdates) -> PersistClient {
976 let cache = new_test_client_cache(dyncfgs);
977 cache
978 .open(PersistLocation::new_in_mem())
979 .await
980 .expect("client construction failed")
981 }
982
983 pub fn all_ok<'a, K, V, T, D, I>(iter: I, as_of: T) -> Vec<((K, V), T, D)>
984 where
985 K: Ord + Clone + 'a,
986 V: Ord + Clone + 'a,
987 T: Timestamp + Lattice + Clone + 'a,
988 D: Monoid + Clone + 'a,
989 I: IntoIterator<Item = &'a ((K, V), T, D)>,
990 {
991 let as_of = Antichain::from_elem(as_of);
992 let mut ret = iter
993 .into_iter()
994 .map(|((k, v), t, d)| {
995 let mut t = t.clone();
996 t.advance_by(as_of.borrow());
997 ((k.clone(), v.clone()), t, d.clone())
998 })
999 .collect();
1000 consolidate_updates(&mut ret);
1001 ret
1002 }
1003
1004 pub async fn expect_fetch_part<K, V, T, D>(
1005 blob: &dyn Blob,
1006 key: &BlobKey,
1007 metrics: &Metrics,
1008 read_schemas: &Schemas<K, V>,
1009 ) -> (BlobTraceBatchPart<T>, Vec<((K, V), T, D)>)
1010 where
1011 K: Codec + Clone,
1012 V: Codec + Clone,
1013 T: Timestamp + Codec64,
1014 D: Codec64,
1015 {
1016 let value = blob
1017 .get(key)
1018 .await
1019 .expect("failed to fetch part")
1020 .expect("missing part");
1021 let mut part =
1022 BlobTraceBatchPart::decode(&value, &metrics.columnar).expect("failed to decode part");
1023 let structured = part
1024 .updates
1025 .into_part::<K, V>(&*read_schemas.key, &*read_schemas.val);
1026 let updates = structured
1027 .decode_iter::<K, V, T, D>(&*read_schemas.key, &*read_schemas.val)
1028 .expect("structured data")
1029 .collect();
1030 (part, updates)
1031 }
1032
1033 #[mz_persist_proc::test(tokio::test)]
1034 #[cfg_attr(miri, ignore)] async fn sanity_check(dyncfgs: ConfigUpdates) {
1036 let data = [
1037 (("1".to_owned(), "one".to_owned()), 1, 1),
1038 (("2".to_owned(), "two".to_owned()), 2, 1),
1039 (("3".to_owned(), "three".to_owned()), 3, 1),
1040 ];
1041
1042 let (mut write, mut read) = new_test_client(&dyncfgs)
1043 .await
1044 .expect_open::<String, String, u64, i64>(ShardId::new())
1045 .await;
1046 assert_eq!(write.upper(), &Antichain::from_elem(u64::minimum()));
1047 assert_eq!(read.since(), &Antichain::from_elem(u64::minimum()));
1048
1049 write
1051 .expect_append(&data[..2], write.upper().clone(), vec![3])
1052 .await;
1053 assert_eq!(write.upper(), &Antichain::from_elem(3));
1054
1055 assert_eq!(
1057 read.expect_snapshot_and_fetch(1).await,
1058 all_ok(&data[..1], 1)
1059 );
1060
1061 let mut listen = read.clone("").await.expect_listen(1).await;
1062
1063 write
1065 .expect_append(&data[2..], write.upper().clone(), vec![4])
1066 .await;
1067 assert_eq!(write.upper(), &Antichain::from_elem(4));
1068
1069 assert_eq!(
1071 listen.read_until(&4).await,
1072 (all_ok(&data[1..], 1), Antichain::from_elem(4))
1073 );
1074
1075 read.downgrade_since(&Antichain::from_elem(2)).await;
1077 assert_eq!(read.since(), &Antichain::from_elem(2));
1078 }
1079
1080 #[mz_persist_proc::test(tokio::test)]
1082 #[cfg_attr(miri, ignore)] async fn open_reader_writer(dyncfgs: ConfigUpdates) {
1084 let data = vec![
1085 (("1".to_owned(), "one".to_owned()), 1, 1),
1086 (("2".to_owned(), "two".to_owned()), 2, 1),
1087 (("3".to_owned(), "three".to_owned()), 3, 1),
1088 ];
1089
1090 let shard_id = ShardId::new();
1091 let client = new_test_client(&dyncfgs).await;
1092 let mut write1 = client
1093 .open_writer::<String, String, u64, i64>(
1094 shard_id,
1095 Arc::new(StringSchema),
1096 Arc::new(StringSchema),
1097 Diagnostics::for_tests(),
1098 )
1099 .await
1100 .expect("codec mismatch");
1101 let mut read1 = client
1102 .open_leased_reader::<String, String, u64, i64>(
1103 shard_id,
1104 Arc::new(StringSchema),
1105 Arc::new(StringSchema),
1106 Diagnostics::for_tests(),
1107 true,
1108 )
1109 .await
1110 .expect("codec mismatch");
1111 let mut read2 = client
1112 .open_leased_reader::<String, String, u64, i64>(
1113 shard_id,
1114 Arc::new(StringSchema),
1115 Arc::new(StringSchema),
1116 Diagnostics::for_tests(),
1117 true,
1118 )
1119 .await
1120 .expect("codec mismatch");
1121 let mut write2 = client
1122 .open_writer::<String, String, u64, i64>(
1123 shard_id,
1124 Arc::new(StringSchema),
1125 Arc::new(StringSchema),
1126 Diagnostics::for_tests(),
1127 )
1128 .await
1129 .expect("codec mismatch");
1130
1131 write2.expect_compare_and_append(&data[..1], 0, 2).await;
1132 assert_eq!(
1133 read2.expect_snapshot_and_fetch(1).await,
1134 all_ok(&data[..1], 1)
1135 );
1136 write1.expect_compare_and_append(&data[1..], 2, 4).await;
1137 assert_eq!(read1.expect_snapshot_and_fetch(3).await, all_ok(&data, 3));
1138 }
1139
1140 #[mz_persist_proc::test(tokio::test)]
1141 #[cfg_attr(miri, ignore)] async fn invalid_usage(dyncfgs: ConfigUpdates) {
1143 let data = vec![
1144 (("1".to_owned(), "one".to_owned()), 1, 1),
1145 (("2".to_owned(), "two".to_owned()), 2, 1),
1146 (("3".to_owned(), "three".to_owned()), 3, 1),
1147 ];
1148
1149 let shard_id0 = "s00000000-0000-0000-0000-000000000000"
1150 .parse::<ShardId>()
1151 .expect("invalid shard id");
1152 let mut client = new_test_client(&dyncfgs).await;
1153
1154 let (mut write0, mut read0) = client
1155 .expect_open::<String, String, u64, i64>(shard_id0)
1156 .await;
1157
1158 write0.expect_compare_and_append(&data, 0, 4).await;
1159
1160 {
1162 fn codecs(
1163 k: &str,
1164 v: &str,
1165 t: &str,
1166 d: &str,
1167 ) -> (String, String, String, String, Option<CodecConcreteType>) {
1168 (k.to_owned(), v.to_owned(), t.to_owned(), d.to_owned(), None)
1169 }
1170
1171 client.shared_states = Arc::new(StateCache::new_no_metrics());
1172 assert_eq!(
1173 client
1174 .open::<Vec<u8>, String, u64, i64>(
1175 shard_id0,
1176 Arc::new(VecU8Schema),
1177 Arc::new(StringSchema),
1178 Diagnostics::for_tests(),
1179 true,
1180 )
1181 .await
1182 .unwrap_err(),
1183 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1184 requested: codecs("Vec<u8>", "String", "u64", "i64"),
1185 actual: codecs("String", "String", "u64", "i64"),
1186 }))
1187 );
1188 assert_eq!(
1189 client
1190 .open::<String, Vec<u8>, u64, i64>(
1191 shard_id0,
1192 Arc::new(StringSchema),
1193 Arc::new(VecU8Schema),
1194 Diagnostics::for_tests(),
1195 true,
1196 )
1197 .await
1198 .unwrap_err(),
1199 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1200 requested: codecs("String", "Vec<u8>", "u64", "i64"),
1201 actual: codecs("String", "String", "u64", "i64"),
1202 }))
1203 );
1204 assert_eq!(
1205 client
1206 .open::<String, String, i64, i64>(
1207 shard_id0,
1208 Arc::new(StringSchema),
1209 Arc::new(StringSchema),
1210 Diagnostics::for_tests(),
1211 true,
1212 )
1213 .await
1214 .unwrap_err(),
1215 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1216 requested: codecs("String", "String", "i64", "i64"),
1217 actual: codecs("String", "String", "u64", "i64"),
1218 }))
1219 );
1220 assert_eq!(
1221 client
1222 .open::<String, String, u64, u64>(
1223 shard_id0,
1224 Arc::new(StringSchema),
1225 Arc::new(StringSchema),
1226 Diagnostics::for_tests(),
1227 true,
1228 )
1229 .await
1230 .unwrap_err(),
1231 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1232 requested: codecs("String", "String", "u64", "u64"),
1233 actual: codecs("String", "String", "u64", "i64"),
1234 }))
1235 );
1236
1237 assert_eq!(
1241 client
1242 .open_leased_reader::<Vec<u8>, String, u64, i64>(
1243 shard_id0,
1244 Arc::new(VecU8Schema),
1245 Arc::new(StringSchema),
1246 Diagnostics::for_tests(),
1247 true,
1248 )
1249 .await
1250 .unwrap_err(),
1251 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1252 requested: codecs("Vec<u8>", "String", "u64", "i64"),
1253 actual: codecs("String", "String", "u64", "i64"),
1254 }))
1255 );
1256 assert_eq!(
1257 client
1258 .open_writer::<Vec<u8>, String, u64, i64>(
1259 shard_id0,
1260 Arc::new(VecU8Schema),
1261 Arc::new(StringSchema),
1262 Diagnostics::for_tests(),
1263 )
1264 .await
1265 .unwrap_err(),
1266 InvalidUsage::CodecMismatch(Box::new(CodecMismatch {
1267 requested: codecs("Vec<u8>", "String", "u64", "i64"),
1268 actual: codecs("String", "String", "u64", "i64"),
1269 }))
1270 );
1271 }
1272
1273 {
1275 let snap = read0
1276 .snapshot(Antichain::from_elem(3))
1277 .await
1278 .expect("cannot serve requested as_of");
1279
1280 let shard_id1 = "s11111111-1111-1111-1111-111111111111"
1281 .parse::<ShardId>()
1282 .expect("invalid shard id");
1283 let mut fetcher1 = client
1284 .create_batch_fetcher::<String, String, u64, i64>(
1285 shard_id1,
1286 Default::default(),
1287 Default::default(),
1288 false,
1289 Diagnostics::for_tests(),
1290 )
1291 .await
1292 .unwrap();
1293 for part in snap {
1294 let (part, _lease) = part.into_exchangeable_part();
1295 let res = fetcher1.fetch_leased_part(part).await;
1296 assert_eq!(
1297 res.unwrap_err(),
1298 InvalidUsage::BatchNotFromThisShard {
1299 batch_shard: shard_id0,
1300 handle_shard: shard_id1,
1301 }
1302 );
1303 }
1304 }
1305
1306 {
1308 let ts3 = &data[2];
1309 assert_eq!(ts3.1, 3);
1310 let ts3 = vec![ts3.clone()];
1311
1312 assert_eq!(
1315 write0
1316 .append(&ts3, Antichain::from_elem(4), Antichain::from_elem(5))
1317 .await
1318 .unwrap_err(),
1319 InvalidUsage::UpdateNotBeyondLower {
1320 ts: 3,
1321 lower: Antichain::from_elem(4),
1322 },
1323 );
1324 assert_eq!(
1325 write0
1326 .append(&ts3, Antichain::from_elem(2), Antichain::from_elem(3))
1327 .await
1328 .unwrap_err(),
1329 InvalidUsage::UpdateBeyondUpper {
1330 ts: 3,
1331 expected_upper: Antichain::from_elem(3),
1332 },
1333 );
1334 assert_eq!(
1336 write0
1337 .append(&data[..0], Antichain::from_elem(3), Antichain::from_elem(2))
1338 .await
1339 .unwrap_err(),
1340 InvalidUsage::InvalidBounds {
1341 lower: Antichain::from_elem(3),
1342 upper: Antichain::from_elem(2),
1343 },
1344 );
1345
1346 assert_eq!(
1348 write0
1349 .builder(Antichain::from_elem(3))
1350 .finish(Antichain::from_elem(2))
1351 .await
1352 .unwrap_err(),
1353 InvalidUsage::InvalidBounds {
1354 lower: Antichain::from_elem(3),
1355 upper: Antichain::from_elem(2)
1356 },
1357 );
1358 let batch = write0
1359 .batch(&ts3, Antichain::from_elem(3), Antichain::from_elem(4))
1360 .await
1361 .expect("invalid usage");
1362 assert_eq!(
1363 write0
1364 .append_batch(batch, Antichain::from_elem(4), Antichain::from_elem(5))
1365 .await
1366 .unwrap_err(),
1367 InvalidUsage::InvalidBatchBounds {
1368 batch_lower: Antichain::from_elem(3),
1369 batch_upper: Antichain::from_elem(4),
1370 append_lower: Antichain::from_elem(4),
1371 append_upper: Antichain::from_elem(5),
1372 },
1373 );
1374 let batch = write0
1375 .batch(&ts3, Antichain::from_elem(3), Antichain::from_elem(4))
1376 .await
1377 .expect("invalid usage");
1378 assert_eq!(
1379 write0
1380 .append_batch(batch, Antichain::from_elem(2), Antichain::from_elem(3))
1381 .await
1382 .unwrap_err(),
1383 InvalidUsage::InvalidBatchBounds {
1384 batch_lower: Antichain::from_elem(3),
1385 batch_upper: Antichain::from_elem(4),
1386 append_lower: Antichain::from_elem(2),
1387 append_upper: Antichain::from_elem(3),
1388 },
1389 );
1390 let batch = write0
1391 .batch(&ts3, Antichain::from_elem(3), Antichain::from_elem(4))
1392 .await
1393 .expect("invalid usage");
1394 assert!(matches!(
1397 write0
1398 .append_batch(batch, Antichain::from_elem(3), Antichain::from_elem(3))
1399 .await
1400 .unwrap_err(),
1401 InvalidUsage::InvalidEmptyTimeInterval { .. }
1402 ));
1403 }
1404 }
1405
1406 #[mz_persist_proc::test(tokio::test)]
1407 #[cfg_attr(miri, ignore)] async fn multiple_shards(dyncfgs: ConfigUpdates) {
1409 let data1 = [
1410 (("1".to_owned(), "one".to_owned()), 1, 1),
1411 (("2".to_owned(), "two".to_owned()), 2, 1),
1412 ];
1413
1414 let data2 = [(("1".to_owned(), ()), 1, 1), (("2".to_owned(), ()), 2, 1)];
1415
1416 let client = new_test_client(&dyncfgs).await;
1417
1418 let (mut write1, mut read1) = client
1419 .expect_open::<String, String, u64, i64>(ShardId::new())
1420 .await;
1421
1422 let (mut write2, mut read2) = client
1425 .expect_open::<String, (), u64, i64>(ShardId::new())
1426 .await;
1427
1428 write1
1429 .expect_compare_and_append(&data1[..], u64::minimum(), 3)
1430 .await;
1431
1432 write2
1433 .expect_compare_and_append(&data2[..], u64::minimum(), 3)
1434 .await;
1435
1436 assert_eq!(
1437 read1.expect_snapshot_and_fetch(2).await,
1438 all_ok(&data1[..], 2)
1439 );
1440
1441 assert_eq!(
1442 read2.expect_snapshot_and_fetch(2).await,
1443 all_ok(&data2[..], 2)
1444 );
1445 }
1446
1447 #[mz_persist_proc::test(tokio::test)]
1448 #[cfg_attr(miri, ignore)] async fn fetch_upper(dyncfgs: ConfigUpdates) {
1450 let data = [
1451 (("1".to_owned(), "one".to_owned()), 1, 1),
1452 (("2".to_owned(), "two".to_owned()), 2, 1),
1453 ];
1454
1455 let client = new_test_client(&dyncfgs).await;
1456
1457 let shard_id = ShardId::new();
1458
1459 let (mut write1, _read1) = client
1460 .expect_open::<String, String, u64, i64>(shard_id)
1461 .await;
1462
1463 let (mut write2, _read2) = client
1464 .expect_open::<String, String, u64, i64>(shard_id)
1465 .await;
1466
1467 write1
1468 .expect_append(&data[..], write1.upper().clone(), vec![3])
1469 .await;
1470
1471 assert_eq!(write2.fetch_recent_upper().await, &Antichain::from_elem(3));
1473
1474 assert_eq!(write2.upper(), &Antichain::from_elem(3));
1477 }
1478
1479 #[mz_persist_proc::test(tokio::test)]
1480 #[cfg_attr(miri, ignore)] async fn append_with_invalid_upper(dyncfgs: ConfigUpdates) {
1482 let data = [
1483 (("1".to_owned(), "one".to_owned()), 1, 1),
1484 (("2".to_owned(), "two".to_owned()), 2, 1),
1485 ];
1486
1487 let client = new_test_client(&dyncfgs).await;
1488
1489 let shard_id = ShardId::new();
1490
1491 let (mut write, _read) = client
1492 .expect_open::<String, String, u64, i64>(shard_id)
1493 .await;
1494
1495 write
1496 .expect_append(&data[..], write.upper().clone(), vec![3])
1497 .await;
1498
1499 let data = [
1500 (("5".to_owned(), "fünf".to_owned()), 5, 1),
1501 (("6".to_owned(), "sechs".to_owned()), 6, 1),
1502 ];
1503 let res = write
1504 .append(
1505 data.iter(),
1506 Antichain::from_elem(5),
1507 Antichain::from_elem(7),
1508 )
1509 .await;
1510 assert_eq!(
1511 res,
1512 Ok(Err(UpperMismatch {
1513 expected: Antichain::from_elem(5),
1514 current: Antichain::from_elem(3)
1515 }))
1516 );
1517
1518 assert_eq!(write.upper(), &Antichain::from_elem(3));
1520 }
1521
1522 #[allow(unused)]
1525 async fn sync_send(dyncfgs: ConfigUpdates) {
1526 mz_ore::test::init_logging();
1527
1528 fn is_send_sync<T: Send + Sync>(_x: T) -> bool {
1529 true
1530 }
1531
1532 let client = new_test_client(&dyncfgs).await;
1533
1534 let (write, read) = client
1535 .expect_open::<String, String, u64, i64>(ShardId::new())
1536 .await;
1537
1538 assert!(is_send_sync(client));
1539 assert!(is_send_sync(write));
1540 assert!(is_send_sync(read));
1541 }
1542
1543 #[mz_persist_proc::test(tokio::test)]
1544 #[cfg_attr(miri, ignore)] async fn compare_and_append(dyncfgs: ConfigUpdates) {
1546 let data = vec![
1547 (("1".to_owned(), "one".to_owned()), 1, 1),
1548 (("2".to_owned(), "two".to_owned()), 2, 1),
1549 (("3".to_owned(), "three".to_owned()), 3, 1),
1550 ];
1551
1552 let id = ShardId::new();
1553 let client = new_test_client(&dyncfgs).await;
1554 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1555
1556 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1557
1558 assert_eq!(write1.upper(), &Antichain::from_elem(u64::minimum()));
1559 assert_eq!(write2.upper(), &Antichain::from_elem(u64::minimum()));
1560 assert_eq!(read.since(), &Antichain::from_elem(u64::minimum()));
1561
1562 write1
1564 .expect_compare_and_append(&data[..2], u64::minimum(), 3)
1565 .await;
1566 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1567
1568 assert_eq!(
1569 read.expect_snapshot_and_fetch(2).await,
1570 all_ok(&data[..2], 2)
1571 );
1572
1573 let res = write2
1575 .compare_and_append(
1576 &data[..2],
1577 Antichain::from_elem(u64::minimum()),
1578 Antichain::from_elem(3),
1579 )
1580 .await;
1581 assert_eq!(
1582 res,
1583 Ok(Err(UpperMismatch {
1584 expected: Antichain::from_elem(u64::minimum()),
1585 current: Antichain::from_elem(3)
1586 }))
1587 );
1588
1589 assert_eq!(write2.upper(), &Antichain::from_elem(3));
1591
1592 write2.expect_compare_and_append(&data[2..], 3, 4).await;
1594
1595 assert_eq!(write2.upper(), &Antichain::from_elem(4));
1596
1597 assert_eq!(read.expect_snapshot_and_fetch(3).await, all_ok(&data, 3));
1598 }
1599
1600 #[mz_persist_proc::test(tokio::test)]
1601 #[cfg_attr(miri, ignore)] async fn overlapping_append(dyncfgs: ConfigUpdates) {
1603 mz_ore::test::init_logging_default("info");
1604
1605 let data = vec![
1606 (("1".to_owned(), "one".to_owned()), 1, 1),
1607 (("2".to_owned(), "two".to_owned()), 2, 1),
1608 (("3".to_owned(), "three".to_owned()), 3, 1),
1609 (("4".to_owned(), "vier".to_owned()), 4, 1),
1610 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1611 ];
1612
1613 let id = ShardId::new();
1614 let client = new_test_client(&dyncfgs).await;
1615
1616 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1617
1618 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1619
1620 let mut listen = read.clone("").await.expect_listen(0).await;
1622
1623 write1
1625 .expect_append(&data[..2], write1.upper().clone(), vec![3])
1626 .await;
1627 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1628
1629 write2
1631 .expect_append(&data[..4], write2.upper().clone(), vec![5])
1632 .await;
1633 assert_eq!(write2.upper(), &Antichain::from_elem(5));
1634
1635 write1
1637 .expect_append(&data[2..5], write1.upper().clone(), vec![6])
1638 .await;
1639 assert_eq!(write1.upper(), &Antichain::from_elem(6));
1640
1641 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1642
1643 assert_eq!(
1644 listen.read_until(&6).await,
1645 (all_ok(&data[..], 1), Antichain::from_elem(6))
1646 );
1647 }
1648
1649 #[mz_persist_proc::test(tokio::test)]
1652 #[cfg_attr(miri, ignore)] async fn contiguous_append(dyncfgs: ConfigUpdates) {
1654 let data = vec![
1655 (("1".to_owned(), "one".to_owned()), 1, 1),
1656 (("2".to_owned(), "two".to_owned()), 2, 1),
1657 (("3".to_owned(), "three".to_owned()), 3, 1),
1658 (("4".to_owned(), "vier".to_owned()), 4, 1),
1659 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1660 ];
1661
1662 let id = ShardId::new();
1663 let client = new_test_client(&dyncfgs).await;
1664
1665 let (mut write, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1666
1667 write
1669 .expect_append(&data[..2], write.upper().clone(), vec![3])
1670 .await;
1671 assert_eq!(write.upper(), &Antichain::from_elem(3));
1672
1673 let result = write
1676 .append(
1677 &data[4..5],
1678 Antichain::from_elem(5),
1679 Antichain::from_elem(6),
1680 )
1681 .await;
1682 assert_eq!(
1683 result,
1684 Ok(Err(UpperMismatch {
1685 expected: Antichain::from_elem(5),
1686 current: Antichain::from_elem(3)
1687 }))
1688 );
1689
1690 write.expect_append(&data[2..5], vec![3], vec![6]).await;
1692 assert_eq!(write.upper(), &Antichain::from_elem(6));
1693
1694 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1695 }
1696
1697 #[mz_persist_proc::test(tokio::test)]
1700 #[cfg_attr(miri, ignore)] async fn noncontiguous_append_per_writer(dyncfgs: ConfigUpdates) {
1702 let data = vec![
1703 (("1".to_owned(), "one".to_owned()), 1, 1),
1704 (("2".to_owned(), "two".to_owned()), 2, 1),
1705 (("3".to_owned(), "three".to_owned()), 3, 1),
1706 (("4".to_owned(), "vier".to_owned()), 4, 1),
1707 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1708 ];
1709
1710 let id = ShardId::new();
1711 let client = new_test_client(&dyncfgs).await;
1712
1713 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1714
1715 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1716
1717 write1
1719 .expect_append(&data[..2], write1.upper().clone(), vec![3])
1720 .await;
1721 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1722
1723 write2.upper = Antichain::from_elem(3);
1725 write2
1726 .expect_append(&data[2..4], write2.upper().clone(), vec![5])
1727 .await;
1728 assert_eq!(write2.upper(), &Antichain::from_elem(5));
1729
1730 write1.upper = Antichain::from_elem(5);
1732 write1
1733 .expect_append(&data[4..5], write1.upper().clone(), vec![6])
1734 .await;
1735 assert_eq!(write1.upper(), &Antichain::from_elem(6));
1736
1737 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1738 }
1739
1740 #[mz_persist_proc::test(tokio::test)]
1743 #[cfg_attr(miri, ignore)] async fn contiguous_compare_and_append(dyncfgs: ConfigUpdates) {
1745 let data = vec![
1746 (("1".to_owned(), "one".to_owned()), 1, 1),
1747 (("2".to_owned(), "two".to_owned()), 2, 1),
1748 (("3".to_owned(), "three".to_owned()), 3, 1),
1749 (("4".to_owned(), "vier".to_owned()), 4, 1),
1750 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1751 ];
1752
1753 let id = ShardId::new();
1754 let client = new_test_client(&dyncfgs).await;
1755
1756 let (mut write, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1757
1758 write.expect_compare_and_append(&data[..2], 0, 3).await;
1760 assert_eq!(write.upper(), &Antichain::from_elem(3));
1761
1762 let result = write
1765 .compare_and_append(
1766 &data[4..5],
1767 Antichain::from_elem(5),
1768 Antichain::from_elem(6),
1769 )
1770 .await;
1771 assert_eq!(
1772 result,
1773 Ok(Err(UpperMismatch {
1774 expected: Antichain::from_elem(5),
1775 current: Antichain::from_elem(3)
1776 }))
1777 );
1778
1779 write.expect_compare_and_append(&data[2..5], 3, 6).await;
1782 assert_eq!(write.upper(), &Antichain::from_elem(6));
1783
1784 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1785 }
1786
1787 #[mz_persist_proc::test(tokio::test)]
1790 #[cfg_attr(miri, ignore)] async fn noncontiguous_compare_and_append_per_writer(dyncfgs: ConfigUpdates) {
1792 let data = vec![
1793 (("1".to_owned(), "one".to_owned()), 1, 1),
1794 (("2".to_owned(), "two".to_owned()), 2, 1),
1795 (("3".to_owned(), "three".to_owned()), 3, 1),
1796 (("4".to_owned(), "vier".to_owned()), 4, 1),
1797 (("5".to_owned(), "cinque".to_owned()), 5, 1),
1798 ];
1799
1800 let id = ShardId::new();
1801 let client = new_test_client(&dyncfgs).await;
1802
1803 let (mut write1, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1804
1805 let (mut write2, _read) = client.expect_open::<String, String, u64, i64>(id).await;
1806
1807 write1.expect_compare_and_append(&data[..2], 0, 3).await;
1809 assert_eq!(write1.upper(), &Antichain::from_elem(3));
1810
1811 write2.expect_compare_and_append(&data[2..4], 3, 5).await;
1813 assert_eq!(write2.upper(), &Antichain::from_elem(5));
1814
1815 write1.expect_compare_and_append(&data[4..5], 5, 6).await;
1817 assert_eq!(write1.upper(), &Antichain::from_elem(6));
1818
1819 assert_eq!(read.expect_snapshot_and_fetch(5).await, all_ok(&data, 5));
1820 }
1821
1822 #[mz_ore::test]
1823 fn fmt_ids() {
1824 assert_eq!(
1825 format!("{}", LeasedReaderId([0u8; 16])),
1826 "r00000000-0000-0000-0000-000000000000"
1827 );
1828 assert_eq!(
1829 format!("{:?}", LeasedReaderId([0u8; 16])),
1830 "LeasedReaderId(00000000-0000-0000-0000-000000000000)"
1831 );
1832 }
1833
1834 #[mz_persist_proc::test(tokio::test(flavor = "multi_thread"))]
1835 #[cfg_attr(miri, ignore)] async fn concurrency(dyncfgs: ConfigUpdates) {
1837 let data = DataGenerator::small();
1838
1839 const NUM_WRITERS: usize = 2;
1840 let id = ShardId::new();
1841 let client = new_test_client(&dyncfgs).await;
1842 let mut handles = Vec::<mz_ore::task::JoinHandle<()>>::new();
1843 for idx in 0..NUM_WRITERS {
1844 let (data, client) = (data.clone(), client.clone());
1845
1846 let (batch_tx, mut batch_rx) = tokio::sync::mpsc::channel(1);
1847
1848 let client1 = client.clone();
1849 let handle = mz_ore::task::spawn(|| format!("writer-{}", idx), async move {
1850 let (write, _) = client1.expect_open::<Vec<u8>, Vec<u8>, u64, i64>(id).await;
1851 let mut current_upper = 0;
1852 for batch in data.batches() {
1853 let new_upper = match batch.get(batch.len() - 1) {
1854 Some((_, max_ts, _)) => u64::decode(max_ts) + 1,
1855 None => continue,
1856 };
1857 if PartialOrder::less_equal(&Antichain::from_elem(new_upper), write.upper()) {
1872 continue;
1873 }
1874
1875 let current_upper_chain = Antichain::from_elem(current_upper);
1876 current_upper = new_upper;
1877 let new_upper_chain = Antichain::from_elem(new_upper);
1878 let mut builder = write.builder(current_upper_chain);
1879
1880 for ((k, v), t, d) in batch.iter() {
1881 builder
1882 .add(&k.to_vec(), &v.to_vec(), &u64::decode(t), &i64::decode(d))
1883 .await
1884 .expect("invalid usage");
1885 }
1886
1887 let batch = builder
1888 .finish(new_upper_chain)
1889 .await
1890 .expect("invalid usage");
1891
1892 match batch_tx.send(batch).await {
1893 Ok(_) => (),
1894 Err(e) => panic!("send error: {}", e),
1895 }
1896 }
1897 });
1898 handles.push(handle);
1899
1900 let handle = mz_ore::task::spawn(|| format!("appender-{}", idx), async move {
1901 let (mut write, _) = client.expect_open::<Vec<u8>, Vec<u8>, u64, i64>(id).await;
1902
1903 while let Some(batch) = batch_rx.recv().await {
1904 let lower = batch.lower().clone();
1905 let upper = batch.upper().clone();
1906 write
1907 .append_batch(batch, lower, upper)
1908 .await
1909 .expect("invalid usage")
1910 .expect("unexpected upper");
1911 }
1912 });
1913 handles.push(handle);
1914 }
1915
1916 for handle in handles {
1917 let () = handle.await;
1918 }
1919
1920 let expected = data.records().collect::<Vec<_>>();
1921 let max_ts = expected.last().map(|(_, t, _)| *t).unwrap_or_default();
1922 let (_, mut read) = client.expect_open::<Vec<u8>, Vec<u8>, u64, i64>(id).await;
1923 assert_eq!(
1924 read.expect_snapshot_and_fetch(max_ts).await,
1925 all_ok(expected.iter(), max_ts)
1926 );
1927 }
1928
1929 #[mz_persist_proc::test(tokio::test)]
1933 #[cfg_attr(miri, ignore)] async fn regression_blocking_reads(dyncfgs: ConfigUpdates) {
1935 let waker = noop_waker();
1936 let mut cx = Context::from_waker(&waker);
1937
1938 let data = [
1939 (("1".to_owned(), "one".to_owned()), 1, 1),
1940 (("2".to_owned(), "two".to_owned()), 2, 1),
1941 (("3".to_owned(), "three".to_owned()), 3, 1),
1942 ];
1943
1944 let id = ShardId::new();
1945 let client = new_test_client(&dyncfgs).await;
1946 let (mut write, mut read) = client.expect_open::<String, String, u64, i64>(id).await;
1947
1948 let mut listen = read.clone("").await.expect_listen(1).await;
1950 let mut listen_next = Box::pin(listen.fetch_next());
1951 for _ in 0..100 {
1955 assert!(
1956 Pin::new(&mut listen_next).poll(&mut cx).is_pending(),
1957 "listen::next unexpectedly ready"
1958 );
1959 }
1960
1961 write
1963 .expect_compare_and_append(&data[..2], u64::minimum(), 3)
1964 .await;
1965
1966 assert_eq!(
1969 listen_next.await,
1970 vec![
1971 ListenEvent::Updates(vec![(("2".to_owned(), "two".to_owned()), 2, 1)]),
1972 ListenEvent::Progress(Antichain::from_elem(3)),
1973 ]
1974 );
1975
1976 let mut snap = Box::pin(read.expect_snapshot_and_fetch(3));
1990 for _ in 0..100 {
1991 assert!(
1992 Pin::new(&mut snap).poll(&mut cx).is_pending(),
1993 "snapshot unexpectedly ready"
1994 );
1995 }
1996
1997 write.expect_compare_and_append(&data[2..], 3, 4).await;
1999
2000 assert_eq!(snap.await, all_ok(&data[..], 3));
2002 }
2003
2004 #[mz_persist_proc::test(tokio::test)]
2005 #[cfg_attr(miri, ignore)] async fn heartbeat_task_shutdown(dyncfgs: ConfigUpdates) {
2007 let mut cache = new_test_client_cache(&dyncfgs);
2010 cache
2011 .cfg
2012 .set_config(&READER_LEASE_DURATION, Duration::from_millis(1));
2013 cache.cfg.writer_lease_duration = Duration::from_millis(1);
2014 let (_write, mut read) = cache
2015 .open(PersistLocation::new_in_mem())
2016 .await
2017 .expect("client construction failed")
2018 .expect_open::<(), (), u64, i64>(ShardId::new())
2019 .await;
2020 let read_unexpired_state = read
2021 .unexpired_state
2022 .take()
2023 .expect("handle should have unexpired state");
2024 read.expire().await;
2025 read_unexpired_state.heartbeat_task.await
2026 }
2027
2028 #[mz_persist_proc::test(tokio::test)]
2031 #[cfg_attr(miri, ignore)] async fn finalize_empty_shard(dyncfgs: ConfigUpdates) {
2033 let persist_client = new_test_client(&dyncfgs).await;
2034
2035 let shard_id = ShardId::new();
2036 pub const CRITICAL_SINCE: CriticalReaderId =
2037 CriticalReaderId([0, 0, 0, 0, 17, 17, 34, 34, 51, 51, 68, 68, 68, 68, 68, 68]);
2038
2039 let (mut write, mut read) = persist_client
2040 .expect_open::<(), (), u64, i64>(shard_id)
2041 .await;
2042
2043 let () = read.downgrade_since(&Antichain::new()).await;
2046 let () = write.advance_upper(&Antichain::new()).await;
2047
2048 let mut since_handle: SinceHandle<(), (), u64, i64> = persist_client
2049 .open_critical_since(
2050 shard_id,
2051 CRITICAL_SINCE,
2052 Opaque::encode(&0u64),
2053 Diagnostics::for_tests(),
2054 )
2055 .await
2056 .expect("invalid persist usage");
2057
2058 let epoch = since_handle.opaque().clone();
2059 let new_since = Antichain::new();
2060 let downgrade = since_handle
2061 .compare_and_downgrade_since(&epoch, (&epoch, &new_since))
2062 .await;
2063
2064 assert!(
2065 downgrade.is_ok(),
2066 "downgrade of critical handle must succeed"
2067 );
2068
2069 let finalize = persist_client
2070 .finalize_shard::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2071 .await;
2072
2073 assert_ok!(finalize, "finalization must succeed");
2074
2075 let is_finalized = persist_client
2076 .is_finalized::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2077 .await
2078 .expect("invalid persist usage");
2079 assert!(is_finalized, "shard must still be finalized");
2080 }
2081
2082 #[mz_persist_proc::test(tokio::test)]
2086 #[cfg_attr(miri, ignore)] async fn finalize_shard(dyncfgs: ConfigUpdates) {
2088 const DATA: &[(((), ()), u64, i64)] = &[(((), ()), 0, 1)];
2089 let persist_client = new_test_client(&dyncfgs).await;
2090
2091 let shard_id = ShardId::new();
2092 pub const CRITICAL_SINCE: CriticalReaderId =
2093 CriticalReaderId([0, 0, 0, 0, 17, 17, 34, 34, 51, 51, 68, 68, 68, 68, 68, 68]);
2094
2095 let (mut write, mut read) = persist_client
2096 .expect_open::<(), (), u64, i64>(shard_id)
2097 .await;
2098
2099 let () = write
2101 .compare_and_append(DATA, Antichain::from_elem(0), Antichain::from_elem(1))
2102 .await
2103 .expect("usage should be valid")
2104 .expect("upper should match");
2105
2106 let () = read.downgrade_since(&Antichain::new()).await;
2109 let () = write.advance_upper(&Antichain::new()).await;
2110
2111 let mut since_handle: SinceHandle<(), (), u64, i64> = persist_client
2112 .open_critical_since(
2113 shard_id,
2114 CRITICAL_SINCE,
2115 Opaque::encode(&0u64),
2116 Diagnostics::for_tests(),
2117 )
2118 .await
2119 .expect("invalid persist usage");
2120
2121 let epoch = since_handle.opaque().clone();
2122 let new_since = Antichain::new();
2123 let downgrade = since_handle
2124 .compare_and_downgrade_since(&epoch, (&epoch, &new_since))
2125 .await;
2126
2127 assert!(
2128 downgrade.is_ok(),
2129 "downgrade of critical handle must succeed"
2130 );
2131
2132 let finalize = persist_client
2133 .finalize_shard::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2134 .await;
2135
2136 assert_ok!(finalize, "finalization must succeed");
2137
2138 let is_finalized = persist_client
2139 .is_finalized::<(), (), u64, i64>(shard_id, Diagnostics::for_tests())
2140 .await
2141 .expect("invalid persist usage");
2142 assert!(is_finalized, "shard must still be finalized");
2143 }
2144
2145 proptest! {
2146 #![proptest_config(ProptestConfig::with_cases(4096))]
2147
2148 #[mz_ore::test]
2149 #[cfg_attr(miri, ignore)] fn shard_id_protobuf_roundtrip(expect in any::<ShardId>() ) {
2151 let actual = protobuf_roundtrip::<_, String>(&expect);
2152 assert_ok!(actual);
2153 assert_eq!(actual.unwrap(), expect);
2154 }
2155 }
2156}