Skip to main content

mz_storage_client/
controller.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! A controller that provides an interface to the storage layer.
11//!
12//! The storage controller curates the creation of sources, the progress of readers through these collections,
13//! and their eventual dropping and resource reclamation.
14//!
15//! The storage controller can be viewed as a partial map from `GlobalId` to collection. It is an error to
16//! use an identifier before it has been "created" with `create_source()`. Once created, the controller holds
17//! a read capability for each source, which is manipulated with `update_read_capabilities()`.
18//! Eventually, the source is dropped with either `drop_sources()` or by allowing compaction to the
19//! empty frontier.
20
21use std::collections::{BTreeMap, BTreeSet};
22use std::fmt::Debug;
23use std::future::Future;
24use std::num::NonZeroI64;
25use std::pin::Pin;
26use std::sync::Arc;
27use std::time::Duration;
28
29use async_trait::async_trait;
30use chrono::{DateTime, Utc};
31use differential_dataflow::lattice::Lattice;
32use mz_cluster_client::ReplicaId;
33use mz_cluster_client::client::ClusterReplicaLocation;
34use mz_controller_types::dyncfgs::WALLCLOCK_LAG_HISTOGRAM_PERIOD_INTERVAL;
35use mz_dyncfg::{ConfigSet, ConfigUpdates};
36use mz_ore::soft_panic_or_log;
37use mz_persist_client::batch::ProtoBatch;
38use mz_persist_types::{Codec64, ShardId};
39use mz_repr::adt::interval::Interval;
40use mz_repr::adt::timestamp::CheckedTimestamp;
41use mz_repr::{Datum, Diff, GlobalId, RelationDesc, RelationVersion, Row, Timestamp};
42use mz_storage_types::configuration::StorageConfiguration;
43use mz_storage_types::connections::inline::InlinedConnection;
44use mz_storage_types::controller::{CollectionMetadata, StorageError};
45use mz_storage_types::errors::CollectionMissing;
46use mz_storage_types::instances::StorageInstanceId;
47use mz_storage_types::oneshot_sources::{OneshotIngestionRequest, OneshotResultCallback};
48use mz_storage_types::parameters::StorageParameters;
49use mz_storage_types::read_holds::ReadHold;
50use mz_storage_types::read_policy::ReadPolicy;
51use mz_storage_types::sinks::{StorageSinkConnection, StorageSinkDesc};
52use mz_storage_types::sources::{
53    GenericSourceConnection, IngestionDescription, SourceDesc, SourceExportDataConfig,
54    SourceExportDetails, Timeline,
55};
56use serde::{Deserialize, Serialize};
57use timely::progress::Antichain;
58use timely::progress::frontier::MutableAntichain;
59use tokio::sync::{mpsc, oneshot};
60
61use crate::client::{AppendOnlyUpdate, StatusUpdate, TableData};
62use crate::statistics::WebhookStatistics;
63
64#[derive(
65    Clone,
66    Copy,
67    Debug,
68    Serialize,
69    Deserialize,
70    Eq,
71    PartialEq,
72    Hash,
73    PartialOrd,
74    Ord
75)]
76pub enum IntrospectionType {
77    /// We're not responsible for appending to this collection automatically, but we should
78    /// automatically bump the write frontier from time to time.
79    SinkStatusHistory,
80    SourceStatusHistory,
81    ShardMapping,
82
83    Frontiers,
84    ReplicaFrontiers,
85
86    ReplicaStatusHistory,
87    ReplicaMetricsHistory,
88    WallclockLagHistory,
89    WallclockLagHistogram,
90
91    // Note that this single-shard introspection source will be changed to per-replica,
92    // once we allow multiplexing multiple sources/sinks on a single cluster.
93    StorageSourceStatistics,
94    StorageSinkStatistics,
95
96    // The below are for statement logging.
97    StatementExecutionHistory,
98    SessionHistory,
99    PreparedStatementHistory,
100    SqlText,
101    // For statement lifecycle logging, which is closely related
102    // to statement logging
103    StatementLifecycleHistory,
104
105    // Collections written by the compute controller.
106    ComputeDependencies,
107    ComputeOperatorHydrationStatus,
108    ComputeMaterializedViewRefreshes,
109    ComputeErrorCounts,
110    ComputeHydrationTimes,
111    ComputeObjectArrangementSizes,
112
113    // Written by the Adapter for tracking AWS PrivateLink Connection Status History
114    PrivatelinkConnectionStatusHistory,
115}
116
117/// Describes how data is written to the collection.
118#[derive(Clone, Debug, Eq, PartialEq)]
119pub enum DataSource {
120    /// Ingest data from some external source.
121    Ingestion(IngestionDescription),
122    /// This source receives its data from the identified ingestion,
123    /// from an external object identified using `SourceExportDetails`.
124    ///
125    /// The referenced ingestion must be created before all of its exports.
126    IngestionExport {
127        ingestion_id: GlobalId,
128        details: SourceExportDetails,
129        data_config: SourceExportDataConfig,
130    },
131    /// Data comes from introspection sources, which the controller itself is
132    /// responsible for generating.
133    Introspection(IntrospectionType),
134    /// Data comes from the source's remapping/reclock operator.
135    Progress,
136    /// Data comes from external HTTP requests pushed to Materialize.
137    Webhook,
138    /// The adapter layer appends timestamped data, i.e. it is a `TABLE`.
139    Table,
140    /// This source's data does not need to be managed by the storage
141    /// controller, e.g. it's a materialized view or the catalog collection.
142    Other,
143    /// This collection is the output collection of a sink.
144    Sink { desc: ExportDescription },
145}
146
147/// Describes a request to create a source.
148#[derive(Clone, Debug, Eq, PartialEq)]
149pub struct CollectionDescription {
150    /// The schema of this collection
151    pub desc: RelationDesc,
152    /// The source of this collection's data.
153    pub data_source: DataSource,
154    /// An optional frontier to which the collection's `since` should be advanced.
155    pub since: Option<Antichain<Timestamp>>,
156    /// The timeline of the source. Absent for materialized views, etc.
157    pub timeline: Option<Timeline>,
158    /// The primary of this collections.
159    ///
160    /// Multiple storage collections can point to the same persist shard,
161    /// possibly with different schemas. In such a configuration, we select one
162    /// of the involved collections as the primary, who "owns" the persist
163    /// shard. All other involved collections have a dependency on the primary.
164    pub primary: Option<GlobalId>,
165}
166
167impl CollectionDescription {
168    /// Create a CollectionDescription for [`DataSource::Other`].
169    pub fn for_other(desc: RelationDesc, since: Option<Antichain<Timestamp>>) -> Self {
170        Self {
171            desc,
172            data_source: DataSource::Other,
173            since,
174            timeline: None,
175            primary: None,
176        }
177    }
178
179    /// Create a CollectionDescription for a table.
180    pub fn for_table(desc: RelationDesc) -> Self {
181        Self {
182            desc,
183            data_source: DataSource::Table,
184            since: None,
185            timeline: Some(Timeline::EpochMilliseconds),
186            primary: None,
187        }
188    }
189}
190
191#[derive(Clone, Debug, Eq, PartialEq)]
192pub struct ExportDescription<T = mz_repr::Timestamp> {
193    pub sink: StorageSinkDesc<(), T>,
194    /// The ID of the instance in which to install the export.
195    pub instance_id: StorageInstanceId,
196}
197
198#[derive(Debug)]
199pub enum Response {
200    FrontierUpdates(Vec<(GlobalId, Antichain<Timestamp>)>),
201}
202
203/// Metadata that the storage controller must know to properly handle the life
204/// cycle of creating and dropping collections.
205///
206/// This data should be kept consistent with the state modified using
207/// [`StorageTxn`].
208///
209/// n.b. the "txn WAL shard" is also metadata that's persisted, but if we
210/// included it in this struct it would never be read.
211#[derive(Debug, Clone, Serialize, Default)]
212pub struct StorageMetadata {
213    #[serde(serialize_with = "mz_ore::serde::map_key_to_string")]
214    pub collection_metadata: BTreeMap<GlobalId, ShardId>,
215    pub unfinalized_shards: BTreeSet<ShardId>,
216}
217
218impl StorageMetadata {
219    pub fn get_collection_shard(&self, id: GlobalId) -> Result<ShardId, StorageError> {
220        let shard_id = self
221            .collection_metadata
222            .get(&id)
223            .ok_or(StorageError::IdentifierMissing(id))?;
224
225        Ok(*shard_id)
226    }
227}
228
229/// Provides an interface for the storage controller to read and write data that
230/// is recorded elsewhere.
231///
232/// Data written to the implementor of this trait should make a consistent view
233/// of the data available through [`StorageMetadata`].
234#[async_trait]
235pub trait StorageTxn {
236    /// Retrieve all of the visible storage metadata.
237    ///
238    /// The value of this map should be treated as opaque.
239    fn get_collection_metadata(&self) -> BTreeMap<GlobalId, ShardId>;
240
241    /// Add new storage metadata for a collection.
242    ///
243    /// Subsequent calls to [`StorageTxn::get_collection_metadata`] must include
244    /// this data.
245    fn insert_collection_metadata(
246        &mut self,
247        s: BTreeMap<GlobalId, ShardId>,
248    ) -> Result<(), StorageError>;
249
250    /// Remove the metadata associated with the identified collections.
251    ///
252    /// Subsequent calls to [`StorageTxn::get_collection_metadata`] must not
253    /// include these keys.
254    fn delete_collection_metadata(&mut self, ids: BTreeSet<GlobalId>) -> Vec<(GlobalId, ShardId)>;
255
256    /// Retrieve the durable set of shards recorded as unfinalized.
257    ///
258    /// Entries must be reconciled with active collection metadata before
259    /// finalization because stale entries can refer to active collections.
260    fn get_unfinalized_shards(&self) -> BTreeSet<ShardId>;
261
262    /// Insert the specified values as unfinalized shards.
263    fn insert_unfinalized_shards(&mut self, s: BTreeSet<ShardId>) -> Result<(), StorageError>;
264
265    /// Removes the specified shards from the unfinalized shard collection.
266    ///
267    /// Missing shards are ignored. This only updates transaction metadata and
268    /// does not finalize the underlying Persist shards.
269    fn remove_unfinalized_shards(&mut self, shards: BTreeSet<ShardId>);
270
271    /// Get the txn WAL shard for this environment if it exists.
272    fn get_txn_wal_shard(&self) -> Option<ShardId>;
273
274    /// Store the specified shard as the environment's txn WAL shard.
275    ///
276    /// The implementor should error if the shard is already specified.
277    fn write_txn_wal_shard(&mut self, shard: ShardId) -> Result<(), StorageError>;
278}
279
280pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
281
282/// A predicate for a `Row` filter.
283pub type RowPredicate = Box<dyn Fn(&Row) -> bool + Send + Sync>;
284
285/// High-level write operations applicable to storage collections.
286pub enum StorageWriteOp {
287    /// Append a set of rows with specified multiplicities.
288    ///
289    /// The multiplicities may be negative, so an `Append` operation can perform
290    /// both insertions and retractions.
291    Append { updates: Vec<(Row, Diff)> },
292    /// Delete all rows matching the given predicate.
293    Delete { filter: RowPredicate },
294}
295
296impl StorageWriteOp {
297    /// Returns whether this operation appends an empty set of updates.
298    pub fn is_empty_append(&self) -> bool {
299        match self {
300            Self::Append { updates } => updates.is_empty(),
301            Self::Delete { .. } => false,
302        }
303    }
304}
305
306/// Metadata required to register a table with the txns shard.
307#[derive(Debug, Clone)]
308pub struct TableRegistration {
309    pub id: GlobalId,
310    pub data_shard: ShardId,
311    pub relation_desc: RelationDesc,
312}
313
314/// Queues txns-shard operations on the storage table worker.
315///
316/// The adapter's group committer is the sole runtime caller, preserving FIFO order across appends,
317/// registrations, and forgets. On [`StorageError::InvalidUppers`], the writable implementation
318/// restores its bookkeeping and the caller must retry at a fresh timestamp.
319pub trait TableWriteHandle: Debug + Send + Sync {
320    /// Appends `commands` at `write_ts` and advances all registered tables to `advance_to`.
321    fn append(
322        &self,
323        write_ts: Timestamp,
324        advance_to: Timestamp,
325        commands: Vec<(GlobalId, Vec<TableData>)>,
326    ) -> oneshot::Receiver<Result<(), StorageError>>;
327
328    /// Registers `tables` at `register_ts`.
329    fn register(
330        &self,
331        register_ts: Timestamp,
332        tables: Vec<TableRegistration>,
333    ) -> oneshot::Receiver<Result<(), StorageError>>;
334
335    /// Forgets registered `ids` at `forget_ts`, ignoring unknown IDs.
336    fn forget(
337        &self,
338        forget_ts: Timestamp,
339        ids: Vec<GlobalId>,
340    ) -> oneshot::Receiver<Result<(), StorageError>>;
341}
342
343#[async_trait(?Send)]
344pub trait StorageController: Debug {
345    /// Marks the end of any initialization commands.
346    ///
347    /// The implementor may wait for this method to be called before implementing prior commands,
348    /// and so it is important for a user to invoke this method as soon as it is comfortable.
349    /// This method can be invoked immediately, at the potential expense of performance.
350    fn initialization_complete(&mut self);
351
352    /// Update storage configuration with new parameters.
353    fn update_parameters(&mut self, config_params: StorageParameters);
354
355    /// Replaces the per-replica dyncfg overrides for the given instances.
356    ///
357    /// This only stores the overrides; callers should follow with a
358    /// configuration push (e.g. [`Self::update_parameters`]) so existing
359    /// replicas observe the new values. Instances absent from `overrides` have
360    /// their overrides cleared, so a replica that no longer has an override
361    /// reverts to the environment-wide configuration. Used by the scoped
362    /// feature flags (replica-local) layer.
363    fn update_replica_dyncfg_overrides(
364        &mut self,
365        overrides: BTreeMap<StorageInstanceId, BTreeMap<ReplicaId, ConfigUpdates>>,
366    );
367
368    /// Get the current configuration, including parameters updated with `update_parameters`.
369    fn config(&self) -> &StorageConfiguration;
370
371    /// Returns the [CollectionMetadata] of the collection identified by `id`.
372    fn collection_metadata(&self, id: GlobalId) -> Result<CollectionMetadata, CollectionMissing>;
373
374    /// Returns `true` iff the given collection/ingestion has been hydrated.
375    ///
376    /// For this check, zero-replica clusters are always considered hydrated.
377    /// Their collections would never normally be considered hydrated but it's
378    /// clearly intentional that they have no replicas.
379    fn collection_hydrated(&self, collection_id: GlobalId) -> Result<bool, StorageError>;
380
381    /// Returns `true` if each non-transient, non-excluded collection is
382    /// hydrated on at least one of the provided replicas.
383    ///
384    /// Collections that are not scheduled on any of the provided replicas do
385    /// not count against hydration: a single-replica source keeps running on
386    /// its current replica and can never hydrate on a replica it is not
387    /// scheduled on.
388    ///
389    /// If no replicas are provided, this checks for hydration on _any_ replica.
390    ///
391    /// This also returns `true` in case this cluster does not have any
392    /// replicas.
393    fn collections_hydrated_on_replicas(
394        &self,
395        target_replica_ids: Option<Vec<ReplicaId>>,
396        target_cluster_ids: &StorageInstanceId,
397        exclude_collections: &BTreeSet<GlobalId>,
398    ) -> Result<bool, StorageError>;
399
400    /// Returns the since/upper frontiers of the identified collection.
401    fn collection_frontiers(
402        &self,
403        id: GlobalId,
404    ) -> Result<(Antichain<Timestamp>, Antichain<Timestamp>), CollectionMissing>;
405
406    /// Returns the since/upper frontiers of the identified collections.
407    ///
408    /// Having a method that returns both frontiers at the same time, for all
409    /// requested collections, ensures that we can get a consistent "snapshot"
410    /// of collection state. If we had separate methods instead, and/or would
411    /// allow getting frontiers for collections one at a time, it could happen
412    /// that collection state changes concurrently, while information is
413    /// gathered.
414    fn collections_frontiers(
415        &self,
416        id: Vec<GlobalId>,
417    ) -> Result<Vec<(GlobalId, Antichain<Timestamp>, Antichain<Timestamp>)>, CollectionMissing>;
418
419    /// Acquire an iterator over [CollectionMetadata] for all active
420    /// collections.
421    ///
422    /// A collection is "active" when it has a non-empty frontier of read
423    /// capabilities.
424    fn active_collection_metadatas(&self) -> Vec<(GlobalId, CollectionMetadata)>;
425
426    /// Returns the IDs of ingestion exports running on the given instance. This
427    /// includes the ingestion itself, if any, and running source tables (aka.
428    /// subsources).
429    fn active_ingestion_exports(
430        &self,
431        instance_id: StorageInstanceId,
432    ) -> Box<dyn Iterator<Item = &GlobalId> + '_>;
433
434    /// Checks whether a collection exists under the given `GlobalId`. Returns
435    /// an error if the collection does not exist.
436    fn check_exists(&self, id: GlobalId) -> Result<(), StorageError>;
437
438    /// Creates a storage instance with the specified ID.
439    ///
440    /// A storage instance can have zero or one replicas. The instance is
441    /// created with zero replicas.
442    ///
443    /// Panics if a storage instance with the given ID already exists.
444    fn create_instance(&mut self, id: StorageInstanceId, workload_class: Option<String>);
445
446    /// Drops the storage instance with the given ID.
447    ///
448    /// If you call this method while the storage instance has a replica
449    /// attached, that replica will be leaked. Call `drop_replica` first.
450    ///
451    /// Panics if a storage instance with the given ID does not exist.
452    fn drop_instance(&mut self, id: StorageInstanceId);
453
454    /// Updates a storage instance's workload class.
455    fn update_instance_workload_class(
456        &mut self,
457        id: StorageInstanceId,
458        workload_class: Option<String>,
459    );
460
461    /// Connects the storage instance to the specified replica.
462    ///
463    /// If the storage instance is already attached to a replica, communication
464    /// with that replica is severed in favor of the new replica.
465    ///
466    /// In the future, this API will be adjusted to support active replication
467    /// of storage instances (i.e., multiple replicas attached to a given
468    /// storage instance).
469    fn connect_replica(
470        &mut self,
471        instance_id: StorageInstanceId,
472        replica_id: ReplicaId,
473        location: ClusterReplicaLocation,
474    );
475
476    /// Disconnects the storage instance from the specified replica.
477    fn drop_replica(&mut self, instance_id: StorageInstanceId, replica_id: ReplicaId);
478
479    /// Across versions of Materialize the nullability of columns for some objects can change based
480    /// on updates to our optimizer.
481    ///
482    /// During bootstrap we will register these new schemas with Persist.
483    ///
484    /// See: <https://github.com/MaterializeInc/database-issues/issues/2488>
485    async fn evolve_nullability_for_bootstrap(
486        &mut self,
487        storage_metadata: &StorageMetadata,
488        collections: Vec<(GlobalId, RelationDesc)>,
489    ) -> Result<(), StorageError>;
490
491    /// Create the sources described in the individual RunIngestionCommand commands.
492    ///
493    /// Each command carries the source id, the source description, and any associated metadata
494    /// needed to ingest the particular source.
495    ///
496    /// This command installs collection state for the indicated sources, and they are
497    /// now valid to use in queries at times beyond the initial `since` frontiers. Each
498    /// collection also acquires a read capability at this frontier, which will need to
499    /// be repeatedly downgraded with `allow_compaction()` to permit compaction.
500    ///
501    /// This method is NOT idempotent; It can fail between processing of different
502    /// collections and leave the controller in an inconsistent state. It is almost
503    /// always wrong to do anything but abort the process on `Err`.
504    ///
505    /// The `register_ts` is the initial timestamp at which tables become available for reads. (We
506    /// might later give non-tables the same treatment, but hold off on that initially.) Callers
507    /// must provide a Some if any of the collections is a table. A None may be given if none of the
508    /// collections are a table (i.e. all materialized views, sources, etc).
509    ///
510    /// This sets up storage but does not register tables in the txns shard. Runtime registration
511    /// must go through the adapter's group committer. Bootstrap uses
512    /// [`Self::register_table_collections`].
513    async fn create_collections(
514        &mut self,
515        storage_metadata: &StorageMetadata,
516        register_ts: Option<Timestamp>,
517        collections: Vec<(GlobalId, CollectionDescription)>,
518    ) -> Result<(), StorageError> {
519        self.create_collections_for_bootstrap(
520            storage_metadata,
521            register_ts,
522            collections,
523            &BTreeSet::new(),
524        )
525        .await
526    }
527
528    /// Like [`Self::create_collections`], except used specifically for bootstrap.
529    ///
530    /// `migrated_storage_collections` is a set of migrated storage collections to be excluded
531    /// from the txn-wal sub-system.
532    async fn create_collections_for_bootstrap(
533        &mut self,
534        storage_metadata: &StorageMetadata,
535        register_ts: Option<Timestamp>,
536        collections: Vec<(GlobalId, CollectionDescription)>,
537        migrated_storage_collections: &BTreeSet<GlobalId>,
538    ) -> Result<(), StorageError>;
539
540    /// Check that the ingestion associated with `id` can use the provided
541    /// [`SourceDesc`].
542    ///
543    /// Note that this check is optimistic and its return of `Ok(())` does not
544    /// guarantee that subsequent calls to `alter_ingestion_source_desc` are
545    /// guaranteed to succeed.
546    fn check_alter_ingestion_source_desc(
547        &mut self,
548        ingestion_id: GlobalId,
549        source_desc: &SourceDesc,
550    ) -> Result<(), StorageError>;
551
552    /// Alters each identified ingestion to use the correlated [`SourceDesc`].
553    async fn alter_ingestion_source_desc(
554        &mut self,
555        ingestion_ids: BTreeMap<GlobalId, SourceDesc>,
556    ) -> Result<(), StorageError>;
557
558    /// Alters each identified collection to use the correlated [`GenericSourceConnection`].
559    async fn alter_ingestion_connections(
560        &mut self,
561        source_connections: BTreeMap<GlobalId, GenericSourceConnection<InlinedConnection>>,
562    ) -> Result<(), StorageError>;
563
564    /// Alters the data config for the specified source exports of the specified ingestions.
565    async fn alter_ingestion_export_data_configs(
566        &mut self,
567        source_exports: BTreeMap<GlobalId, SourceExportDataConfig>,
568    ) -> Result<(), StorageError>;
569
570    /// Evolves a table's schema without registering the new collection in the txns shard.
571    ///
572    /// Runtime registration must go through the adapter's group committer.
573    async fn alter_table_desc(
574        &mut self,
575        existing_collection: GlobalId,
576        new_collection: GlobalId,
577        new_desc: RelationDesc,
578        expected_version: RelationVersion,
579    ) -> Result<(), StorageError>;
580
581    /// Registers the `DataSource::Table` collections among `ids` during bootstrap.
582    ///
583    /// Runtime registration must go through the adapter's group committer. In read-only mode, only
584    /// migrated tables are registered.
585    async fn register_table_collections(
586        &mut self,
587        register_ts: Timestamp,
588        ids: Vec<GlobalId>,
589    ) -> Result<(), StorageError>;
590
591    /// Returns registration metadata for the `DataSource::Table` collections among `ids`.
592    ///
593    /// Other data sources are ignored.
594    fn table_registrations(
595        &self,
596        ids: Vec<GlobalId>,
597    ) -> Result<Vec<TableRegistration>, StorageError>;
598
599    /// Returns the `DataSource::Table` IDs among `ids`.
600    fn txns_table_ids(&self, ids: Vec<GlobalId>) -> Result<Vec<GlobalId>, StorageError>;
601
602    /// Acquire an immutable reference to the export state, should it exist.
603    fn export(&self, id: GlobalId) -> Result<&ExportState, StorageError>;
604
605    /// Acquire a mutable reference to the export state, should it exist.
606    fn export_mut(&mut self, id: GlobalId) -> Result<&mut ExportState, StorageError>;
607
608    /// Create a oneshot ingestion.
609    async fn create_oneshot_ingestion(
610        &mut self,
611        ingestion_id: uuid::Uuid,
612        collection_id: GlobalId,
613        instance_id: StorageInstanceId,
614        request: OneshotIngestionRequest,
615        result_tx: OneshotResultCallback<ProtoBatch>,
616    ) -> Result<(), StorageError>;
617
618    /// Cancel a oneshot ingestion.
619    fn cancel_oneshot_ingestion(&mut self, ingestion_id: uuid::Uuid) -> Result<(), StorageError>;
620
621    /// Alter the sink identified by the given id to match the provided `ExportDescription`.
622    async fn alter_export(
623        &mut self,
624        id: GlobalId,
625        export: ExportDescription,
626    ) -> Result<(), StorageError>;
627
628    /// For each identified export, alter its [`StorageSinkConnection`].
629    async fn alter_export_connections(
630        &mut self,
631        exports: BTreeMap<GlobalId, StorageSinkConnection>,
632    ) -> Result<(), StorageError>;
633
634    /// Schedules controller cleanup for tables.
635    ///
636    /// The txn-wal tables among `identifiers` must first be forgotten through the adapter's group
637    /// committer.
638    fn drop_tables(
639        &mut self,
640        storage_metadata: &StorageMetadata,
641        identifiers: Vec<GlobalId>,
642    ) -> Result<(), StorageError>;
643
644    /// Drops the read capability for the sources and allows their resources to be reclaimed.
645    fn drop_sources(
646        &mut self,
647        storage_metadata: &StorageMetadata,
648        identifiers: Vec<GlobalId>,
649    ) -> Result<(), StorageError>;
650
651    /// Drops the read capability for the sinks and allows their resources to be reclaimed.
652    fn drop_sinks(
653        &mut self,
654        storage_metadata: &StorageMetadata,
655        identifiers: Vec<GlobalId>,
656    ) -> Result<(), StorageError>;
657
658    /// Drops the read capability for the sinks and allows their resources to be reclaimed.
659    ///
660    /// TODO(jkosh44): This method does not validate the provided identifiers. Currently when the
661    ///     controller starts/restarts it has no durable state. That means that it has no way of
662    ///     remembering any past commands sent. In the future we plan on persisting state for the
663    ///     controller so that it is aware of past commands.
664    ///     Therefore this method is for dropping sinks that we know to have been previously
665    ///     created, but have been forgotten by the controller due to a restart.
666    ///     Once command history becomes durable we can remove this method and use the normal
667    ///     `drop_sinks`.
668    fn drop_sinks_unvalidated(
669        &mut self,
670        storage_metadata: &StorageMetadata,
671        identifiers: Vec<GlobalId>,
672    );
673
674    /// Drops the read capability for the sources and allows their resources to be reclaimed.
675    ///
676    /// TODO(jkosh44): This method does not validate the provided identifiers. Currently when the
677    ///     controller starts/restarts it has no durable state. That means that it has no way of
678    ///     remembering any past commands sent. In the future we plan on persisting state for the
679    ///     controller so that it is aware of past commands.
680    ///     Therefore this method is for dropping sources that we know to have been previously
681    ///     created, but have been forgotten by the controller due to a restart.
682    ///     Once command history becomes durable we can remove this method and use the normal
683    ///     `drop_sources`.
684    fn drop_sources_unvalidated(
685        &mut self,
686        storage_metadata: &StorageMetadata,
687        identifiers: Vec<GlobalId>,
688    ) -> Result<(), StorageError>;
689
690    /// Appends to tables during bootstrap.
691    ///
692    /// Runtime writes must go through the adapter's group committer. The returned receiver resolves
693    /// when the atomic write completes.
694    fn append_table(
695        &mut self,
696        write_ts: Timestamp,
697        advance_to: Timestamp,
698        commands: Vec<(GlobalId, Vec<TableData>)>,
699    ) -> Result<tokio::sync::oneshot::Receiver<Result<(), StorageError>>, StorageError>;
700
701    /// Returns the process-lifetime storage mechanism used by the adapter's group committer.
702    fn table_write_handle(&self) -> Arc<dyn TableWriteHandle>;
703
704    /// Returns a [`MonotonicAppender`] which is a channel that can be used to monotonically
705    /// append to the specified [`GlobalId`].
706    fn monotonic_appender(&self, id: GlobalId) -> Result<MonotonicAppender, StorageError>;
707
708    /// Returns a shared [`WebhookStatistics`] which can be used to report user-facing
709    /// statistics for this given webhhook, specified by the [`GlobalId`].
710    ///
711    // This is used to support a fairly special case, where a source needs to report statistics
712    // from outside the ordinary controller-clusterd path. Its possible to merge this with
713    // `monotonic_appender`, whose only current user is webhooks, but given that they will
714    // likely be moved to clusterd, we just leave this a special case.
715    fn webhook_statistics(&self, id: GlobalId) -> Result<Arc<WebhookStatistics>, StorageError>;
716
717    /// Waits until the controller is ready to process a response.
718    ///
719    /// This method may block for an arbitrarily long time.
720    ///
721    /// When the method returns, the owner should call
722    /// [`StorageController::process`] to process the ready message.
723    ///
724    /// This method is cancellation safe.
725    async fn ready(&mut self);
726
727    /// Processes the work queued by [`StorageController::ready`].
728    fn process(
729        &mut self,
730        storage_metadata: &StorageMetadata,
731    ) -> Result<Option<Response>, anyhow::Error>;
732
733    /// Exposes the internal state of the data shard for debugging and QA.
734    ///
735    /// We'll be thoughtful about making unnecessary changes, but the **output
736    /// of this method needs to be gated from users**, so that it's not subject
737    /// to our backward compatibility guarantees.
738    ///
739    /// TODO: Ideally this would return `impl Serialize` so the caller can do
740    /// with it what they like, but that doesn't work in traits yet. The
741    /// workaround (an associated type) doesn't work because persist doesn't
742    /// want to make the type public. In the meantime, move the `serde_json`
743    /// call from the single user into this method.
744    async fn inspect_persist_state(&self, id: GlobalId)
745    -> Result<serde_json::Value, anyhow::Error>;
746
747    /// Records append-only updates for the given introspection type.
748    ///
749    /// Rows passed in `updates` MUST have the correct schema for the given
750    /// introspection type, as readers rely on this and might panic otherwise.
751    fn append_introspection_updates(&mut self, type_: IntrospectionType, updates: Vec<(Row, Diff)>);
752
753    /// Records append-only status updates for the given introspection type.
754    fn append_status_introspection_updates(
755        &mut self,
756        type_: IntrospectionType,
757        updates: Vec<StatusUpdate>,
758    );
759
760    /// Updates the desired state of the given introspection type.
761    ///
762    /// Rows passed in `op` MUST have the correct schema for the given
763    /// introspection type, as readers rely on this and might panic otherwise.
764    fn update_introspection_collection(&mut self, type_: IntrospectionType, op: StorageWriteOp);
765
766    /// Returns a sender for updates to the specified append-only introspection collection.
767    ///
768    /// # Panics
769    ///
770    /// Panics if the given introspection type is not associated with an append-only collection.
771    fn append_only_introspection_tx(
772        &self,
773        type_: IntrospectionType,
774    ) -> mpsc::UnboundedSender<(
775        Vec<AppendOnlyUpdate>,
776        oneshot::Sender<Result<(), StorageError>>,
777    )>;
778
779    /// Returns a sender for updates to the specified differential introspection collection.
780    ///
781    /// # Panics
782    ///
783    /// Panics if the given introspection type is not associated with a differential collection.
784    fn differential_introspection_tx(
785        &self,
786        type_: IntrospectionType,
787    ) -> mpsc::UnboundedSender<(StorageWriteOp, oneshot::Sender<Result<(), StorageError>>)>;
788
789    async fn real_time_recent_timestamp(
790        &self,
791        source_ids: BTreeSet<GlobalId>,
792        timeout: Duration,
793    ) -> Result<BoxFuture<Result<Timestamp, StorageError>>, StorageError>;
794
795    /// Returns the state of the [`StorageController`] formatted as JSON.
796    fn dump(&self) -> Result<serde_json::Value, anyhow::Error>;
797}
798
799impl DataSource {
800    /// Returns true if the storage controller manages the data shard for this
801    /// source using txn-wal.
802    pub fn in_txns(&self) -> bool {
803        match self {
804            DataSource::Table => true,
805            DataSource::Other
806            | DataSource::Ingestion(_)
807            | DataSource::IngestionExport { .. }
808            | DataSource::Introspection(_)
809            | DataSource::Progress
810            | DataSource::Webhook => false,
811            DataSource::Sink { .. } => false,
812        }
813    }
814}
815
816/// A wrapper struct that presents the adapter token to a format that is understandable by persist
817/// and also allows us to differentiate between a token being present versus being set for the
818/// first time.
819#[derive(PartialEq, Clone, Debug, Default)]
820pub struct PersistEpoch(pub Option<NonZeroI64>);
821
822impl Codec64 for PersistEpoch {
823    fn codec_name() -> String {
824        "PersistEpoch".to_owned()
825    }
826
827    fn encode(&self) -> [u8; 8] {
828        self.0.map(NonZeroI64::get).unwrap_or(0).to_le_bytes()
829    }
830
831    fn decode(buf: [u8; 8]) -> Self {
832        Self(NonZeroI64::new(i64::from_le_bytes(buf)))
833    }
834}
835
836impl From<NonZeroI64> for PersistEpoch {
837    fn from(epoch: NonZeroI64) -> Self {
838        Self(Some(epoch))
839    }
840}
841
842/// State maintained about individual exports.
843#[derive(Debug)]
844pub struct ExportState {
845    /// Really only for keeping track of changes to the `derived_since`.
846    pub read_capabilities: MutableAntichain<Timestamp>,
847
848    /// The cluster this export is associated with.
849    pub cluster_id: StorageInstanceId,
850
851    /// The current since frontier, derived from `write_frontier` using
852    /// `hold_policy`.
853    pub derived_since: Antichain<Timestamp>,
854
855    /// The read holds that this export has on its dependencies (its input and itself). When
856    /// the upper of the export changes, we downgrade this, which in turn
857    /// downgrades holds we have on our dependencies' sinces.
858    pub read_holds: [ReadHold; 2],
859
860    /// The policy to use to downgrade `self.read_capability`.
861    pub read_policy: ReadPolicy,
862
863    /// Reported write frontier.
864    pub write_frontier: Antichain<Timestamp>,
865}
866
867impl ExportState {
868    pub fn new(
869        cluster_id: StorageInstanceId,
870        read_hold: ReadHold,
871        self_hold: ReadHold,
872        write_frontier: Antichain<Timestamp>,
873        read_policy: ReadPolicy,
874    ) -> Self {
875        let mut dependency_since = Antichain::from_elem(Timestamp::MIN);
876        for read_hold in [&read_hold, &self_hold] {
877            dependency_since.join_assign(read_hold.since());
878        }
879        Self {
880            read_capabilities: MutableAntichain::from(dependency_since.borrow()),
881            cluster_id,
882            derived_since: dependency_since,
883            read_holds: [read_hold, self_hold],
884            read_policy,
885            write_frontier,
886        }
887    }
888
889    /// Returns the cluster to which the export is bound.
890    pub fn cluster_id(&self) -> StorageInstanceId {
891        self.cluster_id
892    }
893
894    /// Returns the cluster to which the export is bound.
895    pub fn input_hold(&self) -> &ReadHold {
896        &self.read_holds[0]
897    }
898
899    /// Returns whether the export was dropped.
900    pub fn is_dropped(&self) -> bool {
901        self.read_holds.iter().all(|h| h.since().is_empty())
902    }
903}
904/// A channel that allows you to append a set of updates to a pre-defined [`GlobalId`].
905///
906/// See `CollectionManager::monotonic_appender` to acquire a [`MonotonicAppender`].
907#[derive(Clone, Debug)]
908pub struct MonotonicAppender {
909    /// Channel that sends to a [`tokio::task`] which pushes updates to Persist.
910    tx: mpsc::UnboundedSender<(
911        Vec<AppendOnlyUpdate>,
912        oneshot::Sender<Result<(), StorageError>>,
913    )>,
914}
915
916impl MonotonicAppender {
917    pub fn new(
918        tx: mpsc::UnboundedSender<(
919            Vec<AppendOnlyUpdate>,
920            oneshot::Sender<Result<(), StorageError>>,
921        )>,
922    ) -> Self {
923        MonotonicAppender { tx }
924    }
925
926    pub async fn append(&self, updates: Vec<AppendOnlyUpdate>) -> Result<(), StorageError> {
927        let (tx, rx) = oneshot::channel();
928
929        // Send our update to the CollectionManager.
930        self.tx
931            .send((updates, tx))
932            .map_err(|_| StorageError::ShuttingDown("collection manager"))?;
933
934        // Wait for a response, if we fail to receive then the CollectionManager has gone away.
935        let result = rx
936            .await
937            .map_err(|_| StorageError::ShuttingDown("collection manager"))?;
938
939        result
940    }
941}
942
943/// A wallclock lag measurement.
944///
945/// The enum representation reflects the fact that wallclock lag is undefined for unreadable
946/// collections, i.e. collections that contain no readable times.
947#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
948pub enum WallclockLag {
949    /// Lag value in seconds, for readable collections.
950    Seconds(u64),
951    /// Undefined lag, for unreadable collections.
952    Undefined,
953}
954
955impl WallclockLag {
956    /// The smallest possible wallclock lag measurement.
957    pub const MIN: Self = Self::Seconds(0);
958
959    /// Return the maximum of two lag values.
960    ///
961    /// We treat `Undefined` lags as greater than `Seconds`, to ensure we never report low lag
962    /// values when a collection was actually unreadable for some amount of time.
963    pub fn max(self, other: Self) -> Self {
964        match (self, other) {
965            (Self::Seconds(a), Self::Seconds(b)) => Self::Seconds(a.max(b)),
966            (Self::Undefined, _) | (_, Self::Undefined) => Self::Undefined,
967        }
968    }
969
970    /// Return the wrapped seconds value, or a default if the lag is `Undefined`.
971    pub fn unwrap_seconds_or(self, default: u64) -> u64 {
972        match self {
973            Self::Seconds(s) => s,
974            Self::Undefined => default,
975        }
976    }
977
978    /// Create a new `WallclockLag` by transforming the wrapped seconds value.
979    pub fn map_seconds(self, f: impl FnOnce(u64) -> u64) -> Self {
980        match self {
981            Self::Seconds(s) => Self::Seconds(f(s)),
982            Self::Undefined => Self::Undefined,
983        }
984    }
985
986    /// Convert this lag value into a [`Datum::Interval`] or [`Datum::Null`].
987    pub fn into_interval_datum(self) -> Datum<'static> {
988        match self {
989            Self::Seconds(secs) => {
990                let micros = i64::try_from(secs * 1_000_000).expect("must fit");
991                Datum::Interval(Interval::new(0, 0, micros))
992            }
993            Self::Undefined => Datum::Null,
994        }
995    }
996
997    /// Convert this lag value into a [`Datum::UInt64`] or [`Datum::Null`].
998    pub fn into_uint64_datum(self) -> Datum<'static> {
999        match self {
1000            Self::Seconds(secs) => Datum::UInt64(secs),
1001            Self::Undefined => Datum::Null,
1002        }
1003    }
1004}
1005
1006/// The period covered by a wallclock lag histogram, represented as a `[start, end)` range.
1007#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1008pub struct WallclockLagHistogramPeriod {
1009    pub start: CheckedTimestamp<DateTime<Utc>>,
1010    pub end: CheckedTimestamp<DateTime<Utc>>,
1011}
1012
1013impl WallclockLagHistogramPeriod {
1014    /// Construct a `WallclockLagHistogramPeriod` from the given epoch timestamp and dyncfg.
1015    pub fn from_epoch_millis(epoch_ms: u64, dyncfg: &ConfigSet) -> Self {
1016        let interval = WALLCLOCK_LAG_HISTOGRAM_PERIOD_INTERVAL.get(dyncfg);
1017        let interval_ms = u64::try_from(interval.as_millis()).unwrap_or_else(|_| {
1018            soft_panic_or_log!("excessive wallclock lag histogram period interval: {interval:?}");
1019            let default = WALLCLOCK_LAG_HISTOGRAM_PERIOD_INTERVAL.default();
1020            u64::try_from(default.as_millis()).unwrap()
1021        });
1022        let interval_ms = std::cmp::max(interval_ms, 1);
1023
1024        let start_ms = epoch_ms - (epoch_ms % interval_ms);
1025        let start_dt = mz_ore::now::to_datetime(start_ms);
1026        let start = start_dt.try_into().expect("must fit");
1027
1028        let end_ms = start_ms + interval_ms;
1029        let end_dt = mz_ore::now::to_datetime(end_ms);
1030        let end = end_dt.try_into().expect("must fit");
1031
1032        Self { start, end }
1033    }
1034}
1035
1036#[cfg(test)]
1037mod tests {
1038    use super::*;
1039
1040    #[mz_ore::test]
1041    fn lag_writes_by_zero() {
1042        let policy =
1043            ReadPolicy::lag_writes_by(mz_repr::Timestamp::default(), mz_repr::Timestamp::default());
1044        let write_frontier = Antichain::from_elem(mz_repr::Timestamp::from(5));
1045        assert_eq!(policy.frontier(write_frontier.borrow()), write_frontier);
1046    }
1047}