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) => id,
2144 CaESchema::ExpectedMismatch {
2146 schema_id,
2147 key,
2148 val,
2149 } => {
2150 mz_ore::soft_panic_or_log!(
2151 "schema expectation mismatch {schema_id:?}, {key:?}, {val:?}"
2152 );
2153 return Err(StorageError::Generic(anyhow::anyhow!(
2154 "schema expected mismatch, {existing_collection:?}",
2155 )));
2156 }
2157 CaESchema::Incompatible => {
2158 mz_ore::soft_panic_or_log!(
2159 "incompatible schema! {existing_collection} {new_desc:?}"
2160 );
2161 return Err(StorageError::Generic(anyhow::anyhow!(
2162 "schema incompatible, {existing_collection:?}"
2163 )));
2164 }
2165 };
2166
2167 let (write_handle, since_handle) = self
2169 .open_data_handles(
2170 &new_collection,
2171 data_shard,
2172 None,
2173 new_desc.clone(),
2174 &persist_client,
2175 )
2176 .await;
2177
2178 {
2184 let mut self_collections = self.collections.lock().expect("lock poisoned");
2185
2186 let existing = self_collections
2188 .get_mut(&existing_collection)
2189 .expect("existing collection missing");
2190
2191 assert_none!(existing.primary);
2193
2194 existing.primary = Some(new_collection);
2196 existing.storage_dependencies.push(new_collection);
2197
2198 let implied_capability = existing.read_capabilities.frontier().to_owned();
2202 let write_frontier = existing.write_frontier.clone();
2203
2204 let mut changes = ChangeBatch::new();
2211 changes.extend(implied_capability.iter().map(|t| (*t, 1)));
2212
2213 let collection_meta = CollectionMetadata {
2215 persist_location: self.persist_location.clone(),
2216 relation_desc: new_desc.clone(),
2217 data_shard,
2218 txns_shard: Some(self.txns_read.txns_id().clone()),
2219 };
2220 let collection_state = CollectionState::new(
2221 None,
2222 existing.time_dependence.clone(),
2223 existing.ingestion_remap_collection_id.clone(),
2224 implied_capability,
2225 write_frontier,
2226 Vec::new(),
2227 collection_meta,
2228 );
2229
2230 self_collections.insert(new_collection, collection_state);
2232
2233 let mut updates = BTreeMap::from([(new_collection, changes)]);
2234 StorageCollectionsImpl::update_read_capabilities_inner(
2235 &self.cmd_tx,
2236 &mut *self_collections,
2237 &mut updates,
2238 );
2239 };
2240
2241 self.register_handles(new_collection, true, since_handle, write_handle);
2243
2244 info!(%existing_collection, %new_collection, ?new_desc, "altered table");
2245
2246 Ok(())
2247 }
2248
2249 fn drop_collections_unvalidated(
2250 &self,
2251 storage_metadata: &StorageMetadata,
2252 identifiers: Vec<GlobalId>,
2253 ) {
2254 debug!(?identifiers, "drop_collections_unvalidated");
2255
2256 let mut self_collections = self.collections.lock().expect("lock poisoned");
2257 let dropping: BTreeSet<_> = identifiers.iter().copied().collect();
2260 let active_collection_ids: BTreeSet<_> = self_collections
2261 .iter()
2262 .filter_map(|(id, collection)| {
2263 (!dropping.contains(id) && !collection.is_dropped()).then_some(*id)
2264 })
2265 .collect();
2266 let shards_in_use: BTreeSet<_> = storage_metadata
2267 .collection_metadata
2268 .iter()
2269 .filter_map(|(id, shard)| active_collection_ids.contains(id).then_some(*shard))
2270 .collect();
2271
2272 let mut finalized_policies = Vec::new();
2280
2281 for id in identifiers {
2282 let Some(collection) = self_collections.get(&id) else {
2284 continue;
2285 };
2286
2287 if collection.primary.is_none() {
2290 let metadata = storage_metadata.get_collection_shard(id);
2291 mz_ore::soft_assert_or_log!(
2292 matches!(metadata, Err(StorageError::IdentifierMissing(_))),
2293 "dropping {id}, but drop was not synchronized with storage \
2294 controller via `prepare_state`"
2295 );
2296
2297 let data_shard = collection.collection_metadata.data_shard;
2301 if shards_in_use.contains(&data_shard) {
2302 mz_ore::soft_panic_or_log!(
2303 "dropping {id} would release the since of shard {data_shard}, \
2304 which an active collection still uses"
2305 );
2306 continue;
2307 }
2308 }
2309
2310 finalized_policies.push((id, ReadPolicy::ValidFrom(Antichain::new())));
2311 }
2312
2313 self.set_read_policies_inner(&mut self_collections, finalized_policies);
2314
2315 drop(self_collections);
2316
2317 self.synchronize_finalized_shards(storage_metadata);
2318 }
2319
2320 fn set_read_policies(&self, policies: Vec<(GlobalId, ReadPolicy)>) {
2321 let mut collections = self.collections.lock().expect("lock poisoned");
2322
2323 if tracing::enabled!(tracing::Level::TRACE) {
2324 let user_capabilities = collections
2325 .iter_mut()
2326 .filter(|(id, _c)| id.is_user())
2327 .map(|(id, c)| {
2328 let updates = c.read_capabilities.updates().cloned().collect_vec();
2329 (*id, c.implied_capability.clone(), updates)
2330 })
2331 .collect_vec();
2332
2333 trace!(?policies, ?user_capabilities, "set_read_policies");
2334 }
2335
2336 self.set_read_policies_inner(&mut collections, policies);
2337
2338 if tracing::enabled!(tracing::Level::TRACE) {
2339 let user_capabilities = collections
2340 .iter_mut()
2341 .filter(|(id, _c)| id.is_user())
2342 .map(|(id, c)| {
2343 let updates = c.read_capabilities.updates().cloned().collect_vec();
2344 (*id, c.implied_capability.clone(), updates)
2345 })
2346 .collect_vec();
2347
2348 trace!(?user_capabilities, "after! set_read_policies");
2349 }
2350 }
2351
2352 fn acquire_read_holds(
2353 &self,
2354 desired_holds: Vec<GlobalId>,
2355 ) -> Result<Vec<ReadHold>, CollectionMissing> {
2356 if desired_holds.is_empty() {
2357 return Ok(vec![]);
2358 }
2359
2360 let mut collections = self.collections.lock().expect("lock poisoned");
2361
2362 let mut advanced_holds = Vec::new();
2363 for id in desired_holds.iter() {
2374 let collection = collections.get(id).ok_or(CollectionMissing(*id))?;
2375 let since = collection.read_capabilities.frontier().to_owned();
2376 advanced_holds.push((*id, since));
2377 }
2378
2379 let mut updates = advanced_holds
2380 .iter()
2381 .map(|(id, hold)| {
2382 let mut changes = ChangeBatch::new();
2383 changes.extend(hold.iter().map(|time| (*time, 1)));
2384 (*id, changes)
2385 })
2386 .collect::<BTreeMap<_, _>>();
2387
2388 StorageCollectionsImpl::update_read_capabilities_inner(
2389 &self.cmd_tx,
2390 &mut collections,
2391 &mut updates,
2392 );
2393
2394 let acquired_holds = advanced_holds
2395 .into_iter()
2396 .map(|(id, since)| ReadHold::with_channel(id, since, self.holds_tx.clone()))
2397 .collect_vec();
2398
2399 trace!(?desired_holds, ?acquired_holds, "acquire_read_holds");
2400
2401 Ok(acquired_holds)
2402 }
2403
2404 fn determine_time_dependence(
2406 &self,
2407 id: GlobalId,
2408 ) -> Result<Option<TimeDependence>, TimeDependenceError> {
2409 use TimeDependenceError::CollectionMissing;
2410 let collections = self.collections.lock().expect("lock poisoned");
2411 let state = collections.get(&id).ok_or(CollectionMissing(id))?;
2412 Ok(state.time_dependence.clone())
2413 }
2414
2415 fn dump(&self) -> Result<serde_json::Value, anyhow::Error> {
2416 let Self {
2418 envd_epoch,
2419 read_only,
2420 finalizable_shards,
2421 finalized_shards,
2422 collections,
2423 txns_read: _,
2424 config,
2425 initial_txn_upper,
2426 persist_location,
2427 persist: _,
2428 cmd_tx: _,
2429 holds_tx: _,
2430 _background_task: _,
2431 _finalize_shards_task: _,
2432 } = self;
2433
2434 let finalizable_shards: Vec<_> = finalizable_shards
2435 .lock()
2436 .iter()
2437 .map(ToString::to_string)
2438 .collect();
2439 let finalized_shards: Vec<_> = finalized_shards
2440 .lock()
2441 .iter()
2442 .map(ToString::to_string)
2443 .collect();
2444 let collections: BTreeMap<_, _> = collections
2445 .lock()
2446 .expect("poisoned")
2447 .iter()
2448 .map(|(id, c)| (id.to_string(), format!("{c:?}")))
2449 .collect();
2450 let config = format!("{:?}", config.lock().expect("poisoned"));
2451
2452 Ok(serde_json::json!({
2453 "envd_epoch": envd_epoch,
2454 "read_only": read_only,
2455 "finalizable_shards": finalizable_shards,
2456 "finalized_shards": finalized_shards,
2457 "collections": collections,
2458 "config": config,
2459 "initial_txn_upper": initial_txn_upper,
2460 "persist_location": format!("{persist_location:?}"),
2461 }))
2462 }
2463}
2464
2465#[derive(Debug)]
2472enum SinceHandleWrapper {
2473 Critical(SinceHandle<SourceData, (), Timestamp, StorageDiff>),
2474 Leased(ReadHandle<SourceData, (), Timestamp, StorageDiff>),
2475}
2476
2477impl SinceHandleWrapper {
2478 pub fn since(&self) -> &Antichain<Timestamp> {
2479 match self {
2480 Self::Critical(handle) => handle.since(),
2481 Self::Leased(handle) => handle.since(),
2482 }
2483 }
2484
2485 pub fn opaque(&self) -> PersistEpoch {
2486 match self {
2487 Self::Critical(handle) => handle.opaque().decode(),
2488 Self::Leased(_handle) => {
2489 PersistEpoch(None)
2494 }
2495 }
2496 }
2497
2498 pub async fn compare_and_downgrade_since(
2499 &mut self,
2500 expected: &PersistEpoch,
2501 (opaque, since): (&PersistEpoch, &Antichain<Timestamp>),
2502 ) -> Result<Antichain<Timestamp>, PersistEpoch> {
2503 match self {
2504 Self::Critical(handle) => handle
2505 .compare_and_downgrade_since(
2506 &Opaque::encode(expected),
2507 (&Opaque::encode(opaque), since),
2508 )
2509 .await
2510 .map_err(|e| e.decode()),
2511 Self::Leased(handle) => {
2512 assert_none!(opaque.0);
2513
2514 handle.downgrade_since(since).await;
2515
2516 Ok(since.clone())
2517 }
2518 }
2519 }
2520
2521 pub async fn maybe_compare_and_downgrade_since(
2522 &mut self,
2523 expected: &PersistEpoch,
2524 (opaque, since): (&PersistEpoch, &Antichain<Timestamp>),
2525 ) -> Option<Result<Antichain<Timestamp>, PersistEpoch>> {
2526 match self {
2527 Self::Critical(handle) => handle
2528 .maybe_compare_and_downgrade_since(
2529 &Opaque::encode(expected),
2530 (&Opaque::encode(opaque), since),
2531 )
2532 .await
2533 .map(|r| r.map_err(|o| o.decode())),
2534 Self::Leased(handle) => {
2535 assert_none!(opaque.0);
2536
2537 handle.maybe_downgrade_since(since).await;
2538
2539 Some(Ok(since.clone()))
2540 }
2541 }
2542 }
2543
2544 pub fn snapshot_stats(
2545 &self,
2546 id: GlobalId,
2547 as_of: Option<Antichain<Timestamp>>,
2548 ) -> BoxFuture<'static, Result<SnapshotStats, StorageError>> {
2549 match self {
2550 Self::Critical(handle) => {
2551 let res = handle
2552 .snapshot_stats(as_of)
2553 .map(move |x| x.map_err(|_| StorageError::ReadBeforeSince(id)));
2554 Box::pin(res)
2555 }
2556 Self::Leased(handle) => {
2557 let res = handle
2558 .snapshot_stats(as_of)
2559 .map(move |x| x.map_err(|_| StorageError::ReadBeforeSince(id)));
2560 Box::pin(res)
2561 }
2562 }
2563 }
2564
2565 pub fn snapshot_stats_from_txn(
2566 &self,
2567 id: GlobalId,
2568 data_snapshot: DataSnapshot<Timestamp>,
2569 ) -> BoxFuture<'static, Result<SnapshotStats, StorageError>> {
2570 match self {
2571 Self::Critical(handle) => Box::pin(
2572 data_snapshot
2573 .snapshot_stats_from_critical(handle)
2574 .map(move |x| x.map_err(|_| StorageError::ReadBeforeSince(id))),
2575 ),
2576 Self::Leased(handle) => Box::pin(
2577 data_snapshot
2578 .snapshot_stats_from_leased(handle)
2579 .map(move |x| x.map_err(|_| StorageError::ReadBeforeSince(id))),
2580 ),
2581 }
2582 }
2583}
2584
2585#[derive(Debug, Clone)]
2587struct CollectionState {
2588 primary: Option<GlobalId>,
2595
2596 time_dependence: Option<TimeDependence>,
2598 ingestion_remap_collection_id: Option<GlobalId>,
2600
2601 pub read_capabilities: MutableAntichain<Timestamp>,
2607
2608 pub implied_capability: Antichain<Timestamp>,
2612
2613 pub read_policy: ReadPolicy,
2615
2616 pub storage_dependencies: Vec<GlobalId>,
2618
2619 pub write_frontier: Antichain<Timestamp>,
2621
2622 pub collection_metadata: CollectionMetadata,
2623}
2624
2625impl CollectionState {
2626 pub fn new(
2629 primary: Option<GlobalId>,
2630 time_dependence: Option<TimeDependence>,
2631 ingestion_remap_collection_id: Option<GlobalId>,
2632 since: Antichain<Timestamp>,
2633 write_frontier: Antichain<Timestamp>,
2634 storage_dependencies: Vec<GlobalId>,
2635 metadata: CollectionMetadata,
2636 ) -> Self {
2637 let mut read_capabilities = MutableAntichain::new();
2638 read_capabilities.update_iter(since.iter().map(|time| (*time, 1)));
2639 Self {
2640 primary,
2641 time_dependence,
2642 ingestion_remap_collection_id,
2643 read_capabilities,
2644 implied_capability: since.clone(),
2645 read_policy: ReadPolicy::NoPolicy {
2646 initial_since: since,
2647 },
2648 storage_dependencies,
2649 write_frontier,
2650 collection_metadata: metadata,
2651 }
2652 }
2653
2654 pub fn is_dropped(&self) -> bool {
2656 self.read_capabilities.is_empty()
2657 }
2658}
2659
2660#[derive(Debug)]
2666struct BackgroundTask {
2667 config: Arc<Mutex<StorageConfiguration>>,
2668 cmds_tx: mpsc::UnboundedSender<BackgroundCmd>,
2669 cmds_rx: mpsc::UnboundedReceiver<BackgroundCmd>,
2670 holds_rx: mpsc::UnboundedReceiver<(GlobalId, ChangeBatch<Timestamp>)>,
2671 finalizable_shards: Arc<ShardIdSet>,
2672 collections: Arc<std::sync::Mutex<BTreeMap<GlobalId, CollectionState>>>,
2673 shard_by_id: BTreeMap<GlobalId, ShardId>,
2676 since_handles: BTreeMap<GlobalId, SinceHandleWrapper>,
2677 txns_handle: Option<WriteHandle<SourceData, (), Timestamp, StorageDiff>>,
2678 txns_shards: BTreeSet<GlobalId>,
2679}
2680
2681#[derive(Debug)]
2682enum BackgroundCmd {
2683 Register {
2684 id: GlobalId,
2685 is_in_txns: bool,
2686 write_handle: WriteHandle<SourceData, (), Timestamp, StorageDiff>,
2687 since_handle: SinceHandleWrapper,
2688 },
2689 DowngradeSince(Vec<(GlobalId, Antichain<Timestamp>)>),
2690 SnapshotStats(
2691 GlobalId,
2692 SnapshotStatsAsOf,
2693 oneshot::Sender<SnapshotStatsRes>,
2694 ),
2695}
2696
2697pub(crate) struct SnapshotStatsRes(BoxFuture<'static, Result<SnapshotStats, StorageError>>);
2699
2700impl Debug for SnapshotStatsRes {
2701 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2702 f.debug_struct("SnapshotStatsRes").finish_non_exhaustive()
2703 }
2704}
2705
2706impl BackgroundTask {
2707 async fn run(&mut self) {
2708 let mut upper_futures: FuturesUnordered<
2710 std::pin::Pin<
2711 Box<
2712 dyn Future<
2713 Output = (
2714 GlobalId,
2715 WriteHandle<SourceData, (), Timestamp, StorageDiff>,
2716 Antichain<Timestamp>,
2717 ),
2718 > + Send,
2719 >,
2720 >,
2721 > = FuturesUnordered::new();
2722
2723 let gen_upper_future =
2724 |id, mut handle: WriteHandle<_, _, _, _>, prev_upper: Antichain<Timestamp>| {
2725 let fut = async move {
2726 soft_assert_or_log!(
2727 !prev_upper.is_empty(),
2728 "cannot await progress when upper is already empty"
2729 );
2730 handle.wait_for_upper_past(&prev_upper).await;
2731 let new_upper = handle.shared_upper();
2732 (id, handle, new_upper)
2733 };
2734
2735 fut
2736 };
2737
2738 let mut txns_upper_future = match self.txns_handle.take() {
2739 Some(txns_handle) => {
2740 let upper = txns_handle.upper().clone();
2741 let txns_upper_future =
2742 gen_upper_future(GlobalId::Transient(1), txns_handle, upper);
2743 txns_upper_future.boxed()
2744 }
2745 None => async { std::future::pending().await }.boxed(),
2746 };
2747
2748 loop {
2749 tokio::select! {
2750 (id, handle, upper) = &mut txns_upper_future => {
2751 trace!("new upper from txns shard: {:?}", upper);
2752 let mut uppers = Vec::new();
2753 for id in self.txns_shards.iter() {
2754 uppers.push((*id, &upper));
2755 }
2756 self.update_write_frontiers(&uppers).await;
2757
2758 let fut = gen_upper_future(id, handle, upper);
2759 txns_upper_future = fut.boxed();
2760 }
2761 Some((id, handle, upper)) = upper_futures.next() => {
2762 if id.is_user() {
2763 trace!("new upper for collection {id}: {:?}", upper);
2764 }
2765 let current_shard = self.shard_by_id.get(&id);
2766 if let Some(shard_id) = current_shard {
2767 if shard_id == &handle.shard_id() {
2768 let uppers = &[(id, &upper)];
2771 self.update_write_frontiers(uppers).await;
2772 if !upper.is_empty() {
2773 let fut = gen_upper_future(id, handle, upper);
2774 upper_futures.push(fut.boxed());
2775 }
2776 } else {
2777 handle.expire().await;
2781 }
2782 }
2783 }
2784 cmd = self.cmds_rx.recv() => {
2785 let Some(cmd) = cmd else {
2786 break;
2788 };
2789
2790 let commands = iter::once(cmd).chain(
2794 iter::from_fn(|| self.cmds_rx.try_recv().ok())
2795 );
2796 let mut downgrades = BTreeMap::<_, Antichain<_>>::new();
2797 for cmd in commands {
2798 match cmd {
2799 BackgroundCmd::Register{
2800 id,
2801 is_in_txns,
2802 write_handle,
2803 since_handle
2804 } => {
2805 debug!("registering handles for {}", id);
2806 let previous = self.shard_by_id.insert(id, write_handle.shard_id());
2807 if previous.is_some() {
2808 panic!("already registered a WriteHandle for collection {id}");
2809 }
2810
2811 let previous = self.since_handles.insert(id, since_handle);
2812 if previous.is_some() {
2813 panic!("already registered a SinceHandle for collection {id}");
2814 }
2815
2816 if is_in_txns {
2817 self.txns_shards.insert(id);
2818 } else {
2819 let upper = write_handle.upper().clone();
2820 if !upper.is_empty() {
2821 let fut = gen_upper_future(id, write_handle, upper);
2822 upper_futures.push(fut.boxed());
2823 }
2824 }
2825 }
2826 BackgroundCmd::DowngradeSince(cmds) => {
2827 for (id, new) in cmds {
2828 downgrades.entry(id)
2829 .and_modify(|since| since.join_assign(&new))
2830 .or_insert(new);
2831 }
2832 }
2833 BackgroundCmd::SnapshotStats(id, as_of, tx) => {
2834 let res = match self.since_handles.get(&id) {
2840 Some(x) => {
2841 let fut: BoxFuture<
2842 'static,
2843 Result<SnapshotStats, StorageError>,
2844 > = match as_of {
2845 SnapshotStatsAsOf::Direct(as_of) => {
2846 x.snapshot_stats(id, Some(as_of))
2847 }
2848 SnapshotStatsAsOf::Txns(data_snapshot) => {
2849 x.snapshot_stats_from_txn(id, data_snapshot)
2850 }
2851 };
2852 SnapshotStatsRes(fut)
2853 }
2854 None => SnapshotStatsRes(Box::pin(futures::future::ready(Err(
2855 StorageError::IdentifierMissing(id),
2856 )))),
2857 };
2858 let _ = tx.send(res);
2860 }
2861 }
2862 }
2863
2864 if !downgrades.is_empty() {
2865 self.downgrade_sinces(downgrades).await;
2866 }
2867 }
2868 Some(holds_changes) = self.holds_rx.recv() => {
2869 let mut batched_changes = BTreeMap::new();
2870 batched_changes.insert(holds_changes.0, holds_changes.1);
2871
2872 while let Ok(mut holds_changes) = self.holds_rx.try_recv() {
2873 let entry = batched_changes.entry(holds_changes.0);
2874 entry
2875 .and_modify(|existing| existing.extend(holds_changes.1.drain()))
2876 .or_insert_with(|| holds_changes.1);
2877 }
2878
2879 let mut collections = self.collections.lock().expect("lock poisoned");
2880
2881 let user_changes = batched_changes
2882 .iter()
2883 .filter(|(id, _c)| id.is_user())
2884 .map(|(id, c)| {
2885 (id.clone(), c.clone())
2886 })
2887 .collect_vec();
2888
2889 if !user_changes.is_empty() {
2890 trace!(?user_changes, "applying holds changes from channel");
2891 }
2892
2893 StorageCollectionsImpl::update_read_capabilities_inner(
2894 &self.cmds_tx,
2895 &mut collections,
2896 &mut batched_changes,
2897 );
2898 }
2899 }
2900 }
2901
2902 warn!("BackgroundTask shutting down");
2903 }
2904
2905 #[instrument(level = "debug")]
2906 async fn update_write_frontiers(&self, updates: &[(GlobalId, &Antichain<Timestamp>)]) {
2907 let mut read_capability_changes = BTreeMap::default();
2908
2909 let mut self_collections = self.collections.lock().expect("lock poisoned");
2910
2911 for (id, new_upper) in updates.iter() {
2912 let collection = if let Some(c) = self_collections.get_mut(id) {
2913 c
2914 } else {
2915 trace!(
2916 "Reference to absent collection {id}, due to concurrent removal of that collection"
2917 );
2918 continue;
2919 };
2920
2921 if PartialOrder::less_than(&collection.write_frontier, *new_upper) {
2922 collection.write_frontier.clone_from(new_upper);
2923 }
2924
2925 let mut new_read_capability = collection
2926 .read_policy
2927 .frontier(collection.write_frontier.borrow());
2928
2929 if id.is_user() {
2930 trace!(
2931 %id,
2932 implied_capability = ?collection.implied_capability,
2933 policy = ?collection.read_policy,
2934 write_frontier = ?collection.write_frontier,
2935 ?new_read_capability,
2936 "update_write_frontiers");
2937 }
2938
2939 if PartialOrder::less_equal(&collection.implied_capability, &new_read_capability) {
2940 let mut update = ChangeBatch::new();
2941 update.extend(new_read_capability.iter().map(|time| (*time, 1)));
2942 std::mem::swap(&mut collection.implied_capability, &mut new_read_capability);
2943 update.extend(new_read_capability.iter().map(|time| (*time, -1)));
2944
2945 if !update.is_empty() {
2946 read_capability_changes.insert(*id, update);
2947 }
2948 }
2949 }
2950
2951 if !read_capability_changes.is_empty() {
2952 StorageCollectionsImpl::update_read_capabilities_inner(
2953 &self.cmds_tx,
2954 &mut self_collections,
2955 &mut read_capability_changes,
2956 );
2957 }
2958 }
2959
2960 async fn downgrade_sinces(&mut self, cmds: BTreeMap<GlobalId, Antichain<Timestamp>>) {
2961 let mut futures = Vec::with_capacity(cmds.len());
2963 for (id, new_since) in cmds {
2964 let Some(mut since_handle) = self.since_handles.remove(&id) else {
2967 trace!("downgrade_sinces: reference to absent collection {id}");
2969 continue;
2970 };
2971
2972 let fut = async move {
2973 if id.is_user() {
2974 trace!("downgrading since of {} to {:?}", id, new_since);
2975 }
2976
2977 let epoch = since_handle.opaque().clone();
2978 let result = if new_since.is_empty() {
2979 Some(
2983 since_handle
2984 .compare_and_downgrade_since(&epoch, (&epoch, &new_since))
2985 .await,
2986 )
2987 } else {
2988 since_handle
2989 .maybe_compare_and_downgrade_since(&epoch, (&epoch, &new_since))
2990 .await
2991 };
2992 (id, since_handle, result)
2993 };
2994 futures.push(fut);
2995 }
2996
2997 for (id, since_handle, result) in futures::future::join_all(futures).await {
2998 let new_since = match result {
2999 Some(Ok(since)) => Some(since),
3000 Some(Err(other_epoch)) => mz_ore::halt!(
3001 "fenced by envd @ {other_epoch:?}. ours = {:?}",
3002 since_handle.opaque(),
3003 ),
3004 None => None,
3005 };
3006
3007 self.since_handles.insert(id, since_handle);
3008
3009 if new_since.is_some_and(|s| s.is_empty()) {
3010 info!(%id, "removing persist handles because the since advanced to []!");
3011
3012 let _since_handle = self.since_handles.remove(&id).expect("known to exist");
3013 let Some(dropped_shard_id) = self.shard_by_id.remove(&id) else {
3014 panic!("missing GlobalId -> ShardId mapping for id {id}");
3015 };
3016
3017 self.txns_shards.remove(&id);
3022
3023 if self
3024 .config
3025 .lock()
3026 .expect("lock poisoned")
3027 .parameters
3028 .finalize_shards
3029 {
3030 info!(
3031 %id, %dropped_shard_id,
3032 "enqueuing shard finalization due to dropped collection and dropped \
3033 persist handle",
3034 );
3035 self.finalizable_shards.lock().insert(dropped_shard_id);
3036 } else {
3037 info!(
3038 "not triggering shard finalization due to dropped storage object \
3039 because enable_storage_shard_finalization parameter is false"
3040 );
3041 }
3042 }
3043 }
3044 }
3045}
3046
3047struct FinalizeShardsTaskConfig {
3048 envd_epoch: NonZeroI64,
3049 config: Arc<Mutex<StorageConfiguration>>,
3050 metrics: StorageCollectionsMetrics,
3051 finalizable_shards: Arc<ShardIdSet>,
3052 finalized_shards: Arc<ShardIdSet>,
3053 persist_location: PersistLocation,
3054 persist: Arc<PersistClientCache>,
3055 read_only: bool,
3056}
3057
3058async fn finalize_shards_task(
3059 FinalizeShardsTaskConfig {
3060 envd_epoch,
3061 config,
3062 metrics,
3063 finalizable_shards,
3064 finalized_shards,
3065 persist_location,
3066 persist,
3067 read_only,
3068 }: FinalizeShardsTaskConfig,
3069) {
3070 if read_only {
3071 info!("disabling shard finalization in read only mode");
3072 return;
3073 }
3074
3075 let mut interval = tokio::time::interval(Duration::from_secs(5));
3076 interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
3077 loop {
3078 interval.tick().await;
3079
3080 if !config
3081 .lock()
3082 .expect("lock poisoned")
3083 .parameters
3084 .finalize_shards
3085 {
3086 debug!(
3087 "not triggering shard finalization due to dropped storage object because enable_storage_shard_finalization parameter is false"
3088 );
3089 continue;
3090 }
3091
3092 let current_finalizable_shards = {
3093 finalizable_shards.lock().iter().cloned().collect_vec()
3096 };
3097
3098 if current_finalizable_shards.is_empty() {
3099 debug!("no shards to finalize");
3100 continue;
3101 }
3102
3103 debug!(?current_finalizable_shards, "attempting to finalize shards");
3104
3105 let persist_client = persist.open(persist_location.clone()).await.unwrap();
3107
3108 let metrics = &metrics;
3109 let finalizable_shards = &finalizable_shards;
3110 let finalized_shards = &finalized_shards;
3111 let persist_client = &persist_client;
3112 let diagnostics = &Diagnostics::from_purpose("finalizing shards");
3113
3114 let force_downgrade_since = STORAGE_DOWNGRADE_SINCE_DURING_FINALIZATION
3115 .get(config.lock().expect("lock poisoned").config_set());
3116
3117 let epoch = &PersistEpoch::from(envd_epoch);
3118
3119 futures::stream::iter(current_finalizable_shards.clone())
3120 .map(|shard_id| async move {
3121 let persist_client = persist_client.clone();
3122 let diagnostics = diagnostics.clone();
3123 let epoch = epoch.clone();
3124
3125 metrics.finalization_started.inc();
3126
3127 let is_finalized = persist_client
3128 .is_finalized::<SourceData, (), Timestamp, StorageDiff>(shard_id, diagnostics)
3129 .await
3130 .expect("invalid persist usage");
3131
3132 if is_finalized {
3133 debug!(%shard_id, "shard is already finalized!");
3134 Some(shard_id)
3135 } else {
3136 debug!(%shard_id, "finalizing shard");
3137 let finalize = || async move {
3138 let diagnostics = Diagnostics::from_purpose("finalizing shards");
3140
3141 let mut write_handle: WriteHandle<SourceData, (), Timestamp, StorageDiff> =
3144 persist_client
3145 .open_writer(
3146 shard_id,
3147 Arc::new(RelationDesc::empty()),
3148 Arc::new(UnitSchema),
3149 diagnostics,
3150 )
3151 .await
3152 .expect("invalid persist usage");
3153 write_handle.advance_upper(&Antichain::new()).await;
3154 write_handle.expire().await;
3155
3156 if force_downgrade_since {
3157 let our_opaque = Opaque::encode(&epoch);
3158 let mut since_handle: SinceHandle<
3159 SourceData,
3160 (),
3161 Timestamp,
3162 StorageDiff,
3163 > = persist_client
3164 .open_critical_since(
3165 shard_id,
3166 PersistClient::CONTROLLER_CRITICAL_SINCE,
3167 our_opaque.clone(),
3168 Diagnostics::from_purpose("finalizing shards"),
3169 )
3170 .await
3171 .expect("invalid persist usage");
3172 let handle_opaque = since_handle.opaque().clone();
3173 let opaque = if our_opaque.codec_name() == handle_opaque.codec_name()
3174 && epoch.0 > handle_opaque.decode::<PersistEpoch>().0
3175 {
3176 handle_opaque
3179 } else {
3180 our_opaque
3186 };
3187 let new_since = Antichain::new();
3188 let downgrade = since_handle
3189 .compare_and_downgrade_since(&opaque, (&opaque, &new_since))
3190 .await;
3191 if let Err(e) = downgrade {
3192 warn!("tried to finalize a shard with an advancing epoch: {e:?}");
3193 return Ok(());
3194 }
3195 }
3198
3199 persist_client
3200 .finalize_shard::<SourceData, (), Timestamp, StorageDiff>(
3201 shard_id,
3202 Diagnostics::from_purpose("finalizing shards"),
3203 )
3204 .await
3205 };
3206
3207 match finalize().await {
3208 Err(e) => {
3209 warn!("error during finalization of shard {shard_id}: {e:?}");
3212 None
3213 }
3214 Ok(()) => {
3215 debug!(%shard_id, "finalize success!");
3216 Some(shard_id)
3217 }
3218 }
3219 }
3220 })
3221 .buffer_unordered(10)
3226 .for_each(|shard_id| async move {
3230 match shard_id {
3231 None => metrics.finalization_failed.inc(),
3232 Some(shard_id) => {
3233 {
3240 let mut finalizable_shards = finalizable_shards.lock();
3241 let mut finalized_shards = finalized_shards.lock();
3242 finalizable_shards.remove(&shard_id);
3243 finalized_shards.insert(shard_id);
3244 }
3245
3246 metrics.finalization_succeeded.inc();
3247 }
3248 }
3249 })
3250 .await;
3251
3252 debug!("done finalizing shards");
3253 }
3254}
3255
3256#[derive(Debug)]
3257pub(crate) enum SnapshotStatsAsOf {
3258 Direct(Antichain<Timestamp>),
3261 Txns(DataSnapshot<Timestamp>),
3264}
3265
3266#[cfg(test)]
3267mod tests {
3268 use std::str::FromStr;
3269 use std::sync::Arc;
3270
3271 use mz_build_info::DUMMY_BUILD_INFO;
3272 use mz_dyncfg::ConfigSet;
3273 use mz_ore::assert_err;
3274 use mz_ore::metrics::{MetricsRegistry, UIntGauge};
3275 use mz_ore::now::SYSTEM_TIME;
3276 use mz_ore::url::SensitiveUrl;
3277 use mz_persist_client::cache::PersistClientCache;
3278 use mz_persist_client::cfg::PersistConfig;
3279 use mz_persist_client::rpc::PubSubClientConnection;
3280 use mz_persist_client::{Diagnostics, PersistClient, PersistLocation, ShardId};
3281 use mz_persist_types::codec_impls::UnitSchema;
3282 use mz_repr::{RelationDesc, Row};
3283 use mz_secrets::InMemorySecretsController;
3284
3285 use super::*;
3286
3287 #[mz_ore::test]
3288 fn test_partition_finalizable_shards() {
3289 let active_shard = ShardId::new();
3290 let dropped_shard = ShardId::new();
3291 let collection_metadata = BTreeMap::from([
3292 (GlobalId::User(1), active_shard),
3293 (GlobalId::User(2), active_shard),
3294 (GlobalId::User(3), dropped_shard),
3295 ]);
3296 let active_collection_ids = BTreeSet::from([GlobalId::User(1), GlobalId::User(2)]);
3297 let unfinalized_shards = BTreeSet::from([active_shard, dropped_shard]);
3298
3299 let (referenced_shards, finalizable_shards) = partition_finalizable_shards(
3300 collection_metadata,
3301 &active_collection_ids,
3302 unfinalized_shards,
3303 );
3304
3305 assert_eq!(referenced_shards, BTreeSet::from([active_shard]));
3306 assert_eq!(finalizable_shards, BTreeSet::from([dropped_shard]));
3307 }
3308
3309 #[mz_ore::test(tokio::test)]
3310 #[cfg_attr(miri, ignore)] async fn test_snapshot_stats(&self) {
3312 let persist_location = PersistLocation {
3313 blob_uri: SensitiveUrl::from_str("mem://").expect("invalid URL"),
3314 consensus_uri: SensitiveUrl::from_str("mem://").expect("invalid URL"),
3315 };
3316 let persist_client = PersistClientCache::new(
3317 PersistConfig::new_default_configs(&DUMMY_BUILD_INFO, SYSTEM_TIME.clone()),
3318 &MetricsRegistry::new(),
3319 |_, _| PubSubClientConnection::noop(),
3320 );
3321 let persist_client = Arc::new(persist_client);
3322
3323 let (cmds_tx, mut background_task) =
3324 BackgroundTask::new_for_test(persist_location.clone(), Arc::clone(&persist_client));
3325 let background_task =
3326 mz_ore::task::spawn(|| "storage_collections::background_task", async move {
3327 background_task.run().await
3328 });
3329
3330 let persist = persist_client.open(persist_location).await.unwrap();
3331
3332 let shard_id = ShardId::new();
3333 let since_handle = persist
3334 .open_critical_since(
3335 shard_id,
3336 PersistClient::CONTROLLER_CRITICAL_SINCE,
3337 Opaque::encode(&PersistEpoch::default()),
3338 Diagnostics::for_tests(),
3339 )
3340 .await
3341 .unwrap();
3342 let write_handle = persist
3343 .open_writer::<SourceData, (), mz_repr::Timestamp, StorageDiff>(
3344 shard_id,
3345 Arc::new(RelationDesc::empty()),
3346 Arc::new(UnitSchema),
3347 Diagnostics::for_tests(),
3348 )
3349 .await
3350 .unwrap();
3351
3352 cmds_tx
3353 .send(BackgroundCmd::Register {
3354 id: GlobalId::User(1),
3355 is_in_txns: false,
3356 since_handle: SinceHandleWrapper::Critical(since_handle),
3357 write_handle,
3358 })
3359 .unwrap();
3360
3361 let mut write_handle = persist
3362 .open_writer::<SourceData, (), mz_repr::Timestamp, StorageDiff>(
3363 shard_id,
3364 Arc::new(RelationDesc::empty()),
3365 Arc::new(UnitSchema),
3366 Diagnostics::for_tests(),
3367 )
3368 .await
3369 .unwrap();
3370
3371 let stats =
3373 snapshot_stats(&cmds_tx, GlobalId::User(2), Antichain::from_elem(0.into())).await;
3374 assert_err!(stats);
3375
3376 let stats_fut = snapshot_stats(&cmds_tx, GlobalId::User(1), Antichain::from_elem(1.into()));
3378 assert_none!(stats_fut.now_or_never());
3379
3380 let stats_ts1_fut =
3382 snapshot_stats(&cmds_tx, GlobalId::User(1), Antichain::from_elem(1.into()));
3383
3384 let data = (
3386 (SourceData(Ok(Row::default())), ()),
3387 mz_repr::Timestamp::from(0),
3388 1i64,
3389 );
3390 let () = write_handle
3391 .compare_and_append(
3392 &[data],
3393 Antichain::from_elem(0.into()),
3394 Antichain::from_elem(1.into()),
3395 )
3396 .await
3397 .unwrap()
3398 .unwrap();
3399
3400 let stats = snapshot_stats(&cmds_tx, GlobalId::User(1), Antichain::from_elem(0.into()))
3402 .await
3403 .unwrap();
3404 assert_eq!(stats.num_updates, 1);
3405
3406 let data = (
3408 (SourceData(Ok(Row::default())), ()),
3409 mz_repr::Timestamp::from(1),
3410 1i64,
3411 );
3412 let () = write_handle
3413 .compare_and_append(
3414 &[data],
3415 Antichain::from_elem(1.into()),
3416 Antichain::from_elem(2.into()),
3417 )
3418 .await
3419 .unwrap()
3420 .unwrap();
3421
3422 let stats = stats_ts1_fut.await.unwrap();
3423 assert_eq!(stats.num_updates, 2);
3424
3425 drop(background_task);
3427 }
3428
3429 async fn snapshot_stats(
3430 cmds_tx: &mpsc::UnboundedSender<BackgroundCmd>,
3431 id: GlobalId,
3432 as_of: Antichain<Timestamp>,
3433 ) -> Result<SnapshotStats, StorageError> {
3434 let (tx, rx) = oneshot::channel();
3435 cmds_tx
3436 .send(BackgroundCmd::SnapshotStats(
3437 id,
3438 SnapshotStatsAsOf::Direct(as_of),
3439 tx,
3440 ))
3441 .unwrap();
3442 let res = rx.await.expect("BackgroundTask should be live").0;
3443
3444 res.await
3445 }
3446
3447 impl BackgroundTask {
3448 fn new_for_test(
3449 _persist_location: PersistLocation,
3450 _persist_client: Arc<PersistClientCache>,
3451 ) -> (mpsc::UnboundedSender<BackgroundCmd>, Self) {
3452 let (cmds_tx, cmds_rx) = mpsc::unbounded_channel();
3453 let (_holds_tx, holds_rx) = mpsc::unbounded_channel();
3454 let connection_context =
3455 ConnectionContext::for_tests(Arc::new(InMemorySecretsController::new()));
3456
3457 let task = Self {
3458 config: Arc::new(Mutex::new(StorageConfiguration::new(
3459 connection_context,
3460 ConfigSet::default(),
3461 ))),
3462 cmds_tx: cmds_tx.clone(),
3463 cmds_rx,
3464 holds_rx,
3465 finalizable_shards: Arc::new(ShardIdSet::new(
3466 UIntGauge::new("finalizable_shards", "dummy gauge for tests").unwrap(),
3467 )),
3468 collections: Arc::new(Mutex::new(BTreeMap::new())),
3469 shard_by_id: BTreeMap::new(),
3470 since_handles: BTreeMap::new(),
3471 txns_handle: None,
3472 txns_shards: BTreeSet::new(),
3473 };
3474
3475 (cmds_tx, task)
3476 }
3477 }
3478}