1use std::cmp::Reverse;
13use std::collections::{BTreeMap, BTreeSet};
14use std::fmt::Debug;
15use std::iter;
16use std::num::NonZeroI64;
17use std::sync::{Arc, Mutex};
18use std::time::Duration;
19
20use async_trait::async_trait;
21use differential_dataflow::lattice::Lattice;
22use futures::future::BoxFuture;
23use futures::stream::{BoxStream, FuturesUnordered};
24use futures::{Future, FutureExt, StreamExt};
25use itertools::Itertools;
26use mz_ore::collections::CollectionExt;
27use mz_ore::metrics::MetricsRegistry;
28use mz_ore::now::NowFn;
29use mz_ore::task::AbortOnDropHandle;
30use mz_ore::{assert_none, instrument, soft_assert_or_log};
31use mz_persist_client::cache::PersistClientCache;
32use mz_persist_client::cfg::USE_CRITICAL_SINCE_SNAPSHOT;
33use mz_persist_client::critical::{Opaque, SinceHandle};
34use mz_persist_client::read::{Cursor, ReadHandle};
35use mz_persist_client::schema::CaESchema;
36use mz_persist_client::stats::{SnapshotPartsStats, SnapshotStats};
37use mz_persist_client::write::WriteHandle;
38use mz_persist_client::{Diagnostics, PersistClient, PersistLocation, ShardId};
39use mz_persist_types::codec_impls::UnitSchema;
40use mz_persist_types::txn::TxnsCodec;
41use mz_repr::{GlobalId, RelationDesc, RelationVersion, Row, Timestamp};
42use mz_storage_types::StorageDiff;
43use mz_storage_types::configuration::StorageConfiguration;
44use mz_storage_types::connections::ConnectionContext;
45use mz_storage_types::controller::{CollectionMetadata, StorageError, TxnsCodecRow};
46use mz_storage_types::dyncfgs::STORAGE_DOWNGRADE_SINCE_DURING_FINALIZATION;
47use mz_storage_types::errors::CollectionMissing;
48use mz_storage_types::parameters::StorageParameters;
49use mz_storage_types::read_holds::ReadHold;
50use mz_storage_types::read_policy::ReadPolicy;
51use mz_storage_types::sources::{GenericSourceConnection, SourceData, SourceEnvelope, Timeline};
52use mz_storage_types::time_dependence::{TimeDependence, TimeDependenceError};
53use mz_txn_wal::metrics::Metrics as TxnMetrics;
54use mz_txn_wal::txn_read::{DataSnapshot, TxnsRead};
55use mz_txn_wal::txns::TxnsHandle;
56use timely::PartialOrder;
57use timely::progress::frontier::MutableAntichain;
58use timely::progress::{Antichain, ChangeBatch};
59use tokio::sync::{mpsc, oneshot};
60use tokio::time::MissedTickBehavior;
61use tracing::{debug, info, trace, warn};
62
63use crate::client::TimestamplessUpdateBuilder;
64use crate::controller::{
65 CollectionDescription, DataSource, PersistEpoch, StorageMetadata, StorageTxn,
66};
67use crate::storage_collections::metrics::{ShardIdSet, StorageCollectionsMetrics};
68
69mod metrics;
70
71#[async_trait]
85pub trait StorageCollections: Debug + Sync {
86 async fn initialize_state(
93 &self,
94 txn: &mut (dyn StorageTxn + Send),
95 init_ids: BTreeSet<GlobalId>,
96 ) -> Result<(), StorageError>;
97
98 fn update_parameters(&self, config_params: StorageParameters);
100
101 fn collection_metadata(&self, id: GlobalId) -> Result<CollectionMetadata, CollectionMissing>;
103
104 fn active_collection_metadatas(&self) -> Vec<(GlobalId, CollectionMetadata)>;
110
111 fn collection_frontiers(&self, id: GlobalId) -> Result<CollectionFrontiers, CollectionMissing> {
113 let frontiers = self
114 .collections_frontiers(vec![id])?
115 .expect_element(|| "known to exist");
116
117 Ok(frontiers)
118 }
119
120 fn collections_frontiers(
123 &self,
124 id: Vec<GlobalId>,
125 ) -> Result<Vec<CollectionFrontiers>, CollectionMissing>;
126
127 fn active_collection_frontiers(&self) -> Vec<CollectionFrontiers>;
132
133 fn check_exists(&self, id: GlobalId) -> Result<(), StorageError>;
136
137 async fn snapshot_stats(
140 &self,
141 id: GlobalId,
142 as_of: Antichain<Timestamp>,
143 ) -> Result<SnapshotStats, StorageError>;
144
145 async fn snapshot_parts_stats(
154 &self,
155 id: GlobalId,
156 as_of: Antichain<Timestamp>,
157 ) -> BoxFuture<'static, Result<SnapshotPartsStats, StorageError>>;
158
159 fn snapshot(
161 &self,
162 id: GlobalId,
163 as_of: Timestamp,
164 ) -> BoxFuture<'static, Result<Vec<(Row, StorageDiff)>, StorageError>>;
165
166 async fn snapshot_latest(&self, id: GlobalId) -> Result<Vec<Row>, StorageError>;
181
182 fn snapshot_cursor(
184 &self,
185 id: GlobalId,
186 as_of: Timestamp,
187 ) -> BoxFuture<'static, Result<SnapshotCursor, StorageError>>;
188
189 fn snapshot_and_stream(
194 &self,
195 id: GlobalId,
196 as_of: Timestamp,
197 ) -> BoxFuture<
198 'static,
199 Result<BoxStream<'static, (SourceData, Timestamp, StorageDiff)>, StorageError>,
200 >;
201
202 fn create_update_builder(
205 &self,
206 id: GlobalId,
207 ) -> BoxFuture<
208 'static,
209 Result<TimestamplessUpdateBuilder<SourceData, (), StorageDiff>, StorageError>,
210 >;
211
212 async fn prepare_state(
218 &self,
219 txn: &mut (dyn StorageTxn + Send),
220 ids_to_add: BTreeSet<GlobalId>,
221 ids_to_drop: BTreeSet<GlobalId>,
222 ids_to_register: BTreeMap<GlobalId, ShardId>,
223 ) -> Result<(), StorageError>;
224
225 async fn create_collections_for_bootstrap(
251 &self,
252 storage_metadata: &StorageMetadata,
253 register_ts: Option<Timestamp>,
254 collections: Vec<(GlobalId, CollectionDescription)>,
255 migrated_storage_collections: &BTreeSet<GlobalId>,
256 ) -> Result<(), StorageError>;
257
258 async fn alter_table_desc(
260 &self,
261 existing_collection: GlobalId,
262 new_collection: GlobalId,
263 new_desc: RelationDesc,
264 expected_version: RelationVersion,
265 ) -> Result<(), StorageError>;
266
267 fn drop_collections_unvalidated(
279 &self,
280 storage_metadata: &StorageMetadata,
281 identifiers: Vec<GlobalId>,
282 );
283
284 fn set_read_policies(&self, policies: Vec<(GlobalId, ReadPolicy)>);
298
299 fn acquire_read_holds(
302 &self,
303 desired_holds: Vec<GlobalId>,
304 ) -> Result<Vec<ReadHold>, CollectionMissing>;
305
306 fn determine_time_dependence(
309 &self,
310 id: GlobalId,
311 ) -> Result<Option<TimeDependence>, TimeDependenceError>;
312
313 fn dump(&self) -> Result<serde_json::Value, anyhow::Error>;
315}
316
317pub struct SnapshotCursor {
320 pub _read_handle: ReadHandle<SourceData, (), Timestamp, StorageDiff>,
323 pub cursor: Cursor<SourceData, (), Timestamp, StorageDiff>,
324}
325
326impl SnapshotCursor {
327 pub async fn next(
328 &mut self,
329 ) -> Option<impl Iterator<Item = (SourceData, Timestamp, StorageDiff)> + Sized + '_> {
330 let iter = self.cursor.next().await?;
331 Some(iter.map(|((k, ()), t, d)| (k, t, d)))
332 }
333}
334
335#[derive(Debug)]
337pub struct CollectionFrontiers {
338 pub id: GlobalId,
340
341 pub write_frontier: Antichain<Timestamp>,
343
344 pub implied_capability: Antichain<Timestamp>,
351
352 pub read_capabilities: Antichain<Timestamp>,
355}
356
357#[derive(Debug, Clone)]
360pub struct StorageCollectionsImpl {
361 envd_epoch: NonZeroI64,
364
365 read_only: bool,
371
372 finalizable_shards: Arc<ShardIdSet>,
375
376 finalized_shards: Arc<ShardIdSet>,
381
382 collections: Arc<std::sync::Mutex<BTreeMap<GlobalId, CollectionState>>>,
384
385 txns_read: TxnsRead<Timestamp>,
387
388 config: Arc<Mutex<StorageConfiguration>>,
390
391 initial_txn_upper: Antichain<Timestamp>,
400
401 persist_location: PersistLocation,
403
404 persist: Arc<PersistClientCache>,
406
407 cmd_tx: mpsc::UnboundedSender<BackgroundCmd>,
409
410 holds_tx: mpsc::UnboundedSender<(GlobalId, ChangeBatch<Timestamp>)>,
412
413 _background_task: Arc<AbortOnDropHandle<()>>,
415 _finalize_shards_task: Arc<AbortOnDropHandle<()>>,
416}
417
418type SourceDataStream = BoxStream<'static, (SourceData, Timestamp, StorageDiff)>;
427
428impl StorageCollectionsImpl {
431 pub async fn new(
439 persist_location: PersistLocation,
440 persist_clients: Arc<PersistClientCache>,
441 metrics_registry: &MetricsRegistry,
442 _now: NowFn,
443 txns_metrics: Arc<TxnMetrics>,
444 envd_epoch: NonZeroI64,
445 read_only: bool,
446 connection_context: ConnectionContext,
447 txn: &dyn StorageTxn,
448 ) -> Self {
449 let metrics = StorageCollectionsMetrics::register_into(metrics_registry);
450
451 let txns_id = txn
455 .get_txn_wal_shard()
456 .expect("must call prepare initialization before creating StorageCollections");
457
458 let txns_client = persist_clients
459 .open(persist_location.clone())
460 .await
461 .expect("location should be valid");
462
463 let _txns_handle: TxnsHandle<SourceData, (), Timestamp, StorageDiff, TxnsCodecRow> =
466 TxnsHandle::open(
467 Timestamp::MIN,
468 txns_client.clone(),
469 txns_client.dyncfgs().clone(),
470 Arc::clone(&txns_metrics),
471 txns_id,
472 Opaque::encode(&PersistEpoch::default()),
473 )
474 .await;
475
476 let (txns_key_schema, txns_val_schema) = TxnsCodecRow::schemas();
478 let mut txns_write = txns_client
479 .open_writer(
480 txns_id,
481 Arc::new(txns_key_schema),
482 Arc::new(txns_val_schema),
483 Diagnostics {
484 shard_name: "txns".to_owned(),
485 handle_purpose: "commit txns".to_owned(),
486 },
487 )
488 .await
489 .expect("txns schema shouldn't change");
490
491 let txns_read = TxnsRead::start::<TxnsCodecRow>(txns_client.clone(), txns_id).await;
492
493 let collections = Arc::new(std::sync::Mutex::new(BTreeMap::default()));
494 let finalizable_shards =
495 Arc::new(ShardIdSet::new(metrics.finalization_outstanding.clone()));
496 let finalized_shards =
497 Arc::new(ShardIdSet::new(metrics.finalization_pending_commit.clone()));
498 let config = Arc::new(Mutex::new(StorageConfiguration::new(
499 connection_context,
500 mz_dyncfgs::all_dyncfgs(),
501 )));
502
503 let initial_txn_upper = txns_write.fetch_recent_upper().await.to_owned();
504
505 let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
506 let (holds_tx, holds_rx) = mpsc::unbounded_channel();
507 let mut background_task = BackgroundTask {
508 config: Arc::clone(&config),
509 cmds_tx: cmd_tx.clone(),
510 cmds_rx: cmd_rx,
511 holds_rx,
512 collections: Arc::clone(&collections),
513 finalizable_shards: Arc::clone(&finalizable_shards),
514 shard_by_id: BTreeMap::new(),
515 since_handles: BTreeMap::new(),
516 txns_handle: Some(txns_write),
517 txns_shards: Default::default(),
518 };
519
520 let background_task =
521 mz_ore::task::spawn(|| "storage_collections::background_task", async move {
522 background_task.run().await
523 });
524
525 let finalize_shards_task = mz_ore::task::spawn(
526 || "storage_collections::finalize_shards_task",
527 finalize_shards_task(FinalizeShardsTaskConfig {
528 envd_epoch: envd_epoch.clone(),
529 config: Arc::clone(&config),
530 metrics,
531 finalizable_shards: Arc::clone(&finalizable_shards),
532 finalized_shards: Arc::clone(&finalized_shards),
533 persist_location: persist_location.clone(),
534 persist: Arc::clone(&persist_clients),
535 read_only,
536 }),
537 );
538
539 Self {
540 finalizable_shards,
541 finalized_shards,
542 collections,
543 txns_read,
544 envd_epoch,
545 read_only,
546 config,
547 initial_txn_upper,
548 persist_location,
549 persist: persist_clients,
550 cmd_tx,
551 holds_tx,
552 _background_task: Arc::new(background_task.abort_on_drop()),
553 _finalize_shards_task: Arc::new(finalize_shards_task.abort_on_drop()),
554 }
555 }
556
557 async fn open_data_handles(
565 &self,
566 id: &GlobalId,
567 shard: ShardId,
568 since: Option<&Antichain<Timestamp>>,
569 relation_desc: RelationDesc,
570 persist_client: &PersistClient,
571 ) -> (
572 WriteHandle<SourceData, (), Timestamp, StorageDiff>,
573 SinceHandleWrapper,
574 ) {
575 let since_handle = if self.read_only {
576 let read_handle = self
577 .open_leased_handle(id, shard, relation_desc.clone(), since, persist_client)
578 .await;
579 SinceHandleWrapper::Leased(read_handle)
580 } else {
581 persist_client
584 .upgrade_version::<SourceData, (), Timestamp, StorageDiff>(
585 shard,
586 Diagnostics {
587 shard_name: id.to_string(),
588 handle_purpose: format!("controller data for {}", id),
589 },
590 )
591 .await
592 .expect("invalid persist usage");
593
594 let since_handle = self
595 .open_critical_handle(id, shard, since, persist_client)
596 .await;
597
598 SinceHandleWrapper::Critical(since_handle)
599 };
600
601 let mut write_handle = self
602 .open_write_handle(id, shard, relation_desc, persist_client)
603 .await;
604
605 write_handle.fetch_recent_upper().await;
616
617 (write_handle, since_handle)
618 }
619
620 async fn open_write_handle(
622 &self,
623 id: &GlobalId,
624 shard: ShardId,
625 relation_desc: RelationDesc,
626 persist_client: &PersistClient,
627 ) -> WriteHandle<SourceData, (), Timestamp, StorageDiff> {
628 let diagnostics = Diagnostics {
629 shard_name: id.to_string(),
630 handle_purpose: format!("controller data for {}", id),
631 };
632
633 let write = persist_client
634 .open_writer(
635 shard,
636 Arc::new(relation_desc),
637 Arc::new(UnitSchema),
638 diagnostics.clone(),
639 )
640 .await
641 .expect("invalid persist usage");
642
643 write
644 }
645
646 async fn open_critical_handle(
654 &self,
655 id: &GlobalId,
656 shard: ShardId,
657 since: Option<&Antichain<Timestamp>>,
658 persist_client: &PersistClient,
659 ) -> SinceHandle<SourceData, (), Timestamp, StorageDiff> {
660 tracing::debug!(%id, ?since, "opening critical handle");
661
662 assert!(
663 !self.read_only,
664 "attempting to open critical SinceHandle in read-only mode"
665 );
666
667 let diagnostics = Diagnostics {
668 shard_name: id.to_string(),
669 handle_purpose: format!("controller data for {}", id),
670 };
671
672 let since_handle = {
675 let mut handle = persist_client
678 .open_critical_since(
679 shard,
680 PersistClient::CONTROLLER_CRITICAL_SINCE,
681 Opaque::encode(&PersistEpoch::default()),
682 diagnostics.clone(),
683 )
684 .await
685 .expect("invalid persist usage");
686
687 let provided_since = match since {
691 Some(since) => since,
692 None => &Antichain::from_elem(Timestamp::MIN),
693 };
694 let since = handle.since().join(provided_since);
695
696 let our_epoch = self.envd_epoch;
697
698 loop {
699 let current_epoch: PersistEpoch = handle.opaque().decode();
700
701 let unchecked_success = current_epoch.0.map(|e| e <= our_epoch).unwrap_or(true);
703
704 if unchecked_success {
705 let checked_success = handle
708 .compare_and_downgrade_since(
709 &Opaque::encode(¤t_epoch),
710 (&Opaque::encode(&PersistEpoch::from(our_epoch)), &since),
711 )
712 .await
713 .is_ok();
714 if checked_success {
715 break handle;
716 }
717 } else {
718 mz_ore::halt!("fenced by envd @ {current_epoch:?}. ours = {our_epoch}");
719 }
720 }
721 };
722
723 since_handle
724 }
725
726 async fn open_leased_handle(
732 &self,
733 id: &GlobalId,
734 shard: ShardId,
735 relation_desc: RelationDesc,
736 since: Option<&Antichain<Timestamp>>,
737 persist_client: &PersistClient,
738 ) -> ReadHandle<SourceData, (), Timestamp, StorageDiff> {
739 tracing::debug!(%id, ?since, "opening leased handle");
740
741 let diagnostics = Diagnostics {
742 shard_name: id.to_string(),
743 handle_purpose: format!("controller data for {}", id),
744 };
745
746 let use_critical_since = false;
747 let mut handle: ReadHandle<_, _, _, _> = persist_client
748 .open_leased_reader(
749 shard,
750 Arc::new(relation_desc),
751 Arc::new(UnitSchema),
752 diagnostics.clone(),
753 use_critical_since,
754 )
755 .await
756 .expect("invalid persist usage");
757
758 let provided_since = match since {
762 Some(since) => since,
763 None => &Antichain::from_elem(Timestamp::MIN),
764 };
765 let since = handle.since().join(provided_since);
766
767 handle.downgrade_since(&since).await;
768
769 handle
770 }
771
772 fn register_handles(
773 &self,
774 id: GlobalId,
775 is_in_txns: bool,
776 since_handle: SinceHandleWrapper,
777 write_handle: WriteHandle<SourceData, (), Timestamp, StorageDiff>,
778 ) {
779 self.send(BackgroundCmd::Register {
780 id,
781 is_in_txns,
782 since_handle,
783 write_handle,
784 });
785 }
786
787 fn send(&self, cmd: BackgroundCmd) {
788 let _ = self.cmd_tx.send(cmd);
789 }
790
791 async fn snapshot_stats_inner(
792 &self,
793 id: GlobalId,
794 as_of: SnapshotStatsAsOf,
795 ) -> Result<SnapshotStats, StorageError> {
796 let (tx, rx) = oneshot::channel();
803 self.send(BackgroundCmd::SnapshotStats(id, as_of, tx));
804 rx.await.expect("BackgroundTask should be live").0.await
805 }
806
807 fn install_collection_dependency_read_holds_inner(
813 &self,
814 self_collections: &mut BTreeMap<GlobalId, CollectionState>,
815 id: GlobalId,
816 ) -> Result<(), StorageError> {
817 let (deps, collection_implied_capability) = match self_collections.get(&id) {
818 Some(CollectionState {
819 storage_dependencies: deps,
820 implied_capability,
821 ..
822 }) => (deps.clone(), implied_capability),
823 _ => return Ok(()),
824 };
825
826 for dep in deps.iter() {
827 let dep_collection = self_collections
828 .get(dep)
829 .ok_or(StorageError::IdentifierMissing(id))?;
830
831 mz_ore::soft_assert_or_log!(
832 PartialOrder::less_equal(
833 &dep_collection.implied_capability,
834 collection_implied_capability
835 ),
836 "dependency since ({dep}@{:?}) cannot be in advance of dependent's since ({id}@{:?})",
837 dep_collection.implied_capability,
838 collection_implied_capability,
839 );
840 }
841
842 self.install_read_capabilities_inner(
843 self_collections,
844 id,
845 &deps,
846 collection_implied_capability.clone(),
847 )?;
848
849 Ok(())
850 }
851
852 fn determine_collection_dependencies(
854 self_collections: &BTreeMap<GlobalId, CollectionState>,
855 source_id: GlobalId,
856 collection_desc: &CollectionDescription,
857 ) -> Result<Vec<GlobalId>, StorageError> {
858 let mut dependencies = Vec::new();
859
860 if let Some(id) = collection_desc.primary {
861 dependencies.push(id);
862 }
863
864 match &collection_desc.data_source {
865 DataSource::Introspection(_)
866 | DataSource::Webhook
867 | DataSource::Table
868 | DataSource::Progress
869 | DataSource::Other => (),
870 DataSource::IngestionExport {
871 ingestion_id,
872 data_config,
873 ..
874 } => {
875 let source = self_collections
878 .get(ingestion_id)
879 .ok_or(StorageError::IdentifierMissing(*ingestion_id))?;
880 let Some(remap_collection_id) = &source.ingestion_remap_collection_id else {
881 panic!("SourceExport must refer to a primary source that already exists");
882 };
883
884 match data_config.envelope {
885 SourceEnvelope::CdcV2 => (),
886 _ => dependencies.push(*remap_collection_id),
887 }
888 }
889 DataSource::Ingestion(ingestion) => {
891 if ingestion.remap_collection_id != source_id {
892 dependencies.push(ingestion.remap_collection_id);
893 }
894 }
895 DataSource::Sink { desc } => dependencies.push(desc.sink.from),
896 }
897
898 Ok(dependencies)
899 }
900
901 #[instrument(level = "debug")]
903 fn install_read_capabilities_inner(
904 &self,
905 self_collections: &mut BTreeMap<GlobalId, CollectionState>,
906 from_id: GlobalId,
907 storage_dependencies: &[GlobalId],
908 read_capability: Antichain<Timestamp>,
909 ) -> Result<(), StorageError> {
910 let mut changes = ChangeBatch::new();
911 for time in read_capability.iter() {
912 changes.update(*time, 1);
913 }
914
915 if tracing::span_enabled!(tracing::Level::TRACE) {
916 let user_capabilities = self_collections
918 .iter_mut()
919 .filter(|(id, _c)| id.is_user())
920 .map(|(id, c)| {
921 let updates = c.read_capabilities.updates().cloned().collect_vec();
922 (*id, c.implied_capability.clone(), updates)
923 })
924 .collect_vec();
925
926 trace!(
927 %from_id,
928 ?storage_dependencies,
929 ?read_capability,
930 ?user_capabilities,
931 "install_read_capabilities_inner");
932 }
933
934 let mut storage_read_updates = storage_dependencies
935 .iter()
936 .map(|id| (*id, changes.clone()))
937 .collect();
938
939 StorageCollectionsImpl::update_read_capabilities_inner(
940 &self.cmd_tx,
941 self_collections,
942 &mut storage_read_updates,
943 );
944
945 if tracing::span_enabled!(tracing::Level::TRACE) {
946 let user_capabilities = self_collections
948 .iter_mut()
949 .filter(|(id, _c)| id.is_user())
950 .map(|(id, c)| {
951 let updates = c.read_capabilities.updates().cloned().collect_vec();
952 (*id, c.implied_capability.clone(), updates)
953 })
954 .collect_vec();
955
956 trace!(
957 %from_id,
958 ?storage_dependencies,
959 ?read_capability,
960 ?user_capabilities,
961 "after install_read_capabilities_inner!");
962 }
963
964 Ok(())
965 }
966
967 async fn recent_upper(&self, id: GlobalId) -> Result<Antichain<Timestamp>, StorageError> {
968 let metadata = &self.collection_metadata(id)?;
969 let persist_client = self
970 .persist
971 .open(metadata.persist_location.clone())
972 .await
973 .unwrap();
974 let diagnostics = Diagnostics {
977 shard_name: id.to_string(),
978 handle_purpose: format!("controller data for {}", id),
979 };
980 let write = persist_client
983 .open_writer::<SourceData, (), Timestamp, StorageDiff>(
984 metadata.data_shard,
985 Arc::new(metadata.relation_desc.clone()),
986 Arc::new(UnitSchema),
987 diagnostics.clone(),
988 )
989 .await
990 .expect("invalid persist usage");
991 Ok(write.shared_upper())
992 }
993
994 async fn read_handle_for_snapshot(
995 persist: Arc<PersistClientCache>,
996 metadata: &CollectionMetadata,
997 id: GlobalId,
998 ) -> Result<ReadHandle<SourceData, (), Timestamp, StorageDiff>, StorageError> {
999 let persist_client = persist
1000 .open(metadata.persist_location.clone())
1001 .await
1002 .unwrap();
1003
1004 let read_handle = persist_client
1010 .open_leased_reader::<SourceData, (), _, _>(
1011 metadata.data_shard,
1012 Arc::new(metadata.relation_desc.clone()),
1013 Arc::new(UnitSchema),
1014 Diagnostics {
1015 shard_name: id.to_string(),
1016 handle_purpose: format!("snapshot {}", id),
1017 },
1018 USE_CRITICAL_SINCE_SNAPSHOT.get(&persist.cfg),
1019 )
1020 .await
1021 .expect("invalid persist usage");
1022 Ok(read_handle)
1023 }
1024
1025 fn snapshot(
1026 &self,
1027 id: GlobalId,
1028 as_of: Timestamp,
1029 txns_read: &TxnsRead<Timestamp>,
1030 ) -> BoxFuture<'static, Result<Vec<(Row, StorageDiff)>, StorageError>> {
1031 let metadata = match self.collection_metadata(id) {
1032 Ok(metadata) => metadata.clone(),
1033 Err(e) => return async { Err(e.into()) }.boxed(),
1034 };
1035 let txns_read = metadata.txns_shard.as_ref().map(|txns_id| {
1036 assert_eq!(txns_id, txns_read.txns_id());
1037 txns_read.clone()
1038 });
1039 let persist = Arc::clone(&self.persist);
1040 async move {
1041 let mut read_handle = Self::read_handle_for_snapshot(persist, &metadata, id).await?;
1042 let contents = match txns_read {
1043 None => {
1044 read_handle
1046 .snapshot_and_fetch(Antichain::from_elem(as_of))
1047 .await
1048 }
1049 Some(txns_read) => {
1050 txns_read.update_gt(as_of).await;
1064 let data_snapshot = txns_read.data_snapshot(metadata.data_shard, as_of).await;
1065 data_snapshot.snapshot_and_fetch(&mut read_handle).await
1066 }
1067 };
1068 match contents {
1069 Ok(contents) => {
1070 let mut snapshot = Vec::with_capacity(contents.len());
1071 for ((data, _), _, diff) in contents {
1072 let row = data.0?;
1075 snapshot.push((row, diff));
1076 }
1077 Ok(snapshot)
1078 }
1079 Err(_) => Err(StorageError::ReadBeforeSince(id)),
1080 }
1081 }
1082 .boxed()
1083 }
1084
1085 fn snapshot_and_stream(
1086 &self,
1087 id: GlobalId,
1088 as_of: Timestamp,
1089 txns_read: &TxnsRead<Timestamp>,
1090 ) -> BoxFuture<'static, Result<SourceDataStream, StorageError>> {
1091 use futures::stream::StreamExt;
1092
1093 let metadata = match self.collection_metadata(id) {
1094 Ok(metadata) => metadata.clone(),
1095 Err(e) => return async { Err(e.into()) }.boxed(),
1096 };
1097 let txns_read = metadata.txns_shard.as_ref().map(|txns_id| {
1098 assert_eq!(txns_id, txns_read.txns_id());
1099 txns_read.clone()
1100 });
1101 let persist = Arc::clone(&self.persist);
1102
1103 async move {
1104 let mut read_handle = Self::read_handle_for_snapshot(persist, &metadata, id).await?;
1105 let stream = match txns_read {
1106 None => {
1107 read_handle
1109 .snapshot_and_stream(Antichain::from_elem(as_of))
1110 .await
1111 .map_err(|_| StorageError::ReadBeforeSince(id))?
1112 .boxed()
1113 }
1114 Some(txns_read) => {
1115 txns_read.update_gt(as_of).await;
1116 let data_snapshot = txns_read.data_snapshot(metadata.data_shard, as_of).await;
1117 data_snapshot
1118 .snapshot_and_stream(&mut read_handle)
1119 .await
1120 .map_err(|_| StorageError::ReadBeforeSince(id))?
1121 .boxed()
1122 }
1123 };
1124
1125 let stream = stream.map(|((data, _v), t, d)| (data, t, d)).boxed();
1127 Ok(stream)
1128 }
1129 .boxed()
1130 }
1131
1132 fn set_read_policies_inner(
1133 &self,
1134 collections: &mut BTreeMap<GlobalId, CollectionState>,
1135 policies: Vec<(GlobalId, ReadPolicy)>,
1136 ) {
1137 trace!("set_read_policies: {:?}", policies);
1138
1139 let mut read_capability_changes = BTreeMap::default();
1140
1141 for (id, policy) in policies.into_iter() {
1142 let collection = match collections.get_mut(&id) {
1143 Some(c) => c,
1144 None => {
1145 panic!("Reference to absent collection {id}");
1146 }
1147 };
1148
1149 let mut new_read_capability = policy.frontier(collection.write_frontier.borrow());
1150
1151 if PartialOrder::less_equal(&collection.implied_capability, &new_read_capability) {
1152 let mut update = ChangeBatch::new();
1153 update.extend(new_read_capability.iter().map(|time| (*time, 1)));
1154 std::mem::swap(&mut collection.implied_capability, &mut new_read_capability);
1155 update.extend(new_read_capability.iter().map(|time| (*time, -1)));
1156 if !update.is_empty() {
1157 read_capability_changes.insert(id, update);
1158 }
1159 }
1160
1161 collection.read_policy = policy;
1162 }
1163
1164 for (id, changes) in read_capability_changes.iter() {
1165 if id.is_user() {
1166 trace!(%id, ?changes, "in set_read_policies, capability changes");
1167 }
1168 }
1169
1170 if !read_capability_changes.is_empty() {
1171 StorageCollectionsImpl::update_read_capabilities_inner(
1172 &self.cmd_tx,
1173 collections,
1174 &mut read_capability_changes,
1175 );
1176 }
1177 }
1178
1179 fn update_read_capabilities_inner(
1183 cmd_tx: &mpsc::UnboundedSender<BackgroundCmd>,
1184 collections: &mut BTreeMap<GlobalId, CollectionState>,
1185 updates: &mut BTreeMap<GlobalId, ChangeBatch<Timestamp>>,
1186 ) {
1187 let mut collections_net = BTreeMap::new();
1189
1190 while let Some(id) = updates.keys().rev().next().cloned() {
1195 let mut update = updates.remove(&id).unwrap();
1196
1197 if id.is_user() {
1198 trace!(id = ?id, update = ?update, "update_read_capabilities");
1199 }
1200
1201 let collection = if let Some(c) = collections.get_mut(&id) {
1202 c
1203 } else {
1204 let has_positive_updates = update.iter().any(|(_ts, diff)| *diff > 0);
1205 if has_positive_updates {
1206 panic!(
1207 "reference to absent collection {id} but we have positive updates: {:?}",
1208 update
1209 );
1210 } else {
1211 continue;
1214 }
1215 };
1216
1217 let current_read_capabilities = collection.read_capabilities.frontier().to_owned();
1218 for (time, diff) in update.iter() {
1219 assert!(
1220 collection.read_capabilities.count_for(time) + diff >= 0,
1221 "update {:?} for collection {id} would lead to negative \
1222 read capabilities, read capabilities before applying: {:?}",
1223 update,
1224 collection.read_capabilities
1225 );
1226
1227 if collection.read_capabilities.count_for(time) + diff > 0 {
1228 assert!(
1229 current_read_capabilities.less_equal(time),
1230 "update {:?} for collection {id} is trying to \
1231 install read capabilities before the current \
1232 frontier of read capabilities, read capabilities before applying: {:?}",
1233 update,
1234 collection.read_capabilities
1235 );
1236 }
1237 }
1238
1239 let changes = collection.read_capabilities.update_iter(update.drain());
1240 update.extend(changes);
1241
1242 if id.is_user() {
1243 trace!(
1244 %id,
1245 ?collection.storage_dependencies,
1246 ?update,
1247 "forwarding update to storage dependencies");
1248 }
1249
1250 for id in collection.storage_dependencies.iter() {
1251 updates
1252 .entry(*id)
1253 .or_insert_with(ChangeBatch::new)
1254 .extend(update.iter().cloned());
1255 }
1256
1257 let (changes, frontier) = collections_net
1258 .entry(id)
1259 .or_insert_with(|| (<ChangeBatch<_>>::new(), Antichain::new()));
1260
1261 changes.extend(update.drain());
1262 *frontier = collection.read_capabilities.frontier().to_owned();
1263 }
1264
1265 let mut persist_compaction_commands = Vec::with_capacity(collections_net.len());
1268 for (key, (mut changes, frontier)) in collections_net {
1269 if !changes.is_empty() {
1270 let collection = collections.get(&key).expect("must still exist");
1272 let should_emit_persist_compaction = collection.primary.is_none();
1273
1274 if frontier.is_empty() {
1275 info!(id = %key, "removing collection state because the since advanced to []!");
1276 collections.remove(&key).expect("must still exist");
1277 }
1278
1279 if should_emit_persist_compaction {
1280 persist_compaction_commands.push((key, frontier));
1281 }
1282 }
1283 }
1284
1285 if !persist_compaction_commands.is_empty() {
1286 cmd_tx
1287 .send(BackgroundCmd::DowngradeSince(persist_compaction_commands))
1288 .expect("cannot fail to send");
1289 }
1290 }
1291
1292 fn synchronize_finalized_shards(&self, storage_metadata: &StorageMetadata) {
1294 self.finalized_shards
1295 .lock()
1296 .retain(|shard| storage_metadata.unfinalized_shards.contains(shard));
1297 }
1298}
1299
1300fn partition_finalizable_shards(
1303 collection_metadata: BTreeMap<GlobalId, ShardId>,
1304 active_collection_ids: &BTreeSet<GlobalId>,
1305 unfinalized_shards: BTreeSet<ShardId>,
1306) -> (BTreeSet<ShardId>, BTreeSet<ShardId>) {
1307 let active_shards: BTreeSet<_> = collection_metadata
1308 .into_iter()
1309 .filter_map(|(id, shard)| active_collection_ids.contains(&id).then_some(shard))
1310 .collect();
1311 let referenced_shards = unfinalized_shards
1312 .intersection(&active_shards)
1313 .copied()
1314 .collect();
1315 let finalizable_shards = unfinalized_shards
1316 .difference(&active_shards)
1317 .copied()
1318 .collect();
1319
1320 (referenced_shards, finalizable_shards)
1321}
1322
1323#[async_trait]
1325impl StorageCollections for StorageCollectionsImpl {
1326 async fn initialize_state(
1327 &self,
1328 txn: &mut (dyn StorageTxn + Send),
1329 init_ids: BTreeSet<GlobalId>,
1330 ) -> Result<(), StorageError> {
1331 let metadata = txn.get_collection_metadata();
1332 let existing_metadata: BTreeSet<_> = metadata.into_iter().map(|(id, _)| id).collect();
1333
1334 let new_collections: BTreeSet<GlobalId> =
1336 init_ids.difference(&existing_metadata).cloned().collect();
1337
1338 self.prepare_state(
1339 txn,
1340 new_collections,
1341 BTreeSet::default(),
1342 BTreeMap::default(),
1343 )
1344 .await?;
1345
1346 let (referenced_shards, unfinalized_shards) = partition_finalizable_shards(
1353 txn.get_collection_metadata(),
1354 &init_ids,
1355 txn.get_unfinalized_shards(),
1356 );
1357 if !referenced_shards.is_empty() {
1358 warn!(
1359 ?referenced_shards,
1360 "removing active collection shards from the finalization WAL"
1361 );
1362 txn.remove_unfinalized_shards(referenced_shards);
1366 }
1367
1368 info!(?unfinalized_shards, "initializing finalizable_shards");
1369
1370 self.finalizable_shards.lock().extend(unfinalized_shards);
1371
1372 Ok(())
1373 }
1374
1375 fn update_parameters(&self, config_params: StorageParameters) {
1376 config_params.dyncfg_updates.apply(self.persist.cfg());
1379
1380 self.config
1381 .lock()
1382 .expect("lock poisoned")
1383 .update(config_params);
1384 }
1385
1386 fn collection_metadata(&self, id: GlobalId) -> Result<CollectionMetadata, CollectionMissing> {
1387 let collections = self.collections.lock().expect("lock poisoned");
1388
1389 collections
1390 .get(&id)
1391 .map(|c| c.collection_metadata.clone())
1392 .ok_or(CollectionMissing(id))
1393 }
1394
1395 fn active_collection_metadatas(&self) -> Vec<(GlobalId, CollectionMetadata)> {
1396 let collections = self.collections.lock().expect("lock poisoned");
1397
1398 collections
1399 .iter()
1400 .filter(|(_id, c)| !c.is_dropped())
1401 .map(|(id, c)| (*id, c.collection_metadata.clone()))
1402 .collect()
1403 }
1404
1405 fn collections_frontiers(
1406 &self,
1407 ids: Vec<GlobalId>,
1408 ) -> Result<Vec<CollectionFrontiers>, CollectionMissing> {
1409 if ids.is_empty() {
1410 return Ok(vec![]);
1411 }
1412
1413 let collections = self.collections.lock().expect("lock poisoned");
1414
1415 let res = ids
1416 .into_iter()
1417 .map(|id| {
1418 collections
1419 .get(&id)
1420 .map(|c| CollectionFrontiers {
1421 id: id.clone(),
1422 write_frontier: c.write_frontier.clone(),
1423 implied_capability: c.implied_capability.clone(),
1424 read_capabilities: c.read_capabilities.frontier().to_owned(),
1425 })
1426 .ok_or(CollectionMissing(id))
1427 })
1428 .collect::<Result<Vec<_>, _>>()?;
1429
1430 Ok(res)
1431 }
1432
1433 fn active_collection_frontiers(&self) -> Vec<CollectionFrontiers> {
1434 let collections = self.collections.lock().expect("lock poisoned");
1435
1436 let res = collections
1437 .iter()
1438 .filter(|(_id, c)| !c.is_dropped())
1439 .map(|(id, c)| CollectionFrontiers {
1440 id: id.clone(),
1441 write_frontier: c.write_frontier.clone(),
1442 implied_capability: c.implied_capability.clone(),
1443 read_capabilities: c.read_capabilities.frontier().to_owned(),
1444 })
1445 .collect_vec();
1446
1447 res
1448 }
1449
1450 async fn snapshot_stats(
1451 &self,
1452 id: GlobalId,
1453 as_of: Antichain<Timestamp>,
1454 ) -> Result<SnapshotStats, StorageError> {
1455 let metadata = self.collection_metadata(id)?;
1456
1457 let as_of = match metadata.txns_shard.as_ref() {
1460 None => SnapshotStatsAsOf::Direct(as_of),
1461 Some(txns_id) => {
1462 assert_eq!(txns_id, self.txns_read.txns_id());
1463 let as_of = as_of
1464 .into_option()
1465 .expect("cannot read as_of the empty antichain");
1466 self.txns_read.update_gt(as_of).await;
1467 let data_snapshot = self
1468 .txns_read
1469 .data_snapshot(metadata.data_shard, as_of)
1470 .await;
1471 SnapshotStatsAsOf::Txns(data_snapshot)
1472 }
1473 };
1474 self.snapshot_stats_inner(id, as_of).await
1475 }
1476
1477 async fn snapshot_parts_stats(
1478 &self,
1479 id: GlobalId,
1480 as_of: Antichain<Timestamp>,
1481 ) -> BoxFuture<'static, Result<SnapshotPartsStats, StorageError>> {
1482 let metadata = {
1483 let self_collections = self.collections.lock().expect("lock poisoned");
1484
1485 let collection_metadata = self_collections
1486 .get(&id)
1487 .ok_or(StorageError::IdentifierMissing(id))
1488 .map(|c| c.collection_metadata.clone());
1489
1490 match collection_metadata {
1491 Ok(m) => m,
1492 Err(e) => return Box::pin(async move { Err(e) }),
1493 }
1494 };
1495
1496 let persist = Arc::clone(&self.persist);
1499 let read_handle = Self::read_handle_for_snapshot(persist, &metadata, id).await;
1500
1501 let data_snapshot = match (metadata, as_of.as_option()) {
1502 (
1503 CollectionMetadata {
1504 txns_shard: Some(txns_id),
1505 data_shard,
1506 ..
1507 },
1508 Some(as_of),
1509 ) => {
1510 assert_eq!(txns_id, *self.txns_read.txns_id());
1511 self.txns_read.update_gt(*as_of).await;
1512 let data_snapshot = self.txns_read.data_snapshot(data_shard, *as_of).await;
1513 Some(data_snapshot)
1514 }
1515 _ => None,
1516 };
1517
1518 Box::pin(async move {
1519 let read_handle = read_handle?;
1520 let result = match data_snapshot {
1521 Some(data_snapshot) => data_snapshot.snapshot_parts_stats(&read_handle).await,
1522 None => read_handle.snapshot_parts_stats(as_of).await,
1523 };
1524 read_handle.expire().await;
1525 result.map_err(|_| StorageError::ReadBeforeSince(id))
1526 })
1527 }
1528
1529 fn snapshot(
1530 &self,
1531 id: GlobalId,
1532 as_of: Timestamp,
1533 ) -> BoxFuture<'static, Result<Vec<(Row, StorageDiff)>, StorageError>> {
1534 self.snapshot(id, as_of, &self.txns_read)
1535 }
1536
1537 async fn snapshot_latest(&self, id: GlobalId) -> Result<Vec<Row>, StorageError> {
1538 let upper = self.recent_upper(id).await?;
1539 let res = match upper.as_option() {
1540 Some(f) if f > &Timestamp::MIN => {
1541 let as_of = f.step_back().expect("checked that f > &Timestamp::MIN");
1542
1543 let snapshot = self.snapshot(id, as_of, &self.txns_read).await?;
1544 snapshot
1545 .into_iter()
1546 .map(|(row, diff)| {
1547 assert_eq!(diff, 1, "snapshot doesn't accumulate to set");
1550 row
1551 })
1552 .collect()
1553 }
1554 Some(_min) => {
1555 Vec::new()
1557 }
1558 _ => {
1561 return Err(StorageError::InvalidUsage(
1562 "collection closed, cannot determine a read timestamp based on the upper"
1563 .to_string(),
1564 ));
1565 }
1566 };
1567
1568 Ok(res)
1569 }
1570
1571 fn snapshot_cursor(
1572 &self,
1573 id: GlobalId,
1574 as_of: Timestamp,
1575 ) -> BoxFuture<'static, Result<SnapshotCursor, StorageError>> {
1576 let metadata = match self.collection_metadata(id) {
1577 Ok(metadata) => metadata.clone(),
1578 Err(e) => return async { Err(e.into()) }.boxed(),
1579 };
1580 let txns_read = metadata.txns_shard.as_ref().map(|txns_id| {
1581 assert_eq!(txns_id, self.txns_read.txns_id());
1584 self.txns_read.clone()
1585 });
1586 let persist = Arc::clone(&self.persist);
1587
1588 async move {
1590 let mut handle = Self::read_handle_for_snapshot(persist, &metadata, id).await?;
1591 let cursor = match txns_read {
1592 None => {
1593 let cursor = handle
1594 .snapshot_cursor(Antichain::from_elem(as_of), |_| true)
1595 .await
1596 .map_err(|_| StorageError::ReadBeforeSince(id))?;
1597 SnapshotCursor {
1598 _read_handle: handle,
1599 cursor,
1600 }
1601 }
1602 Some(txns_read) => {
1603 txns_read.update_gt(as_of).await;
1604 let data_snapshot = txns_read.data_snapshot(metadata.data_shard, as_of).await;
1605 let cursor = data_snapshot
1606 .snapshot_cursor(&mut handle, |_| true)
1607 .await
1608 .map_err(|_| StorageError::ReadBeforeSince(id))?;
1609 SnapshotCursor {
1610 _read_handle: handle,
1611 cursor,
1612 }
1613 }
1614 };
1615
1616 Ok(cursor)
1617 }
1618 .boxed()
1619 }
1620
1621 fn snapshot_and_stream(
1622 &self,
1623 id: GlobalId,
1624 as_of: Timestamp,
1625 ) -> BoxFuture<
1626 'static,
1627 Result<BoxStream<'static, (SourceData, Timestamp, StorageDiff)>, StorageError>,
1628 > {
1629 self.snapshot_and_stream(id, as_of, &self.txns_read)
1630 }
1631
1632 fn create_update_builder(
1633 &self,
1634 id: GlobalId,
1635 ) -> BoxFuture<
1636 'static,
1637 Result<TimestamplessUpdateBuilder<SourceData, (), StorageDiff>, StorageError>,
1638 > {
1639 let metadata = match self.collection_metadata(id) {
1640 Ok(m) => m,
1641 Err(e) => return Box::pin(async move { Err(e.into()) }),
1642 };
1643 let persist = Arc::clone(&self.persist);
1644
1645 async move {
1646 let persist_client = persist
1647 .open(metadata.persist_location.clone())
1648 .await
1649 .expect("invalid persist usage");
1650 let write_handle = persist_client
1651 .open_writer::<SourceData, (), Timestamp, StorageDiff>(
1652 metadata.data_shard,
1653 Arc::new(metadata.relation_desc.clone()),
1654 Arc::new(UnitSchema),
1655 Diagnostics {
1656 shard_name: id.to_string(),
1657 handle_purpose: format!("create write batch {}", id),
1658 },
1659 )
1660 .await
1661 .expect("invalid persist usage");
1662 let builder = TimestamplessUpdateBuilder::new(&write_handle);
1663
1664 Ok(builder)
1665 }
1666 .boxed()
1667 }
1668
1669 fn check_exists(&self, id: GlobalId) -> Result<(), StorageError> {
1670 let collections = self.collections.lock().expect("lock poisoned");
1671
1672 if collections.contains_key(&id) {
1673 Ok(())
1674 } else {
1675 Err(StorageError::IdentifierMissing(id))
1676 }
1677 }
1678
1679 async fn prepare_state(
1680 &self,
1681 txn: &mut (dyn StorageTxn + Send),
1682 ids_to_add: BTreeSet<GlobalId>,
1683 ids_to_drop: BTreeSet<GlobalId>,
1684 ids_to_register: BTreeMap<GlobalId, ShardId>,
1685 ) -> Result<(), StorageError> {
1686 let mut active_collection_ids: BTreeSet<_> = {
1689 let collections = self.collections.lock().expect("poisoned");
1690 collections
1691 .iter()
1692 .filter_map(|(id, collection)| {
1693 (!ids_to_drop.contains(id) && !collection.is_dropped()).then_some(*id)
1694 })
1695 .collect()
1696 };
1697 active_collection_ids.extend(ids_to_add.iter().copied());
1698 active_collection_ids.extend(ids_to_register.keys().copied());
1699
1700 txn.insert_collection_metadata(
1701 ids_to_add
1702 .into_iter()
1703 .map(|id| (id, ShardId::new()))
1704 .collect(),
1705 )?;
1706 txn.insert_collection_metadata(ids_to_register)?;
1707
1708 let dropped_mappings = txn.delete_collection_metadata(ids_to_drop);
1710
1711 let mut dropped_shards = BTreeSet::new();
1714 {
1715 let collections = self.collections.lock().expect("poisoned");
1716 for (id, shard) in dropped_mappings {
1717 let coll = collections.get(&id).expect("must exist");
1718 if coll.primary.is_none() {
1719 dropped_shards.insert(shard);
1720 }
1721 }
1722 }
1723 let remaining_metadata = txn.get_collection_metadata();
1724 let (referenced_shards, dropped_shards) = partition_finalizable_shards(
1725 remaining_metadata,
1726 &active_collection_ids,
1727 dropped_shards,
1728 );
1729 if !referenced_shards.is_empty() {
1730 mz_ore::soft_panic_or_log!(
1731 "dropped collections would finalize shards that active collections still use: \
1732 {referenced_shards:?}"
1733 );
1734 }
1735 txn.insert_unfinalized_shards(dropped_shards)?;
1736
1737 let finalized_shards = self.finalized_shards.lock().iter().copied().collect();
1740 txn.remove_unfinalized_shards(finalized_shards);
1741
1742 Ok(())
1743 }
1744
1745 #[instrument(level = "debug")]
1748 async fn create_collections_for_bootstrap(
1749 &self,
1750 storage_metadata: &StorageMetadata,
1751 register_ts: Option<Timestamp>,
1752 mut collections: Vec<(GlobalId, CollectionDescription)>,
1753 migrated_storage_collections: &BTreeSet<GlobalId>,
1754 ) -> Result<(), StorageError> {
1755 let is_in_txns = |id, metadata: &CollectionMetadata| {
1756 metadata.txns_shard.is_some()
1757 && !(self.read_only && migrated_storage_collections.contains(&id))
1758 };
1759
1760 collections.sort_by_key(|(id, _)| *id);
1765 collections.dedup();
1766 for pos in 1..collections.len() {
1767 if collections[pos - 1].0 == collections[pos].0 {
1768 return Err(StorageError::CollectionIdReused(collections[pos].0));
1769 }
1770 }
1771
1772 let enriched_with_metadata = collections
1775 .into_iter()
1776 .map(|(id, description)| {
1777 let data_shard = storage_metadata.get_collection_shard(id)?;
1778
1779 let txns_shard = description
1783 .data_source
1784 .in_txns()
1785 .then(|| *self.txns_read.txns_id());
1786
1787 let metadata = CollectionMetadata {
1788 persist_location: self.persist_location.clone(),
1789 data_shard,
1790 relation_desc: description.desc.clone(),
1791 txns_shard,
1792 };
1793
1794 Ok((id, description, metadata))
1795 })
1796 .collect_vec();
1797
1798 let persist_client = self
1800 .persist
1801 .open(self.persist_location.clone())
1802 .await
1803 .unwrap();
1804 let persist_client = &persist_client;
1805 use futures::stream::{StreamExt, TryStreamExt};
1808 let this = &*self;
1809 let mut to_register: Vec<_> = futures::stream::iter(enriched_with_metadata)
1810 .map(|data: Result<_, StorageError>| {
1811 async move {
1812 let (id, description, metadata) = data?;
1813
1814 debug!("mapping GlobalId={} to shard ({})", id, metadata.data_shard);
1819
1820 let since = if description.primary.is_some() {
1824 None
1825 } else {
1826 description.since.as_ref()
1827 };
1828
1829 let (write, mut since_handle) = this
1830 .open_data_handles(
1831 &id,
1832 metadata.data_shard,
1833 since,
1834 metadata.relation_desc.clone(),
1835 persist_client,
1836 )
1837 .await;
1838
1839 match description.data_source {
1848 DataSource::Introspection(_)
1849 | DataSource::IngestionExport { .. }
1850 | DataSource::Webhook
1851 | DataSource::Ingestion(_)
1852 | DataSource::Progress
1853 | DataSource::Other => {}
1854 DataSource::Sink { .. } => {}
1855 DataSource::Table => {
1856 let register_ts = register_ts.expect(
1857 "caller should have provided a register_ts when creating a table",
1858 );
1859 if since_handle.since().elements() == &[Timestamp::MIN]
1860 && !migrated_storage_collections.contains(&id)
1861 {
1862 debug!("advancing {} to initial since of {:?}", id, register_ts);
1863 let token = since_handle.opaque();
1864 let _ = since_handle
1865 .compare_and_downgrade_since(
1866 &token,
1867 (&token, &Antichain::from_elem(register_ts)),
1868 )
1869 .await;
1870 }
1871 }
1872 }
1873
1874 Ok::<_, StorageError>((id, description, write, since_handle, metadata))
1875 }
1876 })
1877 .buffer_unordered(50)
1879 .try_collect()
1893 .await?;
1894
1895 #[derive(Ord, PartialOrd, Eq, PartialEq)]
1897 enum DependencyOrder {
1898 Table(Reverse<GlobalId>),
1900 Collection(GlobalId),
1902 Sink(GlobalId),
1904 }
1905 to_register.sort_by_key(|(id, desc, ..)| match &desc.data_source {
1906 DataSource::Table => DependencyOrder::Table(Reverse(*id)),
1907 DataSource::Sink { .. } => DependencyOrder::Sink(*id),
1908 _ => DependencyOrder::Collection(*id),
1909 });
1910
1911 let mut self_collections = self.collections.lock().expect("lock poisoned");
1914
1915 for (id, description, write_handle, since_handle, metadata) in to_register {
1916 let write_frontier = write_handle.upper();
1917 let data_shard_since = since_handle.since().clone();
1918
1919 let storage_dependencies =
1921 Self::determine_collection_dependencies(&*self_collections, id, &description)?;
1922
1923 let initial_since = match storage_dependencies
1925 .iter()
1926 .at_most_one()
1927 .expect("should have at most one dependency")
1928 {
1929 Some(dep) => {
1930 let dependency_collection = self_collections
1931 .get(dep)
1932 .ok_or(StorageError::IdentifierMissing(*dep))?;
1933 let dependency_since = dependency_collection.implied_capability.clone();
1934
1935 if PartialOrder::less_than(&data_shard_since, &dependency_since) {
1946 if description.primary.is_none() {
1968 mz_ore::soft_assert_or_log!(
1969 write_frontier.elements() == &[Timestamp::MIN]
1970 || write_frontier.is_empty()
1971 || PartialOrder::less_than(&dependency_since, write_frontier),
1972 "dependency ({dep}) since has advanced past dependent ({id}) upper \n
1973 dependent ({id}): since {:?}, upper {:?} \n
1974 dependency ({dep}): since {:?}",
1975 data_shard_since,
1976 write_frontier,
1977 dependency_since
1978 );
1979 }
1980
1981 dependency_since
1982 } else {
1983 data_shard_since
1984 }
1985 }
1986 None => data_shard_since,
1987 };
1988
1989 let time_dependence = {
1991 use DataSource::*;
1992 if let Some(timeline) = &description.timeline
1993 && *timeline != Timeline::EpochMilliseconds
1994 {
1995 None
1997 } else {
1998 match &description.data_source {
1999 Ingestion(ingestion) => {
2000 use GenericSourceConnection::*;
2001 match ingestion.desc.connection {
2002 Kafka(_) | Postgres(_) | MySql(_) | SqlServer(_) => {
2005 Some(TimeDependence::default())
2006 }
2007 LoadGenerator(_) => None,
2009 }
2010 }
2011 IngestionExport { ingestion_id, .. } => {
2012 let c = self_collections.get(ingestion_id).expect("known to exist");
2013 c.time_dependence.clone()
2014 }
2015 Introspection(_) | Progress | Table { .. } | Webhook { .. } => {
2017 Some(TimeDependence::default())
2018 }
2019 Other => None,
2021 Sink { .. } => None,
2022 }
2023 }
2024 };
2025
2026 let ingestion_remap_collection_id = match &description.data_source {
2027 DataSource::Ingestion(desc) => Some(desc.remap_collection_id),
2028 _ => None,
2029 };
2030
2031 let mut collection_state = CollectionState::new(
2032 description.primary,
2033 time_dependence,
2034 ingestion_remap_collection_id,
2035 initial_since,
2036 write_frontier.clone(),
2037 storage_dependencies,
2038 metadata.clone(),
2039 );
2040
2041 match &description.data_source {
2043 DataSource::Introspection(_) => {
2044 self_collections.insert(id, collection_state);
2045 }
2046 DataSource::Webhook => {
2047 self_collections.insert(id, collection_state);
2048 }
2049 DataSource::IngestionExport { .. } => {
2050 self_collections.insert(id, collection_state);
2051 }
2052 DataSource::Table => {
2053 if is_in_txns(id, &metadata)
2056 && PartialOrder::less_than(
2057 &collection_state.write_frontier,
2058 &self.initial_txn_upper,
2059 )
2060 {
2061 collection_state
2067 .write_frontier
2068 .clone_from(&self.initial_txn_upper);
2069 }
2070 self_collections.insert(id, collection_state);
2071 }
2072 DataSource::Progress | DataSource::Other => {
2073 self_collections.insert(id, collection_state);
2074 }
2075 DataSource::Ingestion(_) => {
2076 self_collections.insert(id, collection_state);
2077 }
2078 DataSource::Sink { .. } => {
2079 self_collections.insert(id, collection_state);
2080 }
2081 }
2082
2083 self.register_handles(id, is_in_txns(id, &metadata), since_handle, write_handle);
2084
2085 self.install_collection_dependency_read_holds_inner(&mut *self_collections, id)?;
2087 }
2088
2089 drop(self_collections);
2090
2091 self.synchronize_finalized_shards(storage_metadata);
2092
2093 Ok(())
2094 }
2095
2096 async fn alter_table_desc(
2097 &self,
2098 existing_collection: GlobalId,
2099 new_collection: GlobalId,
2100 new_desc: RelationDesc,
2101 expected_version: RelationVersion,
2102 ) -> Result<(), StorageError> {
2103 let data_shard = {
2104 let self_collections = self.collections.lock().expect("lock poisoned");
2105 let existing = self_collections
2106 .get(&existing_collection)
2107 .ok_or_else(|| StorageError::IdentifierMissing(existing_collection))?;
2108
2109 existing.collection_metadata.data_shard
2110 };
2111
2112 let persist_client = self
2113 .persist
2114 .open(self.persist_location.clone())
2115 .await
2116 .unwrap();
2117
2118 let diagnostics = Diagnostics {
2120 shard_name: existing_collection.to_string(),
2121 handle_purpose: "alter_table_desc".to_string(),
2122 };
2123 let expected_schema = expected_version.into();
2125 let schema_result = persist_client
2126 .compare_and_evolve_schema::<SourceData, (), Timestamp, StorageDiff>(
2127 data_shard,
2128 expected_schema,
2129 &new_desc,
2130 &UnitSchema,
2131 diagnostics,
2132 )
2133 .await
2134 .map_err(|e| StorageError::InvalidUsage(e.to_string()))?;
2135 tracing::info!(
2136 ?existing_collection,
2137 ?new_collection,
2138 ?new_desc,
2139 "evolved schema"
2140 );
2141
2142 match schema_result {
2143 CaESchema::Ok(_id) => {}
2145 CaESchema::ExpectedMismatch {
2150 schema_id,
2151 key,
2152 val,
2153 } => {
2154 mz_ore::soft_panic_or_log!(
2155 "schema expectation mismatch, expected {expected_schema:?} \
2156 but found {schema_id:?}, {key:?}, {val:?}"
2157 );
2158 return Err(StorageError::Generic(anyhow::anyhow!(
2159 "schema expected mismatch, {existing_collection:?}",
2160 )));
2161 }
2162 CaESchema::Incompatible => {
2163 mz_ore::soft_panic_or_log!(
2164 "incompatible schema! {existing_collection} {new_desc:?}"
2165 );
2166 return Err(StorageError::Generic(anyhow::anyhow!(
2167 "schema incompatible, {existing_collection:?}"
2168 )));
2169 }
2170 };
2171
2172 let (write_handle, since_handle) = self
2174 .open_data_handles(
2175 &new_collection,
2176 data_shard,
2177 None,
2178 new_desc.clone(),
2179 &persist_client,
2180 )
2181 .await;
2182
2183 {
2189 let mut self_collections = self.collections.lock().expect("lock poisoned");
2190
2191 let existing = self_collections
2193 .get_mut(&existing_collection)
2194 .expect("existing collection missing");
2195
2196 assert_none!(existing.primary);
2198
2199 existing.primary = Some(new_collection);
2201 existing.storage_dependencies.push(new_collection);
2202
2203 let implied_capability = existing.read_capabilities.frontier().to_owned();
2207 let write_frontier = existing.write_frontier.clone();
2208
2209 let mut changes = ChangeBatch::new();
2216 changes.extend(implied_capability.iter().map(|t| (*t, 1)));
2217
2218 let collection_meta = CollectionMetadata {
2220 persist_location: self.persist_location.clone(),
2221 relation_desc: new_desc.clone(),
2222 data_shard,
2223 txns_shard: Some(self.txns_read.txns_id().clone()),
2224 };
2225 let collection_state = CollectionState::new(
2226 None,
2227 existing.time_dependence.clone(),
2228 existing.ingestion_remap_collection_id.clone(),
2229 implied_capability,
2230 write_frontier,
2231 Vec::new(),
2232 collection_meta,
2233 );
2234
2235 self_collections.insert(new_collection, collection_state);
2237
2238 let mut updates = BTreeMap::from([(new_collection, changes)]);
2239 StorageCollectionsImpl::update_read_capabilities_inner(
2240 &self.cmd_tx,
2241 &mut *self_collections,
2242 &mut updates,
2243 );
2244 };
2245
2246 self.register_handles(new_collection, true, since_handle, write_handle);
2248
2249 info!(%existing_collection, %new_collection, ?new_desc, "altered table");
2250
2251 Ok(())
2252 }
2253
2254 fn drop_collections_unvalidated(
2255 &self,
2256 storage_metadata: &StorageMetadata,
2257 identifiers: Vec<GlobalId>,
2258 ) {
2259 debug!(?identifiers, "drop_collections_unvalidated");
2260
2261 let mut self_collections = self.collections.lock().expect("lock poisoned");
2262 let dropping: BTreeSet<_> = identifiers.iter().copied().collect();
2265 let active_collection_ids: BTreeSet<_> = self_collections
2266 .iter()
2267 .filter_map(|(id, collection)| {
2268 (!dropping.contains(id) && !collection.is_dropped()).then_some(*id)
2269 })
2270 .collect();
2271 let shards_in_use: BTreeSet<_> = storage_metadata
2272 .collection_metadata
2273 .iter()
2274 .filter_map(|(id, shard)| active_collection_ids.contains(id).then_some(*shard))
2275 .collect();
2276
2277 let mut finalized_policies = Vec::new();
2285
2286 for id in identifiers {
2287 let Some(collection) = self_collections.get(&id) else {
2289 continue;
2290 };
2291
2292 if collection.primary.is_none() {
2295 let metadata = storage_metadata.get_collection_shard(id);
2296 mz_ore::soft_assert_or_log!(
2297 matches!(metadata, Err(StorageError::IdentifierMissing(_))),
2298 "dropping {id}, but drop was not synchronized with storage \
2299 controller via `prepare_state`"
2300 );
2301
2302 let data_shard = collection.collection_metadata.data_shard;
2306 if shards_in_use.contains(&data_shard) {
2307 mz_ore::soft_panic_or_log!(
2308 "dropping {id} would release the since of shard {data_shard}, \
2309 which an active collection still uses"
2310 );
2311 continue;
2312 }
2313 }
2314
2315 finalized_policies.push((id, ReadPolicy::ValidFrom(Antichain::new())));
2316 }
2317
2318 self.set_read_policies_inner(&mut self_collections, finalized_policies);
2319
2320 drop(self_collections);
2321
2322 self.synchronize_finalized_shards(storage_metadata);
2323 }
2324
2325 fn set_read_policies(&self, policies: Vec<(GlobalId, ReadPolicy)>) {
2326 let mut collections = self.collections.lock().expect("lock poisoned");
2327
2328 if tracing::enabled!(tracing::Level::TRACE) {
2329 let user_capabilities = collections
2330 .iter_mut()
2331 .filter(|(id, _c)| id.is_user())
2332 .map(|(id, c)| {
2333 let updates = c.read_capabilities.updates().cloned().collect_vec();
2334 (*id, c.implied_capability.clone(), updates)
2335 })
2336 .collect_vec();
2337
2338 trace!(?policies, ?user_capabilities, "set_read_policies");
2339 }
2340
2341 self.set_read_policies_inner(&mut collections, policies);
2342
2343 if tracing::enabled!(tracing::Level::TRACE) {
2344 let user_capabilities = collections
2345 .iter_mut()
2346 .filter(|(id, _c)| id.is_user())
2347 .map(|(id, c)| {
2348 let updates = c.read_capabilities.updates().cloned().collect_vec();
2349 (*id, c.implied_capability.clone(), updates)
2350 })
2351 .collect_vec();
2352
2353 trace!(?user_capabilities, "after! set_read_policies");
2354 }
2355 }
2356
2357 fn acquire_read_holds(
2358 &self,
2359 desired_holds: Vec<GlobalId>,
2360 ) -> Result<Vec<ReadHold>, CollectionMissing> {
2361 if desired_holds.is_empty() {
2362 return Ok(vec![]);
2363 }
2364
2365 let mut collections = self.collections.lock().expect("lock poisoned");
2366
2367 let mut advanced_holds = Vec::new();
2368 for id in desired_holds.iter() {
2379 let collection = collections.get(id).ok_or(CollectionMissing(*id))?;
2380 let since = collection.read_capabilities.frontier().to_owned();
2381 advanced_holds.push((*id, since));
2382 }
2383
2384 let mut updates = advanced_holds
2385 .iter()
2386 .map(|(id, hold)| {
2387 let mut changes = ChangeBatch::new();
2388 changes.extend(hold.iter().map(|time| (*time, 1)));
2389 (*id, changes)
2390 })
2391 .collect::<BTreeMap<_, _>>();
2392
2393 StorageCollectionsImpl::update_read_capabilities_inner(
2394 &self.cmd_tx,
2395 &mut collections,
2396 &mut updates,
2397 );
2398
2399 let acquired_holds = advanced_holds
2400 .into_iter()
2401 .map(|(id, since)| ReadHold::with_channel(id, since, self.holds_tx.clone()))
2402 .collect_vec();
2403
2404 trace!(?desired_holds, ?acquired_holds, "acquire_read_holds");
2405
2406 Ok(acquired_holds)
2407 }
2408
2409 fn determine_time_dependence(
2411 &self,
2412 id: GlobalId,
2413 ) -> Result<Option<TimeDependence>, TimeDependenceError> {
2414 use TimeDependenceError::CollectionMissing;
2415 let collections = self.collections.lock().expect("lock poisoned");
2416 let state = collections.get(&id).ok_or(CollectionMissing(id))?;
2417 Ok(state.time_dependence.clone())
2418 }
2419
2420 fn dump(&self) -> Result<serde_json::Value, anyhow::Error> {
2421 let Self {
2423 envd_epoch,
2424 read_only,
2425 finalizable_shards,
2426 finalized_shards,
2427 collections,
2428 txns_read: _,
2429 config,
2430 initial_txn_upper,
2431 persist_location,
2432 persist: _,
2433 cmd_tx: _,
2434 holds_tx: _,
2435 _background_task: _,
2436 _finalize_shards_task: _,
2437 } = self;
2438
2439 let finalizable_shards: Vec<_> = finalizable_shards
2440 .lock()
2441 .iter()
2442 .map(ToString::to_string)
2443 .collect();
2444 let finalized_shards: Vec<_> = finalized_shards
2445 .lock()
2446 .iter()
2447 .map(ToString::to_string)
2448 .collect();
2449 let collections: BTreeMap<_, _> = collections
2450 .lock()
2451 .expect("poisoned")
2452 .iter()
2453 .map(|(id, c)| (id.to_string(), format!("{c:?}")))
2454 .collect();
2455 let config = format!("{:?}", config.lock().expect("poisoned"));
2456
2457 Ok(serde_json::json!({
2458 "envd_epoch": envd_epoch,
2459 "read_only": read_only,
2460 "finalizable_shards": finalizable_shards,
2461 "finalized_shards": finalized_shards,
2462 "collections": collections,
2463 "config": config,
2464 "initial_txn_upper": initial_txn_upper,
2465 "persist_location": format!("{persist_location:?}"),
2466 }))
2467 }
2468}
2469
2470#[derive(Debug)]
2477enum SinceHandleWrapper {
2478 Critical(SinceHandle<SourceData, (), Timestamp, StorageDiff>),
2479 Leased(ReadHandle<SourceData, (), Timestamp, StorageDiff>),
2480}
2481
2482impl SinceHandleWrapper {
2483 pub fn since(&self) -> &Antichain<Timestamp> {
2484 match self {
2485 Self::Critical(handle) => handle.since(),
2486 Self::Leased(handle) => handle.since(),
2487 }
2488 }
2489
2490 pub fn opaque(&self) -> PersistEpoch {
2491 match self {
2492 Self::Critical(handle) => handle.opaque().decode(),
2493 Self::Leased(_handle) => {
2494 PersistEpoch(None)
2499 }
2500 }
2501 }
2502
2503 pub async fn compare_and_downgrade_since(
2504 &mut self,
2505 expected: &PersistEpoch,
2506 (opaque, since): (&PersistEpoch, &Antichain<Timestamp>),
2507 ) -> Result<Antichain<Timestamp>, PersistEpoch> {
2508 match self {
2509 Self::Critical(handle) => handle
2510 .compare_and_downgrade_since(
2511 &Opaque::encode(expected),
2512 (&Opaque::encode(opaque), since),
2513 )
2514 .await
2515 .map_err(|e| e.decode()),
2516 Self::Leased(handle) => {
2517 assert_none!(opaque.0);
2518
2519 handle.downgrade_since(since).await;
2520
2521 Ok(since.clone())
2522 }
2523 }
2524 }
2525
2526 pub async fn maybe_compare_and_downgrade_since(
2527 &mut self,
2528 expected: &PersistEpoch,
2529 (opaque, since): (&PersistEpoch, &Antichain<Timestamp>),
2530 ) -> Option<Result<Antichain<Timestamp>, PersistEpoch>> {
2531 match self {
2532 Self::Critical(handle) => handle
2533 .maybe_compare_and_downgrade_since(
2534 &Opaque::encode(expected),
2535 (&Opaque::encode(opaque), since),
2536 )
2537 .await
2538 .map(|r| r.map_err(|o| o.decode())),
2539 Self::Leased(handle) => {
2540 assert_none!(opaque.0);
2541
2542 handle.maybe_downgrade_since(since).await;
2543
2544 Some(Ok(since.clone()))
2545 }
2546 }
2547 }
2548
2549 pub fn snapshot_stats(
2550 &self,
2551 id: GlobalId,
2552 as_of: Option<Antichain<Timestamp>>,
2553 ) -> BoxFuture<'static, Result<SnapshotStats, StorageError>> {
2554 match self {
2555 Self::Critical(handle) => {
2556 let res = handle
2557 .snapshot_stats(as_of)
2558 .map(move |x| x.map_err(|_| StorageError::ReadBeforeSince(id)));
2559 Box::pin(res)
2560 }
2561 Self::Leased(handle) => {
2562 let res = handle
2563 .snapshot_stats(as_of)
2564 .map(move |x| x.map_err(|_| StorageError::ReadBeforeSince(id)));
2565 Box::pin(res)
2566 }
2567 }
2568 }
2569
2570 pub fn snapshot_stats_from_txn(
2571 &self,
2572 id: GlobalId,
2573 data_snapshot: DataSnapshot<Timestamp>,
2574 ) -> BoxFuture<'static, Result<SnapshotStats, StorageError>> {
2575 match self {
2576 Self::Critical(handle) => Box::pin(
2577 data_snapshot
2578 .snapshot_stats_from_critical(handle)
2579 .map(move |x| x.map_err(|_| StorageError::ReadBeforeSince(id))),
2580 ),
2581 Self::Leased(handle) => Box::pin(
2582 data_snapshot
2583 .snapshot_stats_from_leased(handle)
2584 .map(move |x| x.map_err(|_| StorageError::ReadBeforeSince(id))),
2585 ),
2586 }
2587 }
2588}
2589
2590#[derive(Debug, Clone)]
2592struct CollectionState {
2593 primary: Option<GlobalId>,
2600
2601 time_dependence: Option<TimeDependence>,
2603 ingestion_remap_collection_id: Option<GlobalId>,
2605
2606 pub read_capabilities: MutableAntichain<Timestamp>,
2612
2613 pub implied_capability: Antichain<Timestamp>,
2617
2618 pub read_policy: ReadPolicy,
2620
2621 pub storage_dependencies: Vec<GlobalId>,
2623
2624 pub write_frontier: Antichain<Timestamp>,
2626
2627 pub collection_metadata: CollectionMetadata,
2628}
2629
2630impl CollectionState {
2631 pub fn new(
2634 primary: Option<GlobalId>,
2635 time_dependence: Option<TimeDependence>,
2636 ingestion_remap_collection_id: Option<GlobalId>,
2637 since: Antichain<Timestamp>,
2638 write_frontier: Antichain<Timestamp>,
2639 storage_dependencies: Vec<GlobalId>,
2640 metadata: CollectionMetadata,
2641 ) -> Self {
2642 let mut read_capabilities = MutableAntichain::new();
2643 read_capabilities.update_iter(since.iter().map(|time| (*time, 1)));
2644 Self {
2645 primary,
2646 time_dependence,
2647 ingestion_remap_collection_id,
2648 read_capabilities,
2649 implied_capability: since.clone(),
2650 read_policy: ReadPolicy::NoPolicy {
2651 initial_since: since,
2652 },
2653 storage_dependencies,
2654 write_frontier,
2655 collection_metadata: metadata,
2656 }
2657 }
2658
2659 pub fn is_dropped(&self) -> bool {
2661 self.read_capabilities.is_empty()
2662 }
2663}
2664
2665#[derive(Debug)]
2671struct BackgroundTask {
2672 config: Arc<Mutex<StorageConfiguration>>,
2673 cmds_tx: mpsc::UnboundedSender<BackgroundCmd>,
2674 cmds_rx: mpsc::UnboundedReceiver<BackgroundCmd>,
2675 holds_rx: mpsc::UnboundedReceiver<(GlobalId, ChangeBatch<Timestamp>)>,
2676 finalizable_shards: Arc<ShardIdSet>,
2677 collections: Arc<std::sync::Mutex<BTreeMap<GlobalId, CollectionState>>>,
2678 shard_by_id: BTreeMap<GlobalId, ShardId>,
2681 since_handles: BTreeMap<GlobalId, SinceHandleWrapper>,
2682 txns_handle: Option<WriteHandle<SourceData, (), Timestamp, StorageDiff>>,
2683 txns_shards: BTreeSet<GlobalId>,
2684}
2685
2686#[derive(Debug)]
2687enum BackgroundCmd {
2688 Register {
2689 id: GlobalId,
2690 is_in_txns: bool,
2691 write_handle: WriteHandle<SourceData, (), Timestamp, StorageDiff>,
2692 since_handle: SinceHandleWrapper,
2693 },
2694 DowngradeSince(Vec<(GlobalId, Antichain<Timestamp>)>),
2695 SnapshotStats(
2696 GlobalId,
2697 SnapshotStatsAsOf,
2698 oneshot::Sender<SnapshotStatsRes>,
2699 ),
2700}
2701
2702pub(crate) struct SnapshotStatsRes(BoxFuture<'static, Result<SnapshotStats, StorageError>>);
2704
2705impl Debug for SnapshotStatsRes {
2706 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2707 f.debug_struct("SnapshotStatsRes").finish_non_exhaustive()
2708 }
2709}
2710
2711impl BackgroundTask {
2712 async fn run(&mut self) {
2713 let mut upper_futures: FuturesUnordered<
2715 std::pin::Pin<
2716 Box<
2717 dyn Future<
2718 Output = (
2719 GlobalId,
2720 WriteHandle<SourceData, (), Timestamp, StorageDiff>,
2721 Antichain<Timestamp>,
2722 ),
2723 > + Send,
2724 >,
2725 >,
2726 > = FuturesUnordered::new();
2727
2728 let gen_upper_future =
2729 |id, mut handle: WriteHandle<_, _, _, _>, prev_upper: Antichain<Timestamp>| {
2730 let fut = async move {
2731 soft_assert_or_log!(
2732 !prev_upper.is_empty(),
2733 "cannot await progress when upper is already empty"
2734 );
2735 handle.wait_for_upper_past(&prev_upper).await;
2736 let new_upper = handle.shared_upper();
2737 (id, handle, new_upper)
2738 };
2739
2740 fut
2741 };
2742
2743 let mut txns_upper_future = match self.txns_handle.take() {
2744 Some(txns_handle) => {
2745 let upper = txns_handle.upper().clone();
2746 let txns_upper_future =
2747 gen_upper_future(GlobalId::Transient(1), txns_handle, upper);
2748 txns_upper_future.boxed()
2749 }
2750 None => async { std::future::pending().await }.boxed(),
2751 };
2752
2753 loop {
2754 tokio::select! {
2755 (id, handle, upper) = &mut txns_upper_future => {
2756 trace!("new upper from txns shard: {:?}", upper);
2757 let mut uppers = Vec::new();
2758 for id in self.txns_shards.iter() {
2759 uppers.push((*id, &upper));
2760 }
2761 self.update_write_frontiers(&uppers).await;
2762
2763 let fut = gen_upper_future(id, handle, upper);
2764 txns_upper_future = fut.boxed();
2765 }
2766 Some((id, handle, upper)) = upper_futures.next() => {
2767 if id.is_user() {
2768 trace!("new upper for collection {id}: {:?}", upper);
2769 }
2770 let current_shard = self.shard_by_id.get(&id);
2771 if let Some(shard_id) = current_shard {
2772 if shard_id == &handle.shard_id() {
2773 let uppers = &[(id, &upper)];
2776 self.update_write_frontiers(uppers).await;
2777 if !upper.is_empty() {
2778 let fut = gen_upper_future(id, handle, upper);
2779 upper_futures.push(fut.boxed());
2780 }
2781 } else {
2782 handle.expire().await;
2786 }
2787 }
2788 }
2789 cmd = self.cmds_rx.recv() => {
2790 let Some(cmd) = cmd else {
2791 break;
2793 };
2794
2795 let commands = iter::once(cmd).chain(
2799 iter::from_fn(|| self.cmds_rx.try_recv().ok())
2800 );
2801 let mut downgrades = BTreeMap::<_, Antichain<_>>::new();
2802 for cmd in commands {
2803 match cmd {
2804 BackgroundCmd::Register{
2805 id,
2806 is_in_txns,
2807 write_handle,
2808 since_handle
2809 } => {
2810 debug!("registering handles for {}", id);
2811 let previous = self.shard_by_id.insert(id, write_handle.shard_id());
2812 if previous.is_some() {
2813 panic!("already registered a WriteHandle for collection {id}");
2814 }
2815
2816 let previous = self.since_handles.insert(id, since_handle);
2817 if previous.is_some() {
2818 panic!("already registered a SinceHandle for collection {id}");
2819 }
2820
2821 if is_in_txns {
2822 self.txns_shards.insert(id);
2823 } else {
2824 let upper = write_handle.upper().clone();
2825 if !upper.is_empty() {
2826 let fut = gen_upper_future(id, write_handle, upper);
2827 upper_futures.push(fut.boxed());
2828 }
2829 }
2830 }
2831 BackgroundCmd::DowngradeSince(cmds) => {
2832 for (id, new) in cmds {
2833 downgrades.entry(id)
2834 .and_modify(|since| since.join_assign(&new))
2835 .or_insert(new);
2836 }
2837 }
2838 BackgroundCmd::SnapshotStats(id, as_of, tx) => {
2839 let res = match self.since_handles.get(&id) {
2845 Some(x) => {
2846 let fut: BoxFuture<
2847 'static,
2848 Result<SnapshotStats, StorageError>,
2849 > = match as_of {
2850 SnapshotStatsAsOf::Direct(as_of) => {
2851 x.snapshot_stats(id, Some(as_of))
2852 }
2853 SnapshotStatsAsOf::Txns(data_snapshot) => {
2854 x.snapshot_stats_from_txn(id, data_snapshot)
2855 }
2856 };
2857 SnapshotStatsRes(fut)
2858 }
2859 None => SnapshotStatsRes(Box::pin(futures::future::ready(Err(
2860 StorageError::IdentifierMissing(id),
2861 )))),
2862 };
2863 let _ = tx.send(res);
2865 }
2866 }
2867 }
2868
2869 if !downgrades.is_empty() {
2870 self.downgrade_sinces(downgrades).await;
2871 }
2872 }
2873 Some(holds_changes) = self.holds_rx.recv() => {
2874 let mut batched_changes = BTreeMap::new();
2875 batched_changes.insert(holds_changes.0, holds_changes.1);
2876
2877 while let Ok(mut holds_changes) = self.holds_rx.try_recv() {
2878 let entry = batched_changes.entry(holds_changes.0);
2879 entry
2880 .and_modify(|existing| existing.extend(holds_changes.1.drain()))
2881 .or_insert_with(|| holds_changes.1);
2882 }
2883
2884 let mut collections = self.collections.lock().expect("lock poisoned");
2885
2886 let user_changes = batched_changes
2887 .iter()
2888 .filter(|(id, _c)| id.is_user())
2889 .map(|(id, c)| {
2890 (id.clone(), c.clone())
2891 })
2892 .collect_vec();
2893
2894 if !user_changes.is_empty() {
2895 trace!(?user_changes, "applying holds changes from channel");
2896 }
2897
2898 StorageCollectionsImpl::update_read_capabilities_inner(
2899 &self.cmds_tx,
2900 &mut collections,
2901 &mut batched_changes,
2902 );
2903 }
2904 }
2905 }
2906
2907 warn!("BackgroundTask shutting down");
2908 }
2909
2910 #[instrument(level = "debug")]
2911 async fn update_write_frontiers(&self, updates: &[(GlobalId, &Antichain<Timestamp>)]) {
2912 let mut read_capability_changes = BTreeMap::default();
2913
2914 let mut self_collections = self.collections.lock().expect("lock poisoned");
2915
2916 for (id, new_upper) in updates.iter() {
2917 let collection = if let Some(c) = self_collections.get_mut(id) {
2918 c
2919 } else {
2920 trace!(
2921 "Reference to absent collection {id}, due to concurrent removal of that collection"
2922 );
2923 continue;
2924 };
2925
2926 if PartialOrder::less_than(&collection.write_frontier, *new_upper) {
2927 collection.write_frontier.clone_from(new_upper);
2928 }
2929
2930 let mut new_read_capability = collection
2931 .read_policy
2932 .frontier(collection.write_frontier.borrow());
2933
2934 if id.is_user() {
2935 trace!(
2936 %id,
2937 implied_capability = ?collection.implied_capability,
2938 policy = ?collection.read_policy,
2939 write_frontier = ?collection.write_frontier,
2940 ?new_read_capability,
2941 "update_write_frontiers");
2942 }
2943
2944 if PartialOrder::less_equal(&collection.implied_capability, &new_read_capability) {
2945 let mut update = ChangeBatch::new();
2946 update.extend(new_read_capability.iter().map(|time| (*time, 1)));
2947 std::mem::swap(&mut collection.implied_capability, &mut new_read_capability);
2948 update.extend(new_read_capability.iter().map(|time| (*time, -1)));
2949
2950 if !update.is_empty() {
2951 read_capability_changes.insert(*id, update);
2952 }
2953 }
2954 }
2955
2956 if !read_capability_changes.is_empty() {
2957 StorageCollectionsImpl::update_read_capabilities_inner(
2958 &self.cmds_tx,
2959 &mut self_collections,
2960 &mut read_capability_changes,
2961 );
2962 }
2963 }
2964
2965 async fn downgrade_sinces(&mut self, cmds: BTreeMap<GlobalId, Antichain<Timestamp>>) {
2966 let mut futures = Vec::with_capacity(cmds.len());
2968 for (id, new_since) in cmds {
2969 let Some(mut since_handle) = self.since_handles.remove(&id) else {
2972 trace!("downgrade_sinces: reference to absent collection {id}");
2974 continue;
2975 };
2976
2977 let fut = async move {
2978 if id.is_user() {
2979 trace!("downgrading since of {} to {:?}", id, new_since);
2980 }
2981
2982 let epoch = since_handle.opaque().clone();
2983 let result = if new_since.is_empty() {
2984 Some(
2988 since_handle
2989 .compare_and_downgrade_since(&epoch, (&epoch, &new_since))
2990 .await,
2991 )
2992 } else {
2993 since_handle
2994 .maybe_compare_and_downgrade_since(&epoch, (&epoch, &new_since))
2995 .await
2996 };
2997 (id, since_handle, result)
2998 };
2999 futures.push(fut);
3000 }
3001
3002 for (id, since_handle, result) in futures::future::join_all(futures).await {
3003 let new_since = match result {
3004 Some(Ok(since)) => Some(since),
3005 Some(Err(other_epoch)) => mz_ore::halt!(
3006 "fenced by envd @ {other_epoch:?}. ours = {:?}",
3007 since_handle.opaque(),
3008 ),
3009 None => None,
3010 };
3011
3012 self.since_handles.insert(id, since_handle);
3013
3014 if new_since.is_some_and(|s| s.is_empty()) {
3015 info!(%id, "removing persist handles because the since advanced to []!");
3016
3017 let _since_handle = self.since_handles.remove(&id).expect("known to exist");
3018 let Some(dropped_shard_id) = self.shard_by_id.remove(&id) else {
3019 panic!("missing GlobalId -> ShardId mapping for id {id}");
3020 };
3021
3022 self.txns_shards.remove(&id);
3027
3028 if self
3029 .config
3030 .lock()
3031 .expect("lock poisoned")
3032 .parameters
3033 .finalize_shards
3034 {
3035 info!(
3036 %id, %dropped_shard_id,
3037 "enqueuing shard finalization due to dropped collection and dropped \
3038 persist handle",
3039 );
3040 self.finalizable_shards.lock().insert(dropped_shard_id);
3041 } else {
3042 info!(
3043 "not triggering shard finalization due to dropped storage object \
3044 because enable_storage_shard_finalization parameter is false"
3045 );
3046 }
3047 }
3048 }
3049 }
3050}
3051
3052struct FinalizeShardsTaskConfig {
3053 envd_epoch: NonZeroI64,
3054 config: Arc<Mutex<StorageConfiguration>>,
3055 metrics: StorageCollectionsMetrics,
3056 finalizable_shards: Arc<ShardIdSet>,
3057 finalized_shards: Arc<ShardIdSet>,
3058 persist_location: PersistLocation,
3059 persist: Arc<PersistClientCache>,
3060 read_only: bool,
3061}
3062
3063async fn finalize_shards_task(
3064 FinalizeShardsTaskConfig {
3065 envd_epoch,
3066 config,
3067 metrics,
3068 finalizable_shards,
3069 finalized_shards,
3070 persist_location,
3071 persist,
3072 read_only,
3073 }: FinalizeShardsTaskConfig,
3074) {
3075 if read_only {
3076 info!("disabling shard finalization in read only mode");
3077 return;
3078 }
3079
3080 let mut interval = tokio::time::interval(Duration::from_secs(5));
3081 interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
3082 loop {
3083 interval.tick().await;
3084
3085 if !config
3086 .lock()
3087 .expect("lock poisoned")
3088 .parameters
3089 .finalize_shards
3090 {
3091 debug!(
3092 "not triggering shard finalization due to dropped storage object because enable_storage_shard_finalization parameter is false"
3093 );
3094 continue;
3095 }
3096
3097 let current_finalizable_shards = {
3098 finalizable_shards.lock().iter().cloned().collect_vec()
3101 };
3102
3103 if current_finalizable_shards.is_empty() {
3104 debug!("no shards to finalize");
3105 continue;
3106 }
3107
3108 debug!(?current_finalizable_shards, "attempting to finalize shards");
3109
3110 let persist_client = persist.open(persist_location.clone()).await.unwrap();
3112
3113 let metrics = &metrics;
3114 let finalizable_shards = &finalizable_shards;
3115 let finalized_shards = &finalized_shards;
3116 let persist_client = &persist_client;
3117 let diagnostics = &Diagnostics::from_purpose("finalizing shards");
3118
3119 let force_downgrade_since = STORAGE_DOWNGRADE_SINCE_DURING_FINALIZATION
3120 .get(config.lock().expect("lock poisoned").config_set());
3121
3122 let epoch = &PersistEpoch::from(envd_epoch);
3123
3124 futures::stream::iter(current_finalizable_shards.clone())
3125 .map(|shard_id| async move {
3126 let persist_client = persist_client.clone();
3127 let diagnostics = diagnostics.clone();
3128 let epoch = epoch.clone();
3129
3130 metrics.finalization_started.inc();
3131
3132 let is_finalized = persist_client
3133 .is_finalized::<SourceData, (), Timestamp, StorageDiff>(shard_id, diagnostics)
3134 .await
3135 .expect("invalid persist usage");
3136
3137 if is_finalized {
3138 debug!(%shard_id, "shard is already finalized!");
3139 Some(shard_id)
3140 } else {
3141 debug!(%shard_id, "finalizing shard");
3142 let finalize = || async move {
3143 let diagnostics = Diagnostics::from_purpose("finalizing shards");
3145
3146 let mut write_handle: WriteHandle<SourceData, (), Timestamp, StorageDiff> =
3149 persist_client
3150 .open_writer(
3151 shard_id,
3152 Arc::new(RelationDesc::empty()),
3153 Arc::new(UnitSchema),
3154 diagnostics,
3155 )
3156 .await
3157 .expect("invalid persist usage");
3158 write_handle.advance_upper(&Antichain::new()).await;
3159 write_handle.expire().await;
3160
3161 if force_downgrade_since {
3162 let our_opaque = Opaque::encode(&epoch);
3163 let mut since_handle: SinceHandle<
3164 SourceData,
3165 (),
3166 Timestamp,
3167 StorageDiff,
3168 > = persist_client
3169 .open_critical_since(
3170 shard_id,
3171 PersistClient::CONTROLLER_CRITICAL_SINCE,
3172 our_opaque.clone(),
3173 Diagnostics::from_purpose("finalizing shards"),
3174 )
3175 .await
3176 .expect("invalid persist usage");
3177 let handle_opaque = since_handle.opaque().clone();
3178 let opaque = if our_opaque.codec_name() == handle_opaque.codec_name()
3179 && epoch.0 > handle_opaque.decode::<PersistEpoch>().0
3180 {
3181 handle_opaque
3184 } else {
3185 our_opaque
3191 };
3192 let new_since = Antichain::new();
3193 let downgrade = since_handle
3194 .compare_and_downgrade_since(&opaque, (&opaque, &new_since))
3195 .await;
3196 if let Err(e) = downgrade {
3197 warn!("tried to finalize a shard with an advancing epoch: {e:?}");
3198 return Ok(());
3199 }
3200 }
3203
3204 persist_client
3205 .finalize_shard::<SourceData, (), Timestamp, StorageDiff>(
3206 shard_id,
3207 Diagnostics::from_purpose("finalizing shards"),
3208 )
3209 .await
3210 };
3211
3212 match finalize().await {
3213 Err(e) => {
3214 warn!("error during finalization of shard {shard_id}: {e:?}");
3217 None
3218 }
3219 Ok(()) => {
3220 debug!(%shard_id, "finalize success!");
3221 Some(shard_id)
3222 }
3223 }
3224 }
3225 })
3226 .buffer_unordered(10)
3231 .for_each(|shard_id| async move {
3235 match shard_id {
3236 None => metrics.finalization_failed.inc(),
3237 Some(shard_id) => {
3238 {
3245 let mut finalizable_shards = finalizable_shards.lock();
3246 let mut finalized_shards = finalized_shards.lock();
3247 finalizable_shards.remove(&shard_id);
3248 finalized_shards.insert(shard_id);
3249 }
3250
3251 metrics.finalization_succeeded.inc();
3252 }
3253 }
3254 })
3255 .await;
3256
3257 debug!("done finalizing shards");
3258 }
3259}
3260
3261#[derive(Debug)]
3262pub(crate) enum SnapshotStatsAsOf {
3263 Direct(Antichain<Timestamp>),
3266 Txns(DataSnapshot<Timestamp>),
3269}
3270
3271#[cfg(test)]
3272mod tests {
3273 use std::str::FromStr;
3274 use std::sync::Arc;
3275
3276 use mz_build_info::DUMMY_BUILD_INFO;
3277 use mz_dyncfg::ConfigSet;
3278 use mz_ore::assert_err;
3279 use mz_ore::metrics::{MetricsRegistry, UIntGauge};
3280 use mz_ore::now::SYSTEM_TIME;
3281 use mz_ore::url::SensitiveUrl;
3282 use mz_persist_client::cache::PersistClientCache;
3283 use mz_persist_client::cfg::PersistConfig;
3284 use mz_persist_client::rpc::PubSubClientConnection;
3285 use mz_persist_client::{Diagnostics, PersistClient, PersistLocation, ShardId};
3286 use mz_persist_types::codec_impls::UnitSchema;
3287 use mz_repr::{RelationDesc, Row};
3288 use mz_secrets::InMemorySecretsController;
3289
3290 use super::*;
3291
3292 #[mz_ore::test]
3293 fn test_partition_finalizable_shards() {
3294 let active_shard = ShardId::new();
3295 let dropped_shard = ShardId::new();
3296 let collection_metadata = BTreeMap::from([
3297 (GlobalId::User(1), active_shard),
3298 (GlobalId::User(2), active_shard),
3299 (GlobalId::User(3), dropped_shard),
3300 ]);
3301 let active_collection_ids = BTreeSet::from([GlobalId::User(1), GlobalId::User(2)]);
3302 let unfinalized_shards = BTreeSet::from([active_shard, dropped_shard]);
3303
3304 let (referenced_shards, finalizable_shards) = partition_finalizable_shards(
3305 collection_metadata,
3306 &active_collection_ids,
3307 unfinalized_shards,
3308 );
3309
3310 assert_eq!(referenced_shards, BTreeSet::from([active_shard]));
3311 assert_eq!(finalizable_shards, BTreeSet::from([dropped_shard]));
3312 }
3313
3314 #[mz_ore::test(tokio::test)]
3315 #[cfg_attr(miri, ignore)] async fn test_snapshot_stats(&self) {
3317 let persist_location = PersistLocation {
3318 blob_uri: SensitiveUrl::from_str("mem://").expect("invalid URL"),
3319 consensus_uri: SensitiveUrl::from_str("mem://").expect("invalid URL"),
3320 };
3321 let persist_client = PersistClientCache::new(
3322 PersistConfig::new_default_configs(&DUMMY_BUILD_INFO, SYSTEM_TIME.clone()),
3323 &MetricsRegistry::new(),
3324 |_, _| PubSubClientConnection::noop(),
3325 );
3326 let persist_client = Arc::new(persist_client);
3327
3328 let (cmds_tx, mut background_task) =
3329 BackgroundTask::new_for_test(persist_location.clone(), Arc::clone(&persist_client));
3330 let background_task =
3331 mz_ore::task::spawn(|| "storage_collections::background_task", async move {
3332 background_task.run().await
3333 });
3334
3335 let persist = persist_client.open(persist_location).await.unwrap();
3336
3337 let shard_id = ShardId::new();
3338 let since_handle = persist
3339 .open_critical_since(
3340 shard_id,
3341 PersistClient::CONTROLLER_CRITICAL_SINCE,
3342 Opaque::encode(&PersistEpoch::default()),
3343 Diagnostics::for_tests(),
3344 )
3345 .await
3346 .unwrap();
3347 let write_handle = persist
3348 .open_writer::<SourceData, (), mz_repr::Timestamp, StorageDiff>(
3349 shard_id,
3350 Arc::new(RelationDesc::empty()),
3351 Arc::new(UnitSchema),
3352 Diagnostics::for_tests(),
3353 )
3354 .await
3355 .unwrap();
3356
3357 cmds_tx
3358 .send(BackgroundCmd::Register {
3359 id: GlobalId::User(1),
3360 is_in_txns: false,
3361 since_handle: SinceHandleWrapper::Critical(since_handle),
3362 write_handle,
3363 })
3364 .unwrap();
3365
3366 let mut write_handle = persist
3367 .open_writer::<SourceData, (), mz_repr::Timestamp, StorageDiff>(
3368 shard_id,
3369 Arc::new(RelationDesc::empty()),
3370 Arc::new(UnitSchema),
3371 Diagnostics::for_tests(),
3372 )
3373 .await
3374 .unwrap();
3375
3376 let stats =
3378 snapshot_stats(&cmds_tx, GlobalId::User(2), Antichain::from_elem(0.into())).await;
3379 assert_err!(stats);
3380
3381 let stats_fut = snapshot_stats(&cmds_tx, GlobalId::User(1), Antichain::from_elem(1.into()));
3383 assert_none!(stats_fut.now_or_never());
3384
3385 let stats_ts1_fut =
3387 snapshot_stats(&cmds_tx, GlobalId::User(1), Antichain::from_elem(1.into()));
3388
3389 let data = (
3391 (SourceData(Ok(Row::default())), ()),
3392 mz_repr::Timestamp::from(0),
3393 1i64,
3394 );
3395 let () = write_handle
3396 .compare_and_append(
3397 &[data],
3398 Antichain::from_elem(0.into()),
3399 Antichain::from_elem(1.into()),
3400 )
3401 .await
3402 .unwrap()
3403 .unwrap();
3404
3405 let stats = snapshot_stats(&cmds_tx, GlobalId::User(1), Antichain::from_elem(0.into()))
3407 .await
3408 .unwrap();
3409 assert_eq!(stats.num_updates, 1);
3410
3411 let data = (
3413 (SourceData(Ok(Row::default())), ()),
3414 mz_repr::Timestamp::from(1),
3415 1i64,
3416 );
3417 let () = write_handle
3418 .compare_and_append(
3419 &[data],
3420 Antichain::from_elem(1.into()),
3421 Antichain::from_elem(2.into()),
3422 )
3423 .await
3424 .unwrap()
3425 .unwrap();
3426
3427 let stats = stats_ts1_fut.await.unwrap();
3428 assert_eq!(stats.num_updates, 2);
3429
3430 drop(background_task);
3432 }
3433
3434 async fn snapshot_stats(
3435 cmds_tx: &mpsc::UnboundedSender<BackgroundCmd>,
3436 id: GlobalId,
3437 as_of: Antichain<Timestamp>,
3438 ) -> Result<SnapshotStats, StorageError> {
3439 let (tx, rx) = oneshot::channel();
3440 cmds_tx
3441 .send(BackgroundCmd::SnapshotStats(
3442 id,
3443 SnapshotStatsAsOf::Direct(as_of),
3444 tx,
3445 ))
3446 .unwrap();
3447 let res = rx.await.expect("BackgroundTask should be live").0;
3448
3449 res.await
3450 }
3451
3452 impl BackgroundTask {
3453 fn new_for_test(
3454 _persist_location: PersistLocation,
3455 _persist_client: Arc<PersistClientCache>,
3456 ) -> (mpsc::UnboundedSender<BackgroundCmd>, Self) {
3457 let (cmds_tx, cmds_rx) = mpsc::unbounded_channel();
3458 let (_holds_tx, holds_rx) = mpsc::unbounded_channel();
3459 let connection_context =
3460 ConnectionContext::for_tests(Arc::new(InMemorySecretsController::new()));
3461
3462 let task = Self {
3463 config: Arc::new(Mutex::new(StorageConfiguration::new(
3464 connection_context,
3465 ConfigSet::default(),
3466 ))),
3467 cmds_tx: cmds_tx.clone(),
3468 cmds_rx,
3469 holds_rx,
3470 finalizable_shards: Arc::new(ShardIdSet::new(
3471 UIntGauge::new("finalizable_shards", "dummy gauge for tests").unwrap(),
3472 )),
3473 collections: Arc::new(Mutex::new(BTreeMap::new())),
3474 shard_by_id: BTreeMap::new(),
3475 since_handles: BTreeMap::new(),
3476 txns_handle: None,
3477 txns_shards: BTreeSet::new(),
3478 };
3479
3480 (cmds_tx, task)
3481 }
3482 }
3483}