Skip to main content

mz_adapter/
coord.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//! Translation of SQL commands into timestamped `Controller` commands.
11//!
12//! The various SQL commands instruct the system to take actions that are not
13//! yet explicitly timestamped. On the other hand, the underlying data continually
14//! change as time moves forward. On the third hand, we greatly benefit from the
15//! information that some times are no longer of interest, so that we may
16//! compact the representation of the continually changing collections.
17//!
18//! The [`Coordinator`] curates these interactions by observing the progress
19//! collections make through time, choosing timestamps for its own commands,
20//! and eventually communicating that certain times have irretrievably "passed".
21//!
22//! ## Frontiers another way
23//!
24//! If the above description of frontiers left you with questions, this
25//! repackaged explanation might help.
26//!
27//! - `since` is the least recent time (i.e. oldest time) that you can read
28//!   from sources and be guaranteed that the returned data is accurate as of
29//!   that time.
30//!
31//!   Reads at times less than `since` may return values that were not actually
32//!   seen at the specified time, but arrived later (i.e. the results are
33//!   compacted).
34//!
35//!   For correctness' sake, the coordinator never chooses to read at a time
36//!   less than an arrangement's `since`.
37//!
38//! - `upper` is the first time after the most recent time that you can read
39//!   from sources and receive an immediate response. Alternately, it is the
40//!   least time at which the data may still change (that is the reason we may
41//!   not be able to respond immediately).
42//!
43//!   Reads at times >= `upper` may not immediately return because the answer
44//!   isn't known yet. However, once the `upper` is > the specified read time,
45//!   the read can return.
46//!
47//!   For the sake of returned values' freshness, the coordinator prefers
48//!   performing reads at an arrangement's `upper`. However, because we more
49//!   strongly prefer correctness, the coordinator will choose timestamps
50//!   greater than an object's `upper` if it is also being accessed alongside
51//!   objects whose `since` times are >= its `upper`.
52//!
53//! This illustration attempts to show, with time moving left to right, the
54//! relationship between `since` and `upper`.
55//!
56//! - `#`: possibly inaccurate results
57//! - `-`: immediate, correct response
58//! - `?`: not yet known
59//! - `s`: since
60//! - `u`: upper
61//! - `|`: eligible for coordinator to select
62//!
63//! ```nofmt
64//! ####s----u?????
65//!     |||||||||||
66//! ```
67//!
68
69use std::borrow::Cow;
70use std::collections::{BTreeMap, BTreeSet, VecDeque};
71use std::net::IpAddr;
72use std::num::NonZeroI64;
73use std::ops::Neg;
74use std::str::FromStr;
75use std::sync::LazyLock;
76use std::sync::{Arc, Mutex};
77use std::thread;
78use std::time::{Duration, Instant};
79use std::{fmt, mem};
80
81use anyhow::Context;
82use chrono::{DateTime, Utc};
83use derivative::Derivative;
84use differential_dataflow::lattice::Lattice;
85use fail::fail_point;
86use futures::StreamExt;
87use futures::future::{BoxFuture, FutureExt, LocalBoxFuture};
88use http::Uri;
89use ipnet::IpNet;
90use itertools::Itertools;
91use mz_adapter_types::bootstrap_builtin_cluster_config::BootstrapBuiltinClusterConfig;
92use mz_adapter_types::compaction::CompactionWindow;
93use mz_adapter_types::connection::ConnectionId;
94use mz_adapter_types::dyncfgs::FRONTEND_READ_THEN_WRITE;
95use mz_adapter_types::dyncfgs::{
96    USER_ID_POOL_BATCH_SIZE, WITH_0DT_DEPLOYMENT_CAUGHT_UP_CHECK_INTERVAL,
97};
98use mz_auth::password::Password;
99use mz_build_info::BuildInfo;
100use mz_catalog::builtin::{
101    BUILTINS, BUILTINS_STATIC, MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY, MZ_STORAGE_USAGE_BY_SHARD,
102};
103use mz_catalog::config::{AwsPrincipalContext, BuiltinItemMigrationConfig, ClusterReplicaSizeMap};
104use mz_catalog::durable::OpenableDurableCatalogState;
105use mz_catalog::expr_cache::{GlobalExpressions, LocalExpressions};
106use mz_catalog::memory::objects::{
107    CatalogEntry, CatalogItem, ClusterReplicaProcessStatus, ClusterVariantManaged, Connection,
108    DataSourceDesc, ReconfigurationTarget, Table, TableDataSource,
109};
110use mz_cloud_resources::{CloudResourceController, VpcEndpointConfig, VpcEndpointEvent};
111use mz_compute_client::as_of_selection;
112use mz_compute_client::controller::error::{
113    CollectionLookupError, CollectionMissing, DataflowCreationError, InstanceMissing,
114};
115use mz_compute_types::ComputeInstanceId;
116use mz_compute_types::dataflows::DataflowDescription;
117use mz_compute_types::plan::LirRelationExpr;
118use mz_controller::clusters::{
119    ClusterConfig, ClusterEvent, ClusterStatus, ProcessId, ReplicaLocation,
120};
121use mz_controller::{ControllerConfig, Readiness};
122use mz_controller_types::{ClusterId, ReplicaId, WatchSetId};
123use mz_dyncfg::{ConfigUpdates, ParameterScope};
124use mz_expr::{MapFilterProject, MirRelationExpr, OptimizedMirRelationExpr, RowSetFinishing};
125use mz_license_keys::{ExpirationBehavior, ValidatedLicenseKey};
126use mz_orchestrator::OfflineReason;
127use mz_ore::cast::{CastFrom, CastInto, CastLossy};
128use mz_ore::channel::trigger::Trigger;
129use mz_ore::future::TimeoutError;
130use mz_ore::metrics::MetricsRegistry;
131use mz_ore::now::{EpochMillis, NowFn};
132use mz_ore::task::{JoinHandle, spawn};
133use mz_ore::thread::JoinHandleExt;
134use mz_ore::tracing::{OpenTelemetryContext, TracingHandle};
135use mz_ore::url::SensitiveUrl;
136use mz_ore::{
137    assert_none, instrument, soft_assert_eq_or_log, soft_assert_or_log, soft_panic_or_log, stack,
138};
139use mz_persist_client::PersistClient;
140use mz_persist_client::batch::ProtoBatch;
141use mz_persist_client::usage::{ShardsUsageReferenced, StorageUsageClient};
142use mz_repr::adt::numeric::Numeric;
143use mz_repr::explain::{ExplainConfig, ExplainFormat};
144use mz_repr::global_id::TransientIdGen;
145use mz_repr::optimize::{OptimizerFeatureOverrides, OptimizerFeatures, OverrideFrom};
146use mz_repr::role_id::RoleId;
147use mz_repr::{CatalogItemId, Diff, GlobalId, RelationDesc, SqlRelationType, Timestamp};
148use mz_secrets::cache::CachingSecretsReader;
149use mz_secrets::{SecretsController, SecretsReader};
150use mz_sql::ast::{Raw, Statement};
151use mz_sql::catalog::{CatalogCluster, EnvironmentId};
152use mz_sql::names::{QualifiedItemName, ResolvedIds, SchemaSpecifier};
153use mz_sql::optimizer_metrics::OptimizerMetrics;
154use mz_sql::plan::{
155    self, AlterSinkPlan, ConnectionDetails, CreateConnectionPlan, HirRelationExpr,
156    NetworkPolicyRule, OnTimeoutAction, Params, QueryWhen,
157};
158use mz_sql::session::user::User;
159use mz_sql::session::vars::{MAX_CREDIT_CONSUMPTION_RATE, SystemVars, Var};
160use mz_sql_parser::ast::ExplainStage;
161use mz_sql_parser::ast::display::AstDisplay;
162use mz_storage_client::client::TableData;
163use mz_storage_client::controller::{CollectionDescription, DataSource, ExportDescription};
164use mz_storage_types::connections::Connection as StorageConnection;
165use mz_storage_types::connections::ConnectionContext;
166use mz_storage_types::connections::inline::{IntoInlineConnection, ReferencedConnection};
167use mz_storage_types::read_holds::ReadHold;
168use mz_storage_types::sinks::{S3SinkFormat, StorageSinkDesc};
169use mz_storage_types::sources::kafka::KAFKA_PROGRESS_DESC;
170use mz_storage_types::sources::{IngestionDescription, SourceExport, Timeline};
171use mz_timestamp_oracle::{TimestampOracleConfig, WriteTimestamp};
172use mz_transform::dataflow::DataflowMetainfo;
173use opentelemetry::trace::TraceContextExt;
174use serde::Serialize;
175use thiserror::Error;
176use timely::progress::{Antichain, Timestamp as _};
177use tokio::runtime::Handle as TokioHandle;
178use tokio::select;
179use tokio::sync::{Notify, OwnedMutexGuard, Semaphore, mpsc, oneshot, watch};
180use tokio::time::{Interval, MissedTickBehavior};
181use tracing::{Instrument, Level, Span, debug, info, info_span, span, warn};
182use tracing_opentelemetry::OpenTelemetrySpanExt;
183use uuid::Uuid;
184
185use crate::active_compute_sink::{ActiveComputeSink, ActiveCopyFrom};
186use crate::catalog::{BuiltinTableUpdate, Catalog, OpenCatalogResult};
187use crate::client::{Client, Handle};
188use crate::command::{Command, ExecuteResponse};
189use crate::config::{
190    ClusterEvalContext, ReplicaEvalContext, ScopedParameters, ScopedParametersScope,
191    SynchronizedParameters, SystemParameterFrontend, SystemParameterSyncConfig,
192};
193use crate::coord::appends::{
194    BuiltinTableAppendCompletion, BuiltinTableAppendNotify, DeferredOp, GroupCommitPermit,
195    PendingWriteTxn,
196};
197use crate::coord::caught_up::CaughtUpCheckContext;
198use crate::coord::id_bundle::CollectionIdBundle;
199use crate::coord::introspection::IntrospectionSubscribe;
200use crate::coord::peek::PendingPeek;
201use crate::coord::statement_logging::StatementLogging;
202use crate::coord::timeline::{TimelineContext, TimelineState};
203use crate::coord::timestamp_selection::{TimestampContext, TimestampDetermination};
204use crate::coord::validity::PlanValidity;
205use crate::error::AdapterError;
206use crate::explain::insights::PlanInsightsContext;
207use crate::explain::optimizer_trace::{DispatchGuard, OptimizerTrace};
208use crate::metrics::Metrics;
209use crate::optimize::dataflows::{ComputeInstanceSnapshot, DataflowBuilder};
210use crate::optimize::{self, Optimize, OptimizerConfig};
211use crate::session::{EndTransactionAction, Session};
212use crate::statement_logging::{
213    StatementEndedExecutionReason, StatementLifecycleEvent, StatementLoggingId,
214};
215use crate::util::{ClientTransmitter, ResultExt, sort_topological};
216use crate::webhook::{WebhookAppenderInvalidator, WebhookConcurrencyLimiter};
217use crate::{AdapterNotice, ReadHolds, flags};
218
219pub(crate) mod appends;
220pub(crate) mod catalog_serving;
221pub(crate) mod cluster_controller;
222pub(crate) mod consistency;
223pub(crate) mod id_bundle;
224pub(crate) mod in_memory_oracle;
225pub(crate) mod peek;
226pub(crate) mod read_policy;
227pub(crate) mod read_then_write;
228pub(crate) mod sequencer;
229pub(crate) mod statement_logging;
230pub(crate) mod timeline;
231pub(crate) mod timestamp_selection;
232
233pub mod catalog_implications;
234mod caught_up;
235mod command_handler;
236mod ddl;
237pub(crate) mod group_sync;
238mod indexes;
239mod info_metrics;
240mod introspection;
241mod message_handler;
242mod privatelink_status;
243mod sql;
244mod validity;
245
246/// A pool of pre-allocated user IDs to avoid per-DDL persist writes.
247///
248/// IDs in the range `[next, upper)` are available for allocation.
249/// When exhausted, the pool must be refilled via the catalog.
250///
251/// # Correctness
252///
253/// The pool is owned by [`Coordinator`], which processes all requests
254/// on a single-threaded event loop. Because every access requires
255/// `&mut self` on the coordinator, there is no concurrent access to the
256/// pool — no additional synchronization is needed.
257///
258/// Global ID uniqueness is guaranteed because each refill calls
259/// [`Catalog::allocate_user_ids`], which performs a durable persist
260/// write that atomically reserves the entire batch before any IDs from
261/// it are handed out. If the process crashes after a refill but before
262/// all pre-allocated IDs are consumed, the unused IDs form harmless
263/// gaps in the sequence — user IDs are not required to be contiguous.
264///
265/// This guarantee holds even if multiple `environmentd` processes run
266/// concurrently. Each process has its own independent pool,
267/// but every refill goes through the shared persist-backed catalog,
268/// which serializes allocations across all callers. Two processes
269/// will therefore never receive overlapping ID ranges,
270/// for the same reason they could not before this pool existed.
271#[derive(Debug)]
272pub(crate) struct IdPool {
273    next: u64,
274    upper: u64,
275}
276
277impl IdPool {
278    /// Creates an empty pool.
279    pub fn empty() -> Self {
280        IdPool { next: 0, upper: 0 }
281    }
282
283    /// Allocates a single ID from the pool, returning `None` if exhausted.
284    pub fn allocate(&mut self) -> Option<u64> {
285        if self.next < self.upper {
286            let id = self.next;
287            self.next += 1;
288            Some(id)
289        } else {
290            None
291        }
292    }
293
294    /// Allocates `n` consecutive IDs from the pool, returning `None` if
295    /// insufficient IDs remain.
296    pub fn allocate_many(&mut self, n: u64) -> Option<Vec<u64>> {
297        if self.remaining() >= n {
298            let ids = (self.next..self.next + n).collect();
299            self.next += n;
300            Some(ids)
301        } else {
302            None
303        }
304    }
305
306    /// Returns the number of IDs remaining in the pool.
307    pub fn remaining(&self) -> u64 {
308        self.upper - self.next
309    }
310
311    /// Refills the pool with the given range `[next, upper)`.
312    pub fn refill(&mut self, next: u64, upper: u64) {
313        assert!(next <= upper, "invalid pool range: {next}..{upper}");
314        self.next = next;
315        self.upper = upper;
316    }
317}
318
319/// A row for `mz_object_arrangement_size_history`, prepared off-thread by the
320/// arrangement sizes snapshot task and stamped with a collection timestamp at
321/// write time.
322#[derive(Debug)]
323pub struct ArrangementSizeRecord {
324    pub replica_id: String,
325    pub object_id: String,
326    pub size: i64,
327    pub hydration_complete: bool,
328}
329
330#[derive(Debug)]
331pub enum Message {
332    Command(OpenTelemetryContext, Command),
333    ControllerReady {
334        controller: ControllerReadiness,
335    },
336    PurifiedStatementReady(PurifiedStatementReady),
337    CreateConnectionValidationReady(CreateConnectionValidationReady),
338    AlterConnectionValidationReady(AlterConnectionValidationReady),
339    TryDeferred {
340        /// The connection that created this op.
341        conn_id: ConnectionId,
342        /// The write lock that notified us our deferred op might be able to run.
343        ///
344        /// Note: While we never want to hold a partial set of locks, it can be important to hold
345        /// onto the _one_ that notified us our op might be ready. If there are multiple operations
346        /// waiting on a single collection, and we don't hold this lock through retyring the op,
347        /// then everything waiting on this collection will get retried causing traffic in the
348        /// Coordinator's message queue.
349        ///
350        /// See [`DeferredOp::can_be_optimistically_retried`] for more detail.
351        acquired_lock: Option<(CatalogItemId, tokio::sync::OwnedMutexGuard<()>)>,
352    },
353    /// Initiates a group commit.
354    GroupCommitInitiate(Span, Option<GroupCommitPermit>),
355    /// Finalizes an applied group commit.
356    ///
357    /// Statement timestamps precede response retirement because retirement ends statement logging.
358    GroupCommitApplied {
359        /// Responses to retire after recording statement timestamps.
360        responses: Vec<crate::util::CompletedClientTransmitter>,
361        /// Statement executions associated with this commit.
362        statement_logging_ids: Vec<StatementLoggingId>,
363        /// Frontend-sequenced writes to complete after local timestamp bookkeeping.
364        internal_results: Vec<crate::coord::appends::InternalWriteResponder>,
365        /// The applied write timestamp.
366        write_ts: Timestamp,
367    },
368    DeferredStatementReady,
369    AdvanceTimelines,
370    ClusterEvent(ClusterEvent),
371    CancelPendingPeeks {
372        conn_id: ConnectionId,
373    },
374    LinearizeReads,
375    StagedBatches {
376        conn_id: ConnectionId,
377        table_id: CatalogItemId,
378        batches: Vec<Result<ProtoBatch, String>>,
379    },
380    StorageUsageSchedule,
381    StorageUsageFetch,
382    StorageUsageUpdate(ShardsUsageReferenced),
383    StorageUsagePrune(Vec<BuiltinTableUpdate>),
384    ArrangementSizesSchedule,
385    ArrangementSizesSnapshot,
386    ArrangementSizesWrite(Vec<ArrangementSizeRecord>),
387    ArrangementSizesPrune(Vec<BuiltinTableUpdate>),
388    /// Performs any cleanup and logging actions necessary for
389    /// finalizing a statement execution.
390    RetireExecute {
391        data: ExecuteContextExtra,
392        otel_ctx: OpenTelemetryContext,
393        reason: StatementEndedExecutionReason,
394    },
395    ExecuteSingleStatementTransaction {
396        ctx: ExecuteContext,
397        otel_ctx: OpenTelemetryContext,
398        stmt: Arc<Statement<Raw>>,
399        params: mz_sql::plan::Params,
400    },
401    PeekStageReady {
402        ctx: ExecuteContext,
403        span: Span,
404        stage: PeekStage,
405    },
406    CreateIndexStageReady {
407        ctx: ExecuteContext,
408        span: Span,
409        stage: CreateIndexStage,
410    },
411    CreateMetricSinkStageReady {
412        ctx: ExecuteContext,
413        span: Span,
414        stage: CreateMetricSinkStage,
415    },
416    CreateViewStageReady {
417        ctx: ExecuteContext,
418        span: Span,
419        stage: CreateViewStage,
420    },
421    CreateMaterializedViewStageReady {
422        ctx: ExecuteContext,
423        span: Span,
424        stage: CreateMaterializedViewStage,
425    },
426    SubscribeStageReady {
427        ctx: ExecuteContext,
428        span: Span,
429        stage: SubscribeStage,
430    },
431    IntrospectionSubscribeStageReady {
432        span: Span,
433        stage: IntrospectionSubscribeStage,
434    },
435    SecretStageReady {
436        ctx: ExecuteContext,
437        span: Span,
438        stage: SecretStage,
439    },
440    ClusterStageReady {
441        ctx: ExecuteContext,
442        span: Span,
443        stage: ClusterStage,
444    },
445    ExplainTimestampStageReady {
446        ctx: ExecuteContext,
447        span: Span,
448        stage: ExplainTimestampStage,
449    },
450    DrainStatementLog,
451    PrivateLinkVpcEndpointEvents(Vec<VpcEndpointEvent>),
452
453    /// One pull/apply call from the cluster controller task, answered on the main
454    /// coordinator message loop from the catalog and live controller signals.
455    /// See [`cluster_controller`].
456    ClusterControllerRequest(cluster_controller::ClusterControllerRequest),
457}
458
459impl Message {
460    /// Returns a string to identify the kind of [`Message`], useful for logging.
461    pub const fn kind(&self) -> &'static str {
462        match self {
463            Message::Command(_, msg) => match msg {
464                Command::CatalogSnapshot { .. } => "command-catalog_snapshot",
465                Command::Startup { .. } => "command-startup",
466                Command::Execute { .. } => "command-execute",
467                Command::Commit { .. } => "command-commit",
468                Command::CancelRequest { .. } => "command-cancel_request",
469                Command::PrivilegedCancelRequest { .. } => "command-privileged_cancel_request",
470                Command::GetWebhook { .. } => "command-get_webhook",
471                Command::GetSystemVars { .. } => "command-get_system_vars",
472                Command::SetSystemVars { .. } => "command-set_system_vars",
473                Command::UpdateScopedSystemParameters { .. } => {
474                    "command-update_scoped_system_parameters"
475                }
476                Command::InstallScopedSystemParameterFrontend { .. } => {
477                    "command-install_scoped_system_parameter_frontend"
478                }
479                Command::Terminate { .. } => "command-terminate",
480                Command::RetireExecute { .. } => "command-retire_execute",
481                Command::CheckConsistency { .. } => "command-check_consistency",
482                Command::Dump { .. } => "command-dump",
483                Command::AuthenticatePassword { .. } => "command-auth_check",
484                Command::AuthenticateGetSASLChallenge { .. } => "command-auth_get_sasl_challenge",
485                Command::AuthenticateVerifySASLProof { .. } => "command-auth_verify_sasl_proof",
486                Command::CheckRoleCanLogin { .. } => "command-check_role_can_login",
487                Command::GetComputeInstanceClient { .. } => "get-compute-instance-client",
488                Command::GetOracle { .. } => "get-oracle",
489                Command::DetermineRealTimeRecentTimestamp { .. } => {
490                    "determine-real-time-recent-timestamp"
491                }
492                Command::GetTransactionReadHoldsBundle { .. } => {
493                    "get-transaction-read-holds-bundle"
494                }
495                Command::StoreTransactionReadHolds { .. } => "store-transaction-read-holds",
496                Command::ExecuteSlowPathPeek { .. } => "execute-slow-path-peek",
497                Command::ExecuteSubscribe { .. } => "execute-subscribe",
498                Command::CopyToPreflight { .. } => "copy-to-preflight",
499                Command::ExecuteCopyTo { .. } => "execute-copy-to",
500                Command::ExecuteSideEffectingFunc { .. } => "execute-side-effecting-func",
501                Command::LookupConnection { .. } => "lookup-connection",
502                Command::RegisterFrontendPeek { .. } => "register-frontend-peek",
503                Command::UnregisterFrontendPeek { .. } => "unregister-frontend-peek",
504                Command::ExplainTimestamp { .. } => "explain-timestamp",
505                Command::FrontendStatementLogging(..) => "frontend-statement-logging",
506                Command::StartCopyFromStdin { .. } => "start-copy-from-stdin",
507                Command::InjectAuditEvents { .. } => "inject-audit-events",
508                Command::RegisterConnectionCancelWatch { .. } => "register-connection-cancel-watch",
509                Command::CreateInternalSubscribe { .. } => "create-internal-subscribe",
510                Command::AttemptWrite { .. } => "attempt-write",
511                Command::DropInternalSubscribe { .. } => "drop-internal-subscribe",
512            },
513            Message::ControllerReady {
514                controller: ControllerReadiness::Compute,
515            } => "controller_ready(compute)",
516            Message::ControllerReady {
517                controller: ControllerReadiness::Storage,
518            } => "controller_ready(storage)",
519            Message::ControllerReady {
520                controller: ControllerReadiness::Metrics,
521            } => "controller_ready(metrics)",
522            Message::ControllerReady {
523                controller: ControllerReadiness::Internal,
524            } => "controller_ready(internal)",
525            Message::PurifiedStatementReady(_) => "purified_statement_ready",
526            Message::CreateConnectionValidationReady(_) => "create_connection_validation_ready",
527            Message::TryDeferred { .. } => "try_deferred",
528            Message::GroupCommitInitiate(..) => "group_commit_initiate",
529            Message::GroupCommitApplied { .. } => "group_commit_applied",
530            Message::AdvanceTimelines => "advance_timelines",
531            Message::ClusterEvent(_) => "cluster_event",
532            Message::CancelPendingPeeks { .. } => "cancel_pending_peeks",
533            Message::LinearizeReads => "linearize_reads",
534            Message::StagedBatches { .. } => "staged_batches",
535            Message::StorageUsageSchedule => "storage_usage_schedule",
536            Message::StorageUsageFetch => "storage_usage_fetch",
537            Message::StorageUsageUpdate(_) => "storage_usage_update",
538            Message::StorageUsagePrune(_) => "storage_usage_prune",
539            Message::ArrangementSizesSchedule => "arrangement_sizes_schedule",
540            Message::ArrangementSizesSnapshot => "arrangement_sizes_snapshot",
541            Message::ArrangementSizesWrite(_) => "arrangement_sizes_write",
542            Message::ArrangementSizesPrune(_) => "arrangement_sizes_prune",
543            Message::RetireExecute { .. } => "retire_execute",
544            Message::ExecuteSingleStatementTransaction { .. } => {
545                "execute_single_statement_transaction"
546            }
547            Message::PeekStageReady { .. } => "peek_stage_ready",
548            Message::ExplainTimestampStageReady { .. } => "explain_timestamp_stage_ready",
549            Message::CreateIndexStageReady { .. } => "create_index_stage_ready",
550            Message::CreateMetricSinkStageReady { .. } => "create_metric_sink_stage_ready",
551            Message::CreateViewStageReady { .. } => "create_view_stage_ready",
552            Message::CreateMaterializedViewStageReady { .. } => {
553                "create_materialized_view_stage_ready"
554            }
555            Message::SubscribeStageReady { .. } => "subscribe_stage_ready",
556            Message::IntrospectionSubscribeStageReady { .. } => {
557                "introspection_subscribe_stage_ready"
558            }
559            Message::SecretStageReady { .. } => "secret_stage_ready",
560            Message::ClusterStageReady { .. } => "cluster_stage_ready",
561            Message::DrainStatementLog => "drain_statement_log",
562            Message::AlterConnectionValidationReady(..) => "alter_connection_validation_ready",
563            Message::PrivateLinkVpcEndpointEvents(_) => "private_link_vpc_endpoint_events",
564            Message::ClusterControllerRequest(_) => "cluster_controller_request",
565            Message::DeferredStatementReady => "deferred_statement_ready",
566        }
567    }
568}
569
570/// The reason for why a controller needs processing on the main loop.
571#[derive(Debug)]
572pub enum ControllerReadiness {
573    /// The storage controller is ready.
574    Storage,
575    /// The compute controller is ready.
576    Compute,
577    /// A batch of metric data is ready.
578    Metrics,
579    /// An internally-generated message is ready to be returned.
580    Internal,
581}
582
583#[derive(Derivative)]
584#[derivative(Debug)]
585pub struct BackgroundWorkResult<T> {
586    #[derivative(Debug = "ignore")]
587    pub ctx: ExecuteContext,
588    pub result: Result<T, AdapterError>,
589    pub params: Params,
590    pub plan_validity: PlanValidity,
591    pub original_stmt: Arc<Statement<Raw>>,
592    pub otel_ctx: OpenTelemetryContext,
593}
594
595pub type PurifiedStatementReady = BackgroundWorkResult<mz_sql::pure::PurifiedStatement>;
596
597#[derive(Derivative)]
598#[derivative(Debug)]
599pub struct ValidationReady<T> {
600    #[derivative(Debug = "ignore")]
601    pub ctx: ExecuteContext,
602    pub result: Result<T, AdapterError>,
603    pub resolved_ids: ResolvedIds,
604    pub connection_id: CatalogItemId,
605    pub connection_gid: GlobalId,
606    pub plan_validity: PlanValidity,
607    pub otel_ctx: OpenTelemetryContext,
608}
609
610pub type CreateConnectionValidationReady = ValidationReady<CreateConnectionPlan>;
611pub type AlterConnectionValidationReady = ValidationReady<Connection>;
612
613#[derive(Debug)]
614pub enum PeekStage {
615    /// Common stages across SELECT, EXPLAIN and COPY TO queries.
616    LinearizeTimestamp(PeekStageLinearizeTimestamp),
617    RealTimeRecency(PeekStageRealTimeRecency),
618    TimestampReadHold(PeekStageTimestampReadHold),
619    Optimize(PeekStageOptimize),
620    /// Final stage for a peek.
621    Finish(PeekStageFinish),
622    /// Final stage for an explain.
623    ExplainPlan(PeekStageExplainPlan),
624    ExplainPushdown(PeekStageExplainPushdown),
625    /// Preflight checks for a copy to operation.
626    CopyToPreflight(PeekStageCopyTo),
627    /// Final stage for a copy to which involves shipping the dataflow.
628    CopyToDataflow(PeekStageCopyTo),
629}
630
631#[derive(Debug)]
632pub struct CopyToContext {
633    /// The `RelationDesc` of the data to be copied.
634    pub desc: RelationDesc,
635    /// The destination uri of the external service where the data will be copied.
636    pub uri: Uri,
637    /// Connection information required to connect to the external service to copy the data.
638    pub connection: StorageConnection<ReferencedConnection>,
639    /// The ID of the CONNECTION object to be used for copying the data.
640    pub connection_id: CatalogItemId,
641    /// Format params to format the data.
642    pub format: S3SinkFormat,
643    /// Approximate max file size of each uploaded file.
644    pub max_file_size: u64,
645    /// Number of batches the output of the COPY TO will be partitioned into
646    /// to distribute the load across workers deterministically.
647    /// This is only an option since it's not set when CopyToContext is instantiated
648    /// but immediately after in the PeekStageValidate stage.
649    pub output_batch_count: Option<u64>,
650}
651
652#[derive(Debug)]
653pub struct PeekStageLinearizeTimestamp {
654    validity: PlanValidity,
655    plan: mz_sql::plan::SelectPlan,
656    max_query_result_size: Option<u64>,
657    source_ids: BTreeSet<GlobalId>,
658    target_replica: Option<ReplicaId>,
659    timeline_context: TimelineContext,
660    optimizer: optimize::PeekOptimizer,
661    /// An optional context set iff the state machine is initiated from
662    /// sequencing an EXPLAIN for this statement.
663    explain_ctx: ExplainContext,
664}
665
666#[derive(Debug)]
667pub struct PeekStageRealTimeRecency {
668    validity: PlanValidity,
669    plan: mz_sql::plan::SelectPlan,
670    max_query_result_size: Option<u64>,
671    source_ids: BTreeSet<GlobalId>,
672    target_replica: Option<ReplicaId>,
673    timeline_context: TimelineContext,
674    oracle_read_ts: Option<Timestamp>,
675    optimizer: optimize::PeekOptimizer,
676    /// An optional context set iff the state machine is initiated from
677    /// sequencing an EXPLAIN for this statement.
678    explain_ctx: ExplainContext,
679}
680
681#[derive(Debug)]
682pub struct PeekStageTimestampReadHold {
683    validity: PlanValidity,
684    plan: mz_sql::plan::SelectPlan,
685    max_query_result_size: Option<u64>,
686    source_ids: BTreeSet<GlobalId>,
687    target_replica: Option<ReplicaId>,
688    timeline_context: TimelineContext,
689    oracle_read_ts: Option<Timestamp>,
690    real_time_recency_ts: Option<mz_repr::Timestamp>,
691    optimizer: optimize::PeekOptimizer,
692    /// An optional context set iff the state machine is initiated from
693    /// sequencing an EXPLAIN for this statement.
694    explain_ctx: ExplainContext,
695}
696
697#[derive(Debug)]
698pub struct PeekStageOptimize {
699    validity: PlanValidity,
700    plan: mz_sql::plan::SelectPlan,
701    max_query_result_size: Option<u64>,
702    source_ids: BTreeSet<GlobalId>,
703    id_bundle: CollectionIdBundle,
704    target_replica: Option<ReplicaId>,
705    determination: TimestampDetermination,
706    optimizer: optimize::PeekOptimizer,
707    /// An optional context set iff the state machine is initiated from
708    /// sequencing an EXPLAIN for this statement.
709    explain_ctx: ExplainContext,
710}
711
712#[derive(Debug)]
713pub struct PeekStageFinish {
714    validity: PlanValidity,
715    plan: mz_sql::plan::SelectPlan,
716    max_query_result_size: Option<u64>,
717    id_bundle: CollectionIdBundle,
718    target_replica: Option<ReplicaId>,
719    source_ids: BTreeSet<GlobalId>,
720    determination: TimestampDetermination,
721    cluster_id: ComputeInstanceId,
722    finishing: RowSetFinishing,
723    /// When present, an optimizer trace to be used for emitting a plan insights
724    /// notice.
725    plan_insights_optimizer_trace: Option<OptimizerTrace>,
726    insights_ctx: Option<Box<PlanInsightsContext>>,
727    global_lir_plan: optimize::peek::GlobalLirPlan,
728    optimization_finished_at: EpochMillis,
729}
730
731#[derive(Debug)]
732pub struct PeekStageCopyTo {
733    validity: PlanValidity,
734    optimizer: optimize::copy_to::Optimizer,
735    global_lir_plan: optimize::copy_to::GlobalLirPlan,
736    optimization_finished_at: EpochMillis,
737    target_replica: Option<ReplicaId>,
738    source_ids: BTreeSet<GlobalId>,
739}
740
741#[derive(Debug)]
742pub struct PeekStageExplainPlan {
743    validity: PlanValidity,
744    optimizer: optimize::peek::Optimizer,
745    df_meta: DataflowMetainfo,
746    explain_ctx: ExplainPlanContext,
747    insights_ctx: Option<Box<PlanInsightsContext>>,
748}
749
750#[derive(Debug)]
751pub struct PeekStageExplainPushdown {
752    validity: PlanValidity,
753    determination: TimestampDetermination,
754    imports: BTreeMap<GlobalId, MapFilterProject>,
755}
756
757#[derive(Debug)]
758pub enum CreateIndexStage {
759    Optimize(CreateIndexOptimize),
760    Finish(CreateIndexFinish),
761    Explain(CreateIndexExplain),
762}
763
764#[derive(Debug)]
765pub struct CreateIndexOptimize {
766    validity: PlanValidity,
767    plan: plan::CreateIndexPlan,
768    resolved_ids: ResolvedIds,
769    /// An optional context set iff the state machine is initiated from
770    /// sequencing an EXPLAIN for this statement.
771    explain_ctx: ExplainContext,
772}
773
774#[derive(Debug)]
775pub struct CreateIndexFinish {
776    validity: PlanValidity,
777    item_id: CatalogItemId,
778    global_id: GlobalId,
779    plan: plan::CreateIndexPlan,
780    resolved_ids: ResolvedIds,
781    global_mir_plan: optimize::index::GlobalMirPlan,
782    global_lir_plan: optimize::index::GlobalLirPlan,
783    optimizer_features: OptimizerFeatures,
784}
785
786#[derive(Debug)]
787pub struct CreateIndexExplain {
788    validity: PlanValidity,
789    exported_index_id: GlobalId,
790    plan: plan::CreateIndexPlan,
791    df_meta: DataflowMetainfo,
792    explain_ctx: ExplainPlanContext,
793}
794
795#[derive(Debug)]
796pub enum CreateMetricSinkStage {
797    Optimize(CreateMetricSinkOptimize),
798    Finish(CreateMetricSinkFinish),
799}
800
801#[derive(Debug)]
802pub struct CreateMetricSinkOptimize {
803    validity: PlanValidity,
804    plan: plan::CreateMetricSinkPlan,
805    resolved_ids: ResolvedIds,
806}
807
808#[derive(Debug)]
809pub struct CreateMetricSinkFinish {
810    validity: PlanValidity,
811    item_id: CatalogItemId,
812    global_id: GlobalId,
813    plan: plan::CreateMetricSinkPlan,
814    resolved_ids: ResolvedIds,
815    global_mir_plan: optimize::metric_sink::GlobalMirPlan,
816    global_lir_plan: optimize::metric_sink::GlobalLirPlan,
817    optimizer_features: OptimizerFeatures,
818}
819
820#[derive(Debug)]
821pub enum CreateViewStage {
822    Optimize(CreateViewOptimize),
823    Finish(CreateViewFinish),
824    Explain(CreateViewExplain),
825}
826
827#[derive(Debug)]
828pub struct CreateViewOptimize {
829    validity: PlanValidity,
830    plan: plan::CreateViewPlan,
831    resolved_ids: ResolvedIds,
832    /// An optional context set iff the state machine is initiated from
833    /// sequencing an EXPLAIN for this statement.
834    explain_ctx: ExplainContext,
835}
836
837#[derive(Debug)]
838pub struct CreateViewFinish {
839    validity: PlanValidity,
840    /// ID of this item in the Catalog.
841    item_id: CatalogItemId,
842    /// ID by with Compute will reference this View.
843    global_id: GlobalId,
844    plan: plan::CreateViewPlan,
845    /// IDs of objects resolved during name resolution.
846    resolved_ids: ResolvedIds,
847    optimized_expr: OptimizedMirRelationExpr,
848}
849
850#[derive(Debug)]
851pub struct CreateViewExplain {
852    validity: PlanValidity,
853    id: GlobalId,
854    plan: plan::CreateViewPlan,
855    explain_ctx: ExplainPlanContext,
856}
857
858#[derive(Debug)]
859pub enum ExplainTimestampStage {
860    Optimize(ExplainTimestampOptimize),
861    RealTimeRecency(ExplainTimestampRealTimeRecency),
862    LinearizeTimestamp(ExplainTimestampLinearizeTimestamp),
863    Finish(ExplainTimestampFinish),
864}
865
866#[derive(Debug)]
867pub struct ExplainTimestampOptimize {
868    validity: PlanValidity,
869    plan: plan::ExplainTimestampPlan,
870    cluster_id: ClusterId,
871}
872
873#[derive(Debug)]
874pub struct ExplainTimestampRealTimeRecency {
875    validity: PlanValidity,
876    format: ExplainFormat,
877    optimized_plan: OptimizedMirRelationExpr,
878    cluster_id: ClusterId,
879    when: QueryWhen,
880}
881
882#[derive(Debug)]
883pub struct ExplainTimestampLinearizeTimestamp {
884    validity: PlanValidity,
885    format: ExplainFormat,
886    optimized_plan: OptimizedMirRelationExpr,
887    cluster_id: ClusterId,
888    source_ids: BTreeSet<GlobalId>,
889    when: QueryWhen,
890    real_time_recency_ts: Option<Timestamp>,
891}
892
893#[derive(Debug)]
894pub struct ExplainTimestampFinish {
895    validity: PlanValidity,
896    format: ExplainFormat,
897    cluster_id: ClusterId,
898    source_ids: BTreeSet<GlobalId>,
899    when: QueryWhen,
900    real_time_recency_ts: Option<Timestamp>,
901    /// The timeline context derived in the preceding `LinearizeTimestamp`
902    /// stage, carried forward so it stays consistent with `oracle_read_ts`.
903    timeline_context: TimelineContext,
904    /// The linearized read timestamp, read off the coordinator loop in the
905    /// preceding `LinearizeTimestamp` stage. `None` when no linearized read is
906    /// needed.
907    oracle_read_ts: Option<Timestamp>,
908}
909
910#[derive(Debug)]
911pub enum ClusterStage {
912    Alter(AlterCluster),
913    WaitForHydrated(AlterClusterWaitForHydrated),
914    Finalize(AlterClusterFinalize),
915    /// The foreground wait-shim over a controller-driven background
916    /// reconfiguration: poll the durable `reconfiguration` record until it
917    /// clears, then report success or timeout depending on whether the realized
918    /// config reached the target.
919    AwaitReconfiguration(AlterClusterAwaitReconfiguration),
920}
921
922#[derive(Debug)]
923pub struct AlterCluster {
924    validity: PlanValidity,
925    plan: plan::AlterClusterPlan,
926}
927
928#[derive(Debug)]
929pub struct AlterClusterWaitForHydrated {
930    validity: PlanValidity,
931    plan: plan::AlterClusterPlan,
932    new_config: ClusterVariantManaged,
933    workload_class: Option<String>,
934    timeout_time: Instant,
935    on_timeout: OnTimeoutAction,
936}
937
938#[derive(Debug)]
939pub struct AlterClusterFinalize {
940    validity: PlanValidity,
941    plan: plan::AlterClusterPlan,
942    new_config: ClusterVariantManaged,
943    workload_class: Option<String>,
944}
945
946#[derive(Debug)]
947pub struct AlterClusterAwaitReconfiguration {
948    validity: PlanValidity,
949    cluster_id: ClusterId,
950    /// The target shape the awaited `ALTER` wrote. Once the record becomes
951    /// terminal, the realized config matching this is what distinguishes a
952    /// cut-over from a failure. See `await_reconfiguration_stage`.
953    target: ReconfigurationTarget,
954}
955
956#[derive(Debug)]
957pub enum ExplainContext {
958    /// The ordinary, non-explain variant of the statement.
959    None,
960    /// The `EXPLAIN <level> PLAN FOR <explainee>` version of the statement.
961    Plan(ExplainPlanContext),
962    /// Generate a notice containing the `EXPLAIN PLAN INSIGHTS` output
963    /// alongside the query's normal output.
964    PlanInsightsNotice(OptimizerTrace),
965    /// `EXPLAIN FILTER PUSHDOWN`
966    Pushdown,
967}
968
969impl ExplainContext {
970    /// If available for this context, wrap the [`OptimizerTrace`] into a
971    /// [`tracing::Dispatch`] and set it as default, returning the resulting
972    /// guard in a `Some(guard)` option.
973    pub(crate) fn dispatch_guard(&self) -> Option<DispatchGuard<'_>> {
974        let optimizer_trace = match self {
975            ExplainContext::Plan(explain_ctx) => Some(&explain_ctx.optimizer_trace),
976            ExplainContext::PlanInsightsNotice(optimizer_trace) => Some(optimizer_trace),
977            _ => None,
978        };
979        optimizer_trace.map(|optimizer_trace| optimizer_trace.as_guard())
980    }
981
982    pub(crate) fn needs_cluster(&self) -> bool {
983        match self {
984            ExplainContext::None => true,
985            ExplainContext::Plan(..) => false,
986            ExplainContext::PlanInsightsNotice(..) => true,
987            ExplainContext::Pushdown => false,
988        }
989    }
990
991    pub(crate) fn needs_plan_insights(&self) -> bool {
992        matches!(
993            self,
994            ExplainContext::Plan(ExplainPlanContext {
995                stage: ExplainStage::PlanInsights,
996                ..
997            }) | ExplainContext::PlanInsightsNotice(_)
998        )
999    }
1000}
1001
1002#[derive(Debug)]
1003pub struct ExplainPlanContext {
1004    /// EXPLAIN BROKEN is internal syntax for showing EXPLAIN output despite an internal error in
1005    /// the optimizer: we don't immediately bail out from peek sequencing when an internal optimizer
1006    /// error happens, but go on with trying to show the requested EXPLAIN stage. This can still
1007    /// succeed if the requested EXPLAIN stage is before the point where the error happened.
1008    pub broken: bool,
1009    pub config: ExplainConfig,
1010    pub format: ExplainFormat,
1011    pub stage: ExplainStage,
1012    pub replan: Option<GlobalId>,
1013    pub desc: Option<RelationDesc>,
1014    pub optimizer_trace: OptimizerTrace,
1015}
1016
1017#[derive(Debug)]
1018pub enum CreateMaterializedViewStage {
1019    Optimize(CreateMaterializedViewOptimize),
1020    Finish(CreateMaterializedViewFinish),
1021    Explain(CreateMaterializedViewExplain),
1022}
1023
1024#[derive(Debug)]
1025pub struct CreateMaterializedViewOptimize {
1026    validity: PlanValidity,
1027    plan: plan::CreateMaterializedViewPlan,
1028    resolved_ids: ResolvedIds,
1029    /// An optional context set iff the state machine is initiated from
1030    /// sequencing an EXPLAIN for this statement.
1031    explain_ctx: ExplainContext,
1032}
1033
1034#[derive(Debug)]
1035pub struct CreateMaterializedViewFinish {
1036    /// The ID of this Materialized View in the Catalog.
1037    item_id: CatalogItemId,
1038    /// The ID of the durable pTVC backing this Materialized View.
1039    global_id: GlobalId,
1040    validity: PlanValidity,
1041    plan: plan::CreateMaterializedViewPlan,
1042    resolved_ids: ResolvedIds,
1043    local_mir_plan: optimize::materialized_view::LocalMirPlan,
1044    global_mir_plan: optimize::materialized_view::GlobalMirPlan,
1045    global_lir_plan: optimize::materialized_view::GlobalLirPlan,
1046    optimizer_features: OptimizerFeatures,
1047}
1048
1049#[derive(Debug)]
1050pub struct CreateMaterializedViewExplain {
1051    global_id: GlobalId,
1052    validity: PlanValidity,
1053    plan: plan::CreateMaterializedViewPlan,
1054    df_meta: DataflowMetainfo,
1055    explain_ctx: ExplainPlanContext,
1056}
1057
1058#[derive(Debug)]
1059pub enum SubscribeStage {
1060    OptimizeMir(SubscribeOptimizeMir),
1061    LinearizeTimestamp(SubscribeLinearizeTimestamp),
1062    TimestampOptimizeLir(SubscribeTimestampOptimizeLir),
1063    Finish(SubscribeFinish),
1064    Explain(SubscribeExplain),
1065}
1066
1067#[derive(Debug)]
1068pub struct SubscribeOptimizeMir {
1069    validity: PlanValidity,
1070    plan: plan::SubscribePlan,
1071    timeline: TimelineContext,
1072    dependency_ids: BTreeSet<GlobalId>,
1073    cluster_id: ComputeInstanceId,
1074    replica_id: Option<ReplicaId>,
1075    /// An optional context set iff the state machine is initiated from
1076    /// sequencing an EXPLAIN for this statement.
1077    explain_ctx: ExplainContext,
1078}
1079
1080#[derive(Debug)]
1081pub struct SubscribeLinearizeTimestamp {
1082    validity: PlanValidity,
1083    plan: plan::SubscribePlan,
1084    timeline: TimelineContext,
1085    optimizer: optimize::subscribe::Optimizer,
1086    global_mir_plan: optimize::subscribe::GlobalMirPlan<optimize::subscribe::Unresolved>,
1087    dependency_ids: BTreeSet<GlobalId>,
1088    replica_id: Option<ReplicaId>,
1089    /// An optional context set iff the state machine is initiated from
1090    /// sequencing an EXPLAIN for this statement.
1091    explain_ctx: ExplainContext,
1092}
1093
1094#[derive(Debug)]
1095pub struct SubscribeTimestampOptimizeLir {
1096    validity: PlanValidity,
1097    plan: plan::SubscribePlan,
1098    timeline: TimelineContext,
1099    optimizer: optimize::subscribe::Optimizer,
1100    global_mir_plan: optimize::subscribe::GlobalMirPlan<optimize::subscribe::Unresolved>,
1101    dependency_ids: BTreeSet<GlobalId>,
1102    replica_id: Option<ReplicaId>,
1103    /// The linearized read timestamp, read off the coordinator loop in the
1104    /// preceding `LinearizeTimestamp` stage. `None` when no linearized read is
1105    /// needed.
1106    oracle_read_ts: Option<Timestamp>,
1107    /// An optional context set iff the state machine is initiated from
1108    /// sequencing an EXPLAIN for this statement.
1109    explain_ctx: ExplainContext,
1110}
1111
1112#[derive(Debug)]
1113pub struct SubscribeFinish {
1114    validity: PlanValidity,
1115    cluster_id: ComputeInstanceId,
1116    replica_id: Option<ReplicaId>,
1117    plan: plan::SubscribePlan,
1118    global_lir_plan: optimize::subscribe::GlobalLirPlan,
1119    dependency_ids: BTreeSet<GlobalId>,
1120}
1121
1122#[derive(Debug)]
1123pub struct SubscribeExplain {
1124    validity: PlanValidity,
1125    optimizer: optimize::subscribe::Optimizer,
1126    df_meta: DataflowMetainfo,
1127    cluster_id: ComputeInstanceId,
1128    explain_ctx: ExplainPlanContext,
1129}
1130
1131#[derive(Debug)]
1132pub enum IntrospectionSubscribeStage {
1133    OptimizeMir(IntrospectionSubscribeOptimizeMir),
1134    TimestampOptimizeLir(IntrospectionSubscribeTimestampOptimizeLir),
1135    Finish(IntrospectionSubscribeFinish),
1136}
1137
1138#[derive(Debug)]
1139pub struct IntrospectionSubscribeOptimizeMir {
1140    validity: PlanValidity,
1141    plan: plan::SubscribePlan,
1142    subscribe_id: GlobalId,
1143    cluster_id: ComputeInstanceId,
1144    replica_id: ReplicaId,
1145}
1146
1147#[derive(Debug)]
1148pub struct IntrospectionSubscribeTimestampOptimizeLir {
1149    validity: PlanValidity,
1150    optimizer: optimize::subscribe::Optimizer,
1151    global_mir_plan: optimize::subscribe::GlobalMirPlan<optimize::subscribe::Unresolved>,
1152    cluster_id: ComputeInstanceId,
1153    replica_id: ReplicaId,
1154}
1155
1156#[derive(Debug)]
1157pub struct IntrospectionSubscribeFinish {
1158    validity: PlanValidity,
1159    global_lir_plan: optimize::subscribe::GlobalLirPlan,
1160    read_holds: ReadHolds,
1161    cluster_id: ComputeInstanceId,
1162    replica_id: ReplicaId,
1163}
1164
1165#[derive(Debug)]
1166pub enum SecretStage {
1167    CreateEnsure(CreateSecretEnsure),
1168    CreateFinish(CreateSecretFinish),
1169    RotateKeysEnsure(RotateKeysSecretEnsure),
1170    RotateKeysFinish(RotateKeysSecretFinish),
1171    Alter(AlterSecret),
1172}
1173
1174#[derive(Debug)]
1175pub struct CreateSecretEnsure {
1176    validity: PlanValidity,
1177    plan: plan::CreateSecretPlan,
1178}
1179
1180#[derive(Debug)]
1181pub struct CreateSecretFinish {
1182    validity: PlanValidity,
1183    item_id: CatalogItemId,
1184    global_id: GlobalId,
1185    plan: plan::CreateSecretPlan,
1186}
1187
1188#[derive(Debug)]
1189pub struct RotateKeysSecretEnsure {
1190    validity: PlanValidity,
1191    id: CatalogItemId,
1192}
1193
1194#[derive(Debug)]
1195pub struct RotateKeysSecretFinish {
1196    validity: PlanValidity,
1197    ops: Vec<crate::catalog::Op>,
1198}
1199
1200#[derive(Debug)]
1201pub struct AlterSecret {
1202    validity: PlanValidity,
1203    plan: plan::AlterSecretPlan,
1204}
1205
1206/// An enum describing which cluster to run a statement on.
1207///
1208/// One example usage would be that if a query depends only on system tables, we might
1209/// automatically run it on the catalog server cluster to benefit from indexes that exist there.
1210#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1211pub enum TargetCluster {
1212    /// The catalog server cluster.
1213    CatalogServer,
1214    /// The current user's active cluster.
1215    Active,
1216    /// The cluster selected at the start of a transaction.
1217    Transaction(ClusterId),
1218}
1219
1220/// Result types for each stage of a sequence.
1221pub(crate) enum StageResult<T> {
1222    /// A task was spawned that will return the next stage.
1223    Handle(JoinHandle<Result<T, AdapterError>>),
1224    /// A task was spawned that will return a response for the client.
1225    HandleRetire(JoinHandle<Result<ExecuteResponse, AdapterError>>),
1226    /// The next stage is immediately ready and will execute.
1227    Immediate(T),
1228    /// The final stage was executed and is ready to respond to the client.
1229    Response(ExecuteResponse),
1230}
1231
1232/// Common functionality for [Coordinator::sequence_staged].
1233pub(crate) trait Staged: Send {
1234    type Ctx: StagedContext;
1235
1236    fn validity(&mut self) -> &mut PlanValidity;
1237
1238    /// Returns the next stage or final result.
1239    async fn stage(
1240        self,
1241        coord: &mut Coordinator,
1242        ctx: &mut Self::Ctx,
1243    ) -> Result<StageResult<Box<Self>>, AdapterError>;
1244
1245    /// Prepares a message for the Coordinator.
1246    fn message(self, ctx: Self::Ctx, span: Span) -> Message;
1247
1248    /// Whether it is safe to SQL cancel this stage.
1249    fn cancel_enabled(&self) -> bool;
1250}
1251
1252pub trait StagedContext {
1253    fn retire(self, result: Result<ExecuteResponse, AdapterError>);
1254    fn session(&self) -> Option<&Session>;
1255}
1256
1257impl StagedContext for ExecuteContext {
1258    fn retire(self, result: Result<ExecuteResponse, AdapterError>) {
1259        self.retire(result);
1260    }
1261
1262    fn session(&self) -> Option<&Session> {
1263        Some(self.session())
1264    }
1265}
1266
1267impl StagedContext for () {
1268    fn retire(self, _result: Result<ExecuteResponse, AdapterError>) {}
1269
1270    fn session(&self) -> Option<&Session> {
1271        None
1272    }
1273}
1274
1275/// Configures a coordinator.
1276pub struct Config {
1277    pub controller_config: ControllerConfig,
1278    pub controller_envd_epoch: NonZeroI64,
1279    pub storage: Box<dyn mz_catalog::durable::DurableCatalogState>,
1280    pub timestamp_oracle_url: Option<SensitiveUrl>,
1281    pub unsafe_mode: bool,
1282    pub all_features: bool,
1283    pub build_info: &'static BuildInfo,
1284    pub environment_id: EnvironmentId,
1285    pub metrics_registry: MetricsRegistry,
1286    pub now: NowFn,
1287    pub secrets_controller: Arc<dyn SecretsController>,
1288    pub cloud_resource_controller: Option<Arc<dyn CloudResourceController>>,
1289    pub availability_zones: Vec<String>,
1290    pub cluster_replica_sizes: ClusterReplicaSizeMap,
1291    pub builtin_system_cluster_config: BootstrapBuiltinClusterConfig,
1292    pub builtin_catalog_server_cluster_config: BootstrapBuiltinClusterConfig,
1293    pub builtin_probe_cluster_config: BootstrapBuiltinClusterConfig,
1294    pub builtin_support_cluster_config: BootstrapBuiltinClusterConfig,
1295    pub builtin_analytics_cluster_config: BootstrapBuiltinClusterConfig,
1296    pub system_parameter_defaults: BTreeMap<String, String>,
1297    pub storage_usage_client: StorageUsageClient,
1298    pub storage_usage_collection_interval: Duration,
1299    pub storage_usage_retention_period: Option<Duration>,
1300    pub segment_client: Option<mz_segment::Client>,
1301    pub egress_addresses: Vec<IpNet>,
1302    pub remote_system_parameters: Option<BTreeMap<String, String>>,
1303    pub aws_account_id: Option<String>,
1304    pub aws_privatelink_availability_zones: Option<Vec<String>>,
1305    pub connection_context: ConnectionContext,
1306    pub connection_limit_callback: Box<dyn Fn(u64, u64) -> () + Send + Sync + 'static>,
1307    pub webhook_concurrency_limit: WebhookConcurrencyLimiter,
1308    pub http_host_name: Option<String>,
1309    pub tracing_handle: TracingHandle,
1310    /// Whether or not to start controllers in read-only mode. This is only
1311    /// meant for use during development of read-only clusters and 0dt upgrades
1312    /// and should go away once we have proper orchestration during upgrades.
1313    pub read_only_controllers: bool,
1314
1315    /// A trigger that signals that the current deployment has caught up with a
1316    /// previous deployment. Only used during 0dt deployment, while in read-only
1317    /// mode.
1318    pub caught_up_trigger: Option<Trigger>,
1319
1320    pub helm_chart_version: Option<String>,
1321    pub license_key: ValidatedLicenseKey,
1322    pub external_login_password_mz_system: Option<Password>,
1323    pub force_builtin_schema_migration: Option<String>,
1324}
1325
1326/// Metadata about an active connection.
1327#[derive(Debug, Serialize)]
1328pub struct ConnMeta {
1329    /// Pgwire specifies that every connection have a 32-bit secret associated
1330    /// with it, that is known to both the client and the server. Cancellation
1331    /// requests are required to authenticate with the secret of the connection
1332    /// that they are targeting.
1333    secret_key: u32,
1334    /// The time when the session's connection was initiated.
1335    connected_at: EpochMillis,
1336    user: User,
1337    application_name: String,
1338    uuid: Uuid,
1339    conn_id: ConnectionId,
1340    client_ip: Option<IpAddr>,
1341
1342    /// Sinks that will need to be dropped when the current transaction, if
1343    /// any, is cleared.
1344    drop_sinks: BTreeSet<GlobalId>,
1345
1346    /// Lock for the Coordinator's deferred statements that is dropped on transaction clear.
1347    #[serde(skip)]
1348    deferred_lock: Option<OwnedMutexGuard<()>>,
1349
1350    /// Cluster reconfigurations that will need to be
1351    /// cleaned up when the current transaction is cleared
1352    pending_cluster_alters: BTreeSet<ClusterId>,
1353
1354    /// Channel on which to send notices to a session.
1355    #[serde(skip)]
1356    notice_tx: mpsc::UnboundedSender<AdapterNotice>,
1357
1358    /// The role that initiated the database context. Fixed for the duration of the connection.
1359    /// WARNING: This role reference is not updated when the role is dropped.
1360    /// Consumers should not assume that this role exist.
1361    authenticated_role: RoleId,
1362}
1363
1364impl ConnMeta {
1365    pub fn conn_id(&self) -> &ConnectionId {
1366        &self.conn_id
1367    }
1368
1369    pub fn user(&self) -> &User {
1370        &self.user
1371    }
1372
1373    pub fn application_name(&self) -> &str {
1374        &self.application_name
1375    }
1376
1377    pub fn authenticated_role_id(&self) -> &RoleId {
1378        &self.authenticated_role
1379    }
1380
1381    pub fn uuid(&self) -> Uuid {
1382        self.uuid
1383    }
1384
1385    pub fn client_ip(&self) -> Option<IpAddr> {
1386        self.client_ip
1387    }
1388
1389    pub fn connected_at(&self) -> EpochMillis {
1390        self.connected_at
1391    }
1392}
1393
1394#[derive(Debug)]
1395/// A pending transaction waiting to be committed.
1396pub struct PendingTxn {
1397    /// Context used to send a response back to the client.
1398    ctx: ExecuteContext,
1399    /// Client response for transaction.
1400    response: Result<PendingTxnResponse, AdapterError>,
1401    /// The action to take at the end of the transaction.
1402    action: EndTransactionAction,
1403}
1404
1405#[derive(Debug)]
1406/// The response we'll send for a [`PendingTxn`].
1407pub enum PendingTxnResponse {
1408    /// The transaction will be committed.
1409    Committed {
1410        /// Parameters that will change, and their values, once this transaction is complete.
1411        params: BTreeMap<&'static str, String>,
1412    },
1413    /// The transaction will be rolled back.
1414    Rolledback {
1415        /// Parameters that will change, and their values, once this transaction is complete.
1416        params: BTreeMap<&'static str, String>,
1417    },
1418}
1419
1420impl PendingTxnResponse {
1421    pub fn extend_params(&mut self, p: impl IntoIterator<Item = (&'static str, String)>) {
1422        match self {
1423            PendingTxnResponse::Committed { params }
1424            | PendingTxnResponse::Rolledback { params } => params.extend(p),
1425        }
1426    }
1427}
1428
1429impl From<PendingTxnResponse> for ExecuteResponse {
1430    fn from(value: PendingTxnResponse) -> Self {
1431        match value {
1432            PendingTxnResponse::Committed { params } => {
1433                ExecuteResponse::TransactionCommitted { params }
1434            }
1435            PendingTxnResponse::Rolledback { params } => {
1436                ExecuteResponse::TransactionRolledBack { params }
1437            }
1438        }
1439    }
1440}
1441
1442#[derive(Debug)]
1443/// A pending read transaction waiting to be linearized along with metadata about it's state
1444pub struct PendingReadTxn {
1445    /// The transaction type
1446    txn: PendingRead,
1447    /// The timestamp context of the transaction.
1448    timestamp_context: TimestampContext,
1449    /// When we created this pending txn, when the transaction ends. Only used for metrics.
1450    created: Instant,
1451    /// Number of times we requeued the processing of this pending read txn.
1452    /// Requeueing is necessary if the time we executed the query is after the current oracle time;
1453    /// see [`Coordinator::message_linearize_reads`] for more details.
1454    num_requeues: u64,
1455    /// Telemetry context.
1456    otel_ctx: OpenTelemetryContext,
1457}
1458
1459impl PendingReadTxn {
1460    /// Return the timestamp context of the pending read transaction.
1461    pub fn timestamp_context(&self) -> &TimestampContext {
1462        &self.timestamp_context
1463    }
1464
1465    pub(crate) fn take_context(self) -> ExecuteContext {
1466        self.txn.take_context()
1467    }
1468}
1469
1470#[derive(Debug)]
1471/// A pending read transaction waiting to be linearized.
1472enum PendingRead {
1473    Read {
1474        /// The inner transaction.
1475        txn: PendingTxn,
1476    },
1477    ReadThenWrite {
1478        /// Context used to send a response back to the client.
1479        ctx: ExecuteContext,
1480        /// Channel used to alert the transaction that the read has been linearized and send back
1481        /// `ctx`.
1482        tx: oneshot::Sender<Option<ExecuteContext>>,
1483    },
1484}
1485
1486impl PendingRead {
1487    /// Alert the client that the read has been linearized.
1488    ///
1489    /// If it is necessary to finalize an execute, return the state necessary to do so
1490    /// (execution context and result)
1491    #[instrument(level = "debug")]
1492    pub fn finish(self) -> Option<(ExecuteContext, Result<ExecuteResponse, AdapterError>)> {
1493        match self {
1494            PendingRead::Read {
1495                txn:
1496                    PendingTxn {
1497                        mut ctx,
1498                        response,
1499                        action,
1500                    },
1501                ..
1502            } => {
1503                let changed = ctx.session_mut().vars_mut().end_transaction(action);
1504                // Append any parameters that changed to the response.
1505                let response = response.map(|mut r| {
1506                    r.extend_params(changed);
1507                    ExecuteResponse::from(r)
1508                });
1509
1510                Some((ctx, response))
1511            }
1512            PendingRead::ReadThenWrite { ctx, tx, .. } => {
1513                // Ignore errors if the caller has hung up.
1514                let _ = tx.send(Some(ctx));
1515                None
1516            }
1517        }
1518    }
1519
1520    fn label(&self) -> &'static str {
1521        match self {
1522            PendingRead::Read { .. } => "read",
1523            PendingRead::ReadThenWrite { .. } => "read_then_write",
1524        }
1525    }
1526
1527    pub(crate) fn take_context(self) -> ExecuteContext {
1528        match self {
1529            PendingRead::Read { txn, .. } => txn.ctx,
1530            PendingRead::ReadThenWrite { ctx, tx, .. } => {
1531                // Inform the transaction that we've taken their context.
1532                // Ignore errors if the caller has hung up.
1533                let _ = tx.send(None);
1534                ctx
1535            }
1536        }
1537    }
1538}
1539
1540/// State that the coordinator must process as part of retiring
1541/// command execution.  `ExecuteContextExtra::Default` is guaranteed
1542/// to produce a value that will cause the coordinator to do nothing, and
1543/// is intended for use by code that invokes the execution processing flow
1544/// (i.e., `sequence_plan`) without actually being a statement execution.
1545///
1546/// This is a pure data struct containing only the statement logging ID.
1547/// For auto-retire-on-drop behavior, use `ExecuteContextGuard` which wraps
1548/// this struct and owns the channel for sending retirement messages.
1549#[derive(Debug, Default)]
1550#[must_use]
1551pub struct ExecuteContextExtra {
1552    statement_uuid: Option<StatementLoggingId>,
1553}
1554
1555impl ExecuteContextExtra {
1556    pub(crate) fn new(statement_uuid: Option<StatementLoggingId>) -> Self {
1557        Self { statement_uuid }
1558    }
1559    pub fn is_trivial(&self) -> bool {
1560        self.statement_uuid.is_none()
1561    }
1562    pub fn contents(&self) -> Option<StatementLoggingId> {
1563        self.statement_uuid
1564    }
1565    /// Consume this extra and return the statement UUID for retirement.
1566    /// This should only be called from code that knows what to do to finish
1567    /// up logging based on the inner value.
1568    #[must_use]
1569    pub(crate) fn retire(self) -> Option<StatementLoggingId> {
1570        self.statement_uuid
1571    }
1572}
1573
1574/// A guard that wraps `ExecuteContextExtra` and owns a channel for sending
1575/// retirement messages to the coordinator.
1576///
1577/// If this guard is dropped with a `Some` `statement_uuid` in its inner
1578/// `ExecuteContextExtra`, the `Drop` implementation will automatically send a
1579/// `Message::RetireExecute` to log the statement ending.
1580/// This handles cases like connection drops where the context cannot be
1581/// explicitly retired.
1582/// See <https://github.com/MaterializeInc/database-issues/issues/7304>
1583#[derive(Debug)]
1584#[must_use]
1585pub struct ExecuteContextGuard {
1586    extra: ExecuteContextExtra,
1587    /// Channel for sending messages to the coordinator. Used for auto-retiring on drop.
1588    /// For `Default` instances, this is a dummy sender (receiver already dropped), so
1589    /// sends will fail silently - which is the desired behavior since Default instances
1590    /// should only be used for non-logged statements.
1591    coordinator_tx: mpsc::UnboundedSender<Message>,
1592}
1593
1594impl Default for ExecuteContextGuard {
1595    fn default() -> Self {
1596        // Create a dummy sender by immediately dropping the receiver.
1597        // Any send on this channel will fail silently, which is the desired
1598        // behavior for Default instances (non-logged statements).
1599        let (tx, _rx) = mpsc::unbounded_channel();
1600        Self {
1601            extra: ExecuteContextExtra::default(),
1602            coordinator_tx: tx,
1603        }
1604    }
1605}
1606
1607impl ExecuteContextGuard {
1608    pub(crate) fn new(
1609        statement_uuid: Option<StatementLoggingId>,
1610        coordinator_tx: mpsc::UnboundedSender<Message>,
1611    ) -> Self {
1612        Self {
1613            extra: ExecuteContextExtra::new(statement_uuid),
1614            coordinator_tx,
1615        }
1616    }
1617    pub fn is_trivial(&self) -> bool {
1618        self.extra.is_trivial()
1619    }
1620    pub fn contents(&self) -> Option<StatementLoggingId> {
1621        self.extra.contents()
1622    }
1623    /// Take responsibility for the contents.  This should only be
1624    /// called from code that knows what to do to finish up logging
1625    /// based on the inner value.
1626    ///
1627    /// Returns the inner `ExecuteContextExtra`, consuming the guard without
1628    /// triggering the auto-retire behavior.
1629    pub(crate) fn defuse(mut self) -> ExecuteContextExtra {
1630        // Taking statement_uuid prevents the Drop impl from sending a retire message
1631        std::mem::take(&mut self.extra)
1632    }
1633}
1634
1635impl Drop for ExecuteContextGuard {
1636    fn drop(&mut self) {
1637        if let Some(statement_uuid) = self.extra.statement_uuid.take() {
1638            // Auto-retire since the guard was dropped without explicit retirement (likely due
1639            // to connection drop).
1640            let msg = Message::RetireExecute {
1641                data: ExecuteContextExtra {
1642                    statement_uuid: Some(statement_uuid),
1643                },
1644                otel_ctx: OpenTelemetryContext::obtain(),
1645                reason: StatementEndedExecutionReason::Aborted,
1646            };
1647            // Send may fail for Default instances (dummy sender), which is fine since
1648            // Default instances should only be used for non-logged statements.
1649            let _ = self.coordinator_tx.send(msg);
1650        }
1651    }
1652}
1653
1654/// Carries the session and statement state needed to retire an execution.
1655///
1656/// Dropping an unretired context fails the client synchronously. Shutdown can drop contexts from
1657/// task queues, where spawning response-barrier work is no longer safe.
1658#[derive(Debug)]
1659pub struct ExecuteContext {
1660    // `None` only after `retire`/`into_parts` consumed the context.
1661    inner: Option<Box<ExecuteContextInner>>,
1662}
1663
1664impl std::ops::Deref for ExecuteContext {
1665    type Target = ExecuteContextInner;
1666    fn deref(&self) -> &Self::Target {
1667        self.inner.as_ref().expect("only consumed by value")
1668    }
1669}
1670
1671impl std::ops::DerefMut for ExecuteContext {
1672    fn deref_mut(&mut self) -> &mut Self::Target {
1673        self.inner.as_mut().expect("only consumed by value")
1674    }
1675}
1676
1677impl Drop for ExecuteContext {
1678    fn drop(&mut self) {
1679        let Some(inner) = self.inner.take() else {
1680            return;
1681        };
1682        // Destructors cannot spawn response-barrier tasks during runtime shutdown. Send the error
1683        // synchronously and let the statement guard report retirement.
1684        tracing::warn!("execute context dropped without retirement, failing the client");
1685        let ExecuteContextInner { tx, session, .. } = *inner;
1686        tx.send(
1687            Err(AdapterError::Internal(
1688                "statement execution abandoned, outcome unknown (server shutting down)".into(),
1689            )),
1690            session,
1691        );
1692    }
1693}
1694
1695#[derive(Derivative)]
1696#[derivative(Debug)]
1697pub struct ExecuteContextInner {
1698    tx: ClientTransmitter<ExecuteResponse>,
1699    internal_cmd_tx: mpsc::UnboundedSender<Message>,
1700    session: Session,
1701    extra: ExecuteContextGuard,
1702    #[derivative(Debug = "ignore")]
1703    response_barriers: Vec<BuiltinTableAppendNotify>,
1704}
1705
1706impl ExecuteContext {
1707    pub fn session(&self) -> &Session {
1708        &self.session
1709    }
1710
1711    pub fn session_mut(&mut self) -> &mut Session {
1712        &mut self.session
1713    }
1714
1715    pub fn tx(&self) -> &ClientTransmitter<ExecuteResponse> {
1716        &self.tx
1717    }
1718
1719    pub fn tx_mut(&mut self) -> &mut ClientTransmitter<ExecuteResponse> {
1720        &mut self.tx
1721    }
1722
1723    pub fn from_parts(
1724        tx: ClientTransmitter<ExecuteResponse>,
1725        internal_cmd_tx: mpsc::UnboundedSender<Message>,
1726        session: Session,
1727        extra: ExecuteContextGuard,
1728    ) -> Self {
1729        Self::from_parts_with_response_barriers(tx, internal_cmd_tx, session, extra, Vec::new())
1730    }
1731
1732    pub fn from_parts_with_response_barriers(
1733        tx: ClientTransmitter<ExecuteResponse>,
1734        internal_cmd_tx: mpsc::UnboundedSender<Message>,
1735        session: Session,
1736        extra: ExecuteContextGuard,
1737        response_barriers: Vec<BuiltinTableAppendNotify>,
1738    ) -> Self {
1739        Self {
1740            inner: Some(
1741                ExecuteContextInner {
1742                    tx,
1743                    session,
1744                    extra,
1745                    response_barriers,
1746                    internal_cmd_tx,
1747                }
1748                .into(),
1749            ),
1750        }
1751    }
1752
1753    /// By calling this function, the caller takes responsibility for
1754    /// dealing with the instance of `ExecuteContextGuard`. This is
1755    /// intended to support protocols (like `COPY FROM`) that involve
1756    /// multiple passes of sending the session back and forth between
1757    /// the coordinator and the pgwire layer. As part of any such
1758    /// protocol, we must ensure that the `ExecuteContextGuard`
1759    /// (possibly wrapped in a new `ExecuteContext`) is passed back to the coordinator for
1760    /// eventual retirement. The returned response barriers must stay attached
1761    /// to the user-visible response path.
1762    ///
1763    /// The returned parts lose the `Drop` backstop that answers the client on shutdown, so they
1764    /// must not be held across an await point. A bare `ClientTransmitter` panics when dropped
1765    /// unsent.
1766    pub fn into_parts(
1767        mut self,
1768    ) -> (
1769        ClientTransmitter<ExecuteResponse>,
1770        mpsc::UnboundedSender<Message>,
1771        Session,
1772        ExecuteContextGuard,
1773        Vec<BuiltinTableAppendNotify>,
1774    ) {
1775        let ExecuteContextInner {
1776            tx,
1777            internal_cmd_tx,
1778            session,
1779            extra,
1780            response_barriers,
1781        } = *self.inner.take().expect("only consumed by value");
1782        (tx, internal_cmd_tx, session, extra, response_barriers)
1783    }
1784
1785    /// Retire the execution, by sending a message to the coordinator.
1786    #[instrument(level = "debug")]
1787    pub fn retire(mut self, result: Result<ExecuteResponse, AdapterError>) {
1788        let response_barriers = std::mem::take(&mut self.response_barriers);
1789        if response_barriers.is_empty() {
1790            let (tx, internal_cmd_tx, session, extra, _) = self.into_parts();
1791            retire_execution_context(tx, internal_cmd_tx, session, extra, result);
1792            return;
1793        }
1794        // Keep `self` intact across the wait: if shutdown drops this task, the context's `Drop`
1795        // backstop answers the client. Barriers are empty on re-entry, so this terminates.
1796        spawn(
1797            || "execute_context::retire_after_response_barriers",
1798            async move {
1799                for barrier in response_barriers {
1800                    barrier.await;
1801                }
1802                self.retire(result);
1803            },
1804        );
1805    }
1806
1807    /// Delays sending this statement's response until `barrier` resolves.
1808    pub(crate) fn delay_response_until(&mut self, barrier: BuiltinTableAppendCompletion) {
1809        self.response_barriers.push(barrier.into_notify());
1810    }
1811
1812    pub fn extra(&self) -> &ExecuteContextGuard {
1813        &self.extra
1814    }
1815
1816    pub fn extra_mut(&mut self) -> &mut ExecuteContextGuard {
1817        &mut self.extra
1818    }
1819}
1820
1821fn retire_execution_context(
1822    tx: ClientTransmitter<ExecuteResponse>,
1823    internal_cmd_tx: mpsc::UnboundedSender<Message>,
1824    session: Session,
1825    extra: ExecuteContextGuard,
1826    result: Result<ExecuteResponse, AdapterError>,
1827) {
1828    let reason = if extra.is_trivial() {
1829        None
1830    } else {
1831        Some((&result).into())
1832    };
1833    tx.send(result, session);
1834    if let Some(reason) = reason {
1835        let extra = extra.defuse();
1836        if let Err(e) = internal_cmd_tx.send(Message::RetireExecute {
1837            otel_ctx: OpenTelemetryContext::obtain(),
1838            data: extra,
1839            reason,
1840        }) {
1841            warn!("internal_cmd_rx dropped before we could send: {:?}", e);
1842        }
1843    }
1844}
1845
1846#[derive(Debug)]
1847struct ClusterReplicaStatuses(
1848    BTreeMap<ClusterId, BTreeMap<ReplicaId, BTreeMap<ProcessId, ClusterReplicaProcessStatus>>>,
1849);
1850
1851impl ClusterReplicaStatuses {
1852    pub(crate) fn new() -> ClusterReplicaStatuses {
1853        ClusterReplicaStatuses(BTreeMap::new())
1854    }
1855
1856    /// Initializes the statuses of the specified cluster.
1857    ///
1858    /// Panics if the cluster statuses are already initialized.
1859    pub(crate) fn initialize_cluster_statuses(&mut self, cluster_id: ClusterId) {
1860        let prev = self.0.insert(cluster_id, BTreeMap::new());
1861        assert_eq!(
1862            prev, None,
1863            "cluster {cluster_id} statuses already initialized"
1864        );
1865    }
1866
1867    /// Initializes the statuses of the specified cluster replica.
1868    ///
1869    /// Panics if the cluster replica statuses are already initialized.
1870    pub(crate) fn initialize_cluster_replica_statuses(
1871        &mut self,
1872        cluster_id: ClusterId,
1873        replica_id: ReplicaId,
1874        num_processes: usize,
1875        time: DateTime<Utc>,
1876    ) {
1877        tracing::info!(
1878            ?cluster_id,
1879            ?replica_id,
1880            ?time,
1881            "initializing cluster replica status"
1882        );
1883        let replica_statuses = self.0.entry(cluster_id).or_default();
1884        let process_statuses = (0..num_processes)
1885            .map(|process_id| {
1886                let status = ClusterReplicaProcessStatus {
1887                    status: ClusterStatus::Offline(Some(OfflineReason::Initializing)),
1888                    restart_count: 0,
1889                    time: time.clone(),
1890                };
1891                (u64::cast_from(process_id), status)
1892            })
1893            .collect();
1894        let prev = replica_statuses.insert(replica_id, process_statuses);
1895        assert_none!(
1896            prev,
1897            "cluster replica {cluster_id}.{replica_id} statuses already initialized"
1898        );
1899    }
1900
1901    /// Removes the statuses of the specified cluster.
1902    ///
1903    /// Panics if the cluster does not exist.
1904    pub(crate) fn remove_cluster_statuses(
1905        &mut self,
1906        cluster_id: &ClusterId,
1907    ) -> BTreeMap<ReplicaId, BTreeMap<ProcessId, ClusterReplicaProcessStatus>> {
1908        let prev = self.0.remove(cluster_id);
1909        prev.unwrap_or_else(|| panic!("unknown cluster: {cluster_id}"))
1910    }
1911
1912    /// Removes the statuses of the specified cluster replica.
1913    ///
1914    /// Panics if the cluster or replica does not exist.
1915    pub(crate) fn remove_cluster_replica_statuses(
1916        &mut self,
1917        cluster_id: &ClusterId,
1918        replica_id: &ReplicaId,
1919    ) -> BTreeMap<ProcessId, ClusterReplicaProcessStatus> {
1920        let replica_statuses = self
1921            .0
1922            .get_mut(cluster_id)
1923            .unwrap_or_else(|| panic!("unknown cluster: {cluster_id}"));
1924        let prev = replica_statuses.remove(replica_id);
1925        prev.unwrap_or_else(|| panic!("unknown cluster replica: {cluster_id}.{replica_id}"))
1926    }
1927
1928    /// Inserts or updates the status of the specified cluster replica process.
1929    ///
1930    /// Panics if the cluster or replica does not exist.
1931    pub(crate) fn ensure_cluster_status(
1932        &mut self,
1933        cluster_id: ClusterId,
1934        replica_id: ReplicaId,
1935        process_id: ProcessId,
1936        status: ClusterReplicaProcessStatus,
1937    ) {
1938        let replica_statuses = self
1939            .0
1940            .get_mut(&cluster_id)
1941            .unwrap_or_else(|| panic!("unknown cluster: {cluster_id}"))
1942            .get_mut(&replica_id)
1943            .unwrap_or_else(|| panic!("unknown cluster replica: {cluster_id}.{replica_id}"));
1944        replica_statuses.insert(process_id, status);
1945    }
1946
1947    /// Computes the status of the cluster replica as a whole.
1948    ///
1949    /// Panics if `cluster_id` or `replica_id` don't exist.
1950    pub fn get_cluster_replica_status(
1951        &self,
1952        cluster_id: ClusterId,
1953        replica_id: ReplicaId,
1954    ) -> ClusterStatus {
1955        let process_status = self.get_cluster_replica_statuses(cluster_id, replica_id);
1956        Self::cluster_replica_status(process_status)
1957    }
1958
1959    /// Computes the status of the cluster replica as a whole.
1960    pub fn cluster_replica_status(
1961        process_status: &BTreeMap<ProcessId, ClusterReplicaProcessStatus>,
1962    ) -> ClusterStatus {
1963        process_status
1964            .values()
1965            .fold(ClusterStatus::Online, |s, p| match (s, p.status) {
1966                (ClusterStatus::Online, ClusterStatus::Online) => ClusterStatus::Online,
1967                (x, y) => {
1968                    let reason_x = match x {
1969                        ClusterStatus::Offline(reason) => reason,
1970                        ClusterStatus::Online => None,
1971                    };
1972                    let reason_y = match y {
1973                        ClusterStatus::Offline(reason) => reason,
1974                        ClusterStatus::Online => None,
1975                    };
1976                    // Arbitrarily pick the first known not-ready reason.
1977                    ClusterStatus::Offline(reason_x.or(reason_y))
1978                }
1979            })
1980    }
1981
1982    /// Gets the statuses of the given cluster replica.
1983    ///
1984    /// Panics if the cluster or replica does not exist
1985    pub(crate) fn get_cluster_replica_statuses(
1986        &self,
1987        cluster_id: ClusterId,
1988        replica_id: ReplicaId,
1989    ) -> &BTreeMap<ProcessId, ClusterReplicaProcessStatus> {
1990        self.try_get_cluster_replica_statuses(cluster_id, replica_id)
1991            .unwrap_or_else(|| panic!("unknown cluster replica: {cluster_id}.{replica_id}"))
1992    }
1993
1994    /// Gets the statuses of the given cluster replica.
1995    pub(crate) fn try_get_cluster_replica_statuses(
1996        &self,
1997        cluster_id: ClusterId,
1998        replica_id: ReplicaId,
1999    ) -> Option<&BTreeMap<ProcessId, ClusterReplicaProcessStatus>> {
2000        self.try_get_cluster_statuses(cluster_id)
2001            .and_then(|statuses| statuses.get(&replica_id))
2002    }
2003
2004    /// Gets the statuses of the given cluster.
2005    pub(crate) fn try_get_cluster_statuses(
2006        &self,
2007        cluster_id: ClusterId,
2008    ) -> Option<&BTreeMap<ReplicaId, BTreeMap<ProcessId, ClusterReplicaProcessStatus>>> {
2009        self.0.get(&cluster_id)
2010    }
2011}
2012
2013/// Glues the external world to the Timely workers.
2014#[derive(Derivative)]
2015#[derivative(Debug)]
2016pub struct Coordinator {
2017    /// The controller for the storage and compute layers.
2018    #[derivative(Debug = "ignore")]
2019    controller: mz_controller::Controller,
2020    /// The catalog in an Arc suitable for readonly references. The Arc allows
2021    /// us to hand out cheap copies of the catalog to functions that can use it
2022    /// off of the main coordinator thread. If the coordinator needs to mutate
2023    /// the catalog, call [`Self::catalog_mut`], which will clone this struct member,
2024    /// allowing it to be mutated here while the other off-thread references can
2025    /// read their catalog as long as needed. In the future we would like this
2026    /// to be a pTVC, but for now this is sufficient.
2027    catalog: Arc<Catalog>,
2028
2029    /// A client for persist. Initially, this is only used for reading stashed
2030    /// peek responses out of batches.
2031    persist_client: PersistClient,
2032
2033    /// Channel to manage internal commands from the coordinator to itself.
2034    internal_cmd_tx: mpsc::UnboundedSender<Message>,
2035    /// Notification that triggers a group commit.
2036    group_commit_tx: appends::GroupCommitNotifier,
2037    /// Wakes the cluster controller task to reconcile immediately instead of
2038    /// waiting out its tick interval. Notified after catalog transactions that
2039    /// change durable cluster state.
2040    reconcile_now: Arc<Notify>,
2041    group_committer_tx: mpsc::UnboundedSender<appends::TableWriteCmd>,
2042
2043    /// Channel for strict serializable reads ready to commit.
2044    strict_serializable_reads_tx: mpsc::UnboundedSender<(ConnectionId, PendingReadTxn)>,
2045
2046    /// Signals that pending strict serializable reads should be re-checked
2047    /// because the timestamp oracle may have advanced. Awaited below group commit
2048    /// in [`Coordinator::serve`]; see that branch for the ordering rationale.
2049    linearize_reads_notify: Arc<Notify>,
2050
2051    /// Mechanism for totally ordering write and read timestamps, so that all reads
2052    /// reflect exactly the set of writes that precede them, and no writes that follow.
2053    global_timelines: BTreeMap<Timeline, TimelineState>,
2054
2055    /// A generator for transient [`GlobalId`]s, shareable with other threads.
2056    transient_id_gen: Arc<TransientIdGen>,
2057    /// A map from connection ID to metadata about that connection for all
2058    /// active connections.
2059    active_conns: BTreeMap<ConnectionId, ConnMeta>,
2060
2061    /// For each transaction, the read holds taken to support any performed reads.
2062    ///
2063    /// Upon completing a transaction, these read holds should be dropped.
2064    txn_read_holds: BTreeMap<ConnectionId, read_policy::ReadHolds>,
2065
2066    /// Access to the peek fields should be restricted to methods in the [`peek`] API.
2067    /// A map from pending peek ids to the queue into which responses are sent, and
2068    /// the connection id of the client that initiated the peek.
2069    pending_peeks: BTreeMap<Uuid, PendingPeek>,
2070    /// A map from client connection ids to a set of all pending peeks for that client.
2071    client_pending_peeks: BTreeMap<ConnectionId, BTreeMap<Uuid, ClusterId>>,
2072
2073    /// A map from client connection ids to pending linearize read transaction.
2074    pending_linearize_read_txns: BTreeMap<ConnectionId, PendingReadTxn>,
2075
2076    /// A map from the compute sink ID to it's state description.
2077    active_compute_sinks: BTreeMap<GlobalId, ActiveComputeSink>,
2078    /// A map from active webhooks to their invalidation handle.
2079    active_webhooks: BTreeMap<CatalogItemId, WebhookAppenderInvalidator>,
2080    /// A map of active `COPY FROM` statements. The Coordinator waits for `clusterd`
2081    /// to stage Batches in Persist that we will then link into the shard.
2082    active_copies: BTreeMap<ConnectionId, ActiveCopyFrom>,
2083
2084    /// Connection-scoped cancellation watches.
2085    ///
2086    /// Each entry is a watch channel whose value is `false` until cancellation
2087    /// is requested for that connection, at which point it is set to `true`.
2088    ///
2089    /// Consumers install these watches while they have cancellable work in
2090    /// flight, always as a fresh channel, so nobody can observe a cancellation
2091    /// aimed at an earlier statement. An entry is removed when a statement
2092    /// starts, when a stage runs uncancelable, and when the connection's state
2093    /// is cleared.
2094    connection_cancel_watches: BTreeMap<ConnectionId, (watch::Sender<bool>, watch::Receiver<bool>)>,
2095    /// Active introspection subscribes.
2096    introspection_subscribes: BTreeMap<GlobalId, IntrospectionSubscribe>,
2097
2098    /// Locks that grant access to a specific object, populated lazily as objects are written to.
2099    write_locks: BTreeMap<CatalogItemId, Arc<tokio::sync::Mutex<()>>>,
2100    /// Plans that are currently deferred and waiting on a write lock.
2101    deferred_write_ops: BTreeMap<ConnectionId, DeferredOp>,
2102
2103    /// Pending writes waiting for a group commit.
2104    pending_writes: Vec<PendingWriteTxn>,
2105
2106    /// Semaphore to limit concurrent OCC (optimistic concurrency control)
2107    /// read-then-write operations.
2108    ///
2109    /// Each operation maintains a subscribe that continually receives and
2110    /// consolidates updates. With N concurrent loops, every successful write
2111    /// forces the other N-1 to redo work, so total work scales as `O(n^2)`.
2112    /// The semaphore caps concurrency to keep that bounded.
2113    ///
2114    /// NOTE: The number of permits is read from `max_concurrent_occ_writes` at
2115    /// coordinator startup. Runtime changes require an `environmentd` restart.
2116    occ_write_semaphore: Arc<Semaphore>,
2117
2118    /// Whether frontend OCC read-then-write is enabled. Read once at startup
2119    /// from the `FRONTEND_READ_THEN_WRITE` dyncfg and fixed for the lifetime of
2120    /// this process. See the module-level docs on `frontend_read_then_write`
2121    /// for why mixed-mode operation is not allowed.
2122    frontend_read_then_write_enabled: bool,
2123
2124    /// For the realtime timeline, an explicit SELECT or INSERT on a table will bump the
2125    /// table's timestamps, but there are cases where timestamps are not bumped but
2126    /// we expect the closed timestamps to advance (`AS OF X`, SUBSCRIBing views over
2127    /// RT sources and tables). To address these, spawn a task that forces table
2128    /// timestamps to close on a regular interval. This roughly tracks the behavior
2129    /// of realtime sources that close off timestamps on an interval.
2130    ///
2131    /// For non-realtime timelines, nothing pushes the timestamps forward, so we must do
2132    /// it manually.
2133    advance_timelines_interval: Interval,
2134
2135    /// Serialized DDL. DDL must be serialized because:
2136    /// - Many of them do off-thread work and need to verify the catalog is in a valid state, but
2137    ///   [`PlanValidity`] does not currently support tracking all changes. Doing that correctly
2138    ///   seems to be more difficult than it's worth, so we would instead re-plan and re-sequence
2139    ///   the statements.
2140    /// - Re-planning a statement is hard because Coordinator and Session state is mutated at
2141    ///   various points, and we would need to correctly reset those changes before re-planning and
2142    ///   re-sequencing.
2143    serialized_ddl: LockedVecDeque<DeferredPlanStatement>,
2144
2145    /// Handle to secret manager that can create and delete secrets from
2146    /// an arbitrary secret storage engine.
2147    secrets_controller: Arc<dyn SecretsController>,
2148    /// A secrets reader than maintains an in-memory cache, where values have a set TTL.
2149    caching_secrets_reader: CachingSecretsReader,
2150
2151    /// Handle to a manager that can create and delete kubernetes resources
2152    /// (ie: VpcEndpoint objects)
2153    cloud_resource_controller: Option<Arc<dyn CloudResourceController>>,
2154
2155    /// Persist client for fetching storage metadata such as size metrics.
2156    storage_usage_client: StorageUsageClient,
2157    /// The interval at which to collect storage usage information.
2158    storage_usage_collection_interval: Duration,
2159
2160    /// Segment analytics client.
2161    #[derivative(Debug = "ignore")]
2162    segment_client: Option<mz_segment::Client>,
2163
2164    /// Coordinator metrics.
2165    metrics: Metrics,
2166    /// Optimizer metrics.
2167    optimizer_metrics: OptimizerMetrics,
2168
2169    /// Tracing handle.
2170    tracing_handle: TracingHandle,
2171
2172    /// Data used by the statement logging feature.
2173    statement_logging: StatementLogging,
2174
2175    /// Limit for how many concurrent webhook requests we allow.
2176    webhook_concurrency_limit: WebhookConcurrencyLimiter,
2177
2178    /// Optional config for the timestamp oracle. This is _required_ when
2179    /// a timestamp oracle backend is configured.
2180    timestamp_oracle_config: Option<TimestampOracleConfig>,
2181
2182    /// When doing 0dt upgrades/in read-only mode, periodically ask all known
2183    /// clusters/collections whether they are caught up.
2184    caught_up_check_interval: Interval,
2185
2186    /// Context needed to check whether all clusters/collections have caught up.
2187    /// Only used during 0dt deployment, while in read-only mode.
2188    caught_up_check: Option<CaughtUpCheckContext>,
2189
2190    /// The metrics registry, handed to the catalog info-metrics background task
2191    /// so it can register and own its `*_info` series.
2192    catalog_info_metrics_registry: MetricsRegistry,
2193
2194    /// The shared system-parameter frontend, installed by the sync loop once it
2195    /// initializes (and re-installed on reconnect). `None` until then, for
2196    /// example before LaunchDarkly connects, where a newly-created object
2197    /// resolves to the environment-wide value (the cold-cache fallback). Used to
2198    /// resolve a new cluster's or replica's scoped overrides synchronously at
2199    /// create time, so its first plan or first controller configuration is
2200    /// correct rather than waiting for the next sync tick. See the scoped
2201    /// feature flags design.
2202    scoped_frontend: Option<Arc<SystemParameterFrontend>>,
2203
2204    /// Tracks the state associated with the currently installed watchsets.
2205    installed_watch_sets: BTreeMap<WatchSetId, (ConnectionId, WatchSetResponse)>,
2206
2207    /// Tracks the currently installed watchsets for each connection.
2208    connection_watch_sets: BTreeMap<ConnectionId, BTreeSet<WatchSetId>>,
2209
2210    /// Tracks the statuses of all cluster replicas.
2211    cluster_replica_statuses: ClusterReplicaStatuses,
2212
2213    /// Whether or not to start controllers in read-only mode. This is only
2214    /// meant for use during development of read-only clusters and 0dt upgrades
2215    /// and should go away once we have proper orchestration during upgrades.
2216    read_only_controllers: bool,
2217
2218    /// Updates to builtin tables that are being buffered while we are in
2219    /// read-only mode. We apply these all at once when coming out of read-only
2220    /// mode.
2221    ///
2222    /// This is a `Some` while in read-only mode and will be replaced by a
2223    /// `None` when we transition out of read-only mode and write out any
2224    /// buffered updates.
2225    buffered_builtin_table_updates: Option<Vec<BuiltinTableUpdate>>,
2226
2227    license_key: ValidatedLicenseKey,
2228
2229    /// Pre-allocated pool of user IDs to amortize persist writes across DDL operations.
2230    user_id_pool: IdPool,
2231}
2232
2233impl Coordinator {
2234    /// Persists the scoped system-parameter working copy and reconciles it into
2235    /// the per-scope resolution boundaries.
2236    ///
2237    /// The system-parameter sync loop and the create-time fold
2238    /// (`scoped_overrides_create_op`, folded into the create transaction) are the
2239    /// only writers, both serialized on the coordinator loop. The diff is
2240    /// persisted to the
2241    /// durable cache (so values survive an `environmentd` restart and an LD
2242    /// outage) via `Op::UpdateScopedSystemParameters`, which also updates the
2243    /// in-memory working copy in [`CatalogState`] and the
2244    /// `mz_cluster_system_parameters` / `mz_replica_system_parameters`
2245    /// introspection relations. The `replica`-scoped overrides reach the compute
2246    /// controller's per-replica dyncfg layer through the catalog implication for
2247    /// the persisted change. The `cluster`-scoped layer is resolved at plan time
2248    /// via [`CatalogState::cluster_scoped_optimizer_overrides`].
2249    ///
2250    /// [`CatalogState`]: crate::catalog::CatalogState
2251    /// [`CatalogState::cluster_scoped_optimizer_overrides`]: crate::catalog::CatalogState::cluster_scoped_optimizer_overrides
2252    pub(crate) async fn reconcile_scoped_system_parameters(
2253        &mut self,
2254        scoped: ScopedParameters,
2255        prune_scope: ScopedParametersScope,
2256    ) {
2257        // Nothing changed: skip the durable write. This is the common case on
2258        // most sync ticks.
2259        if self.catalog().state().scoped_system_parameters() == &scoped {
2260            return;
2261        }
2262
2263        // Persist the diff and update the in-memory working copy + introspection
2264        // through the catalog transaction, serialized on the coordinator loop
2265        // with the create-time fold. The replica-scoped
2266        // controller push is derived from this transaction's diff by the catalog
2267        // implication. `prune_scope` bounds removals to the evaluated objects, so
2268        // a concurrently-created object's override is not wiped. Best-effort: a
2269        // failure here is logged and retried on the next sync tick.
2270        if let Err(e) = self
2271            .catalog_transact(
2272                None,
2273                vec![crate::catalog::Op::UpdateScopedSystemParameters {
2274                    scoped,
2275                    prune_scope,
2276                }],
2277            )
2278            .await
2279        {
2280            tracing::warn!("failed to persist scoped system parameters: {e}");
2281        }
2282    }
2283
2284    /// Evaluates the scoped overrides for freshly-created objects from explicit
2285    /// eval contexts and returns an [`Op::UpdateScopedSystemParameters`] to fold
2286    /// into the same transaction that creates them.
2287    ///
2288    /// The objects are not yet in the catalog, so the contexts are built from
2289    /// plan data and pre-allocated ids. Folding the op into the create
2290    /// transaction makes its committed diff drive the replica-scoped controller
2291    /// push, as a catalog implication, before `create_replica`. A new replica's
2292    /// first configuration then carries its overrides rather than the env-wide
2293    /// values. Render-frozen flags (e.g. the column-paged batcher, chosen at
2294    /// arrangement-build time) make a later push too late, which is why this
2295    /// happens in the create transaction rather than the next sync tick.
2296    ///
2297    /// Returns `None` when the shared frontend is not yet installed (e.g. before
2298    /// LaunchDarkly connects), or when no override applies. The new objects then
2299    /// resolve to the environment-wide value, and the periodic sync loop remains
2300    /// the authoritative full-state reconciler.
2301    ///
2302    /// [`Op::UpdateScopedSystemParameters`]: crate::catalog::Op::UpdateScopedSystemParameters
2303    fn scoped_overrides_create_op(
2304        &self,
2305        clusters: &[ClusterEvalContext],
2306        replicas: &[ReplicaEvalContext],
2307    ) -> Option<crate::catalog::Op> {
2308        let frontend = self.scoped_frontend.clone()?;
2309        let catalog = self.catalog();
2310        let system_config = catalog.system_config();
2311
2312        // Partition the synced parameters by scope class, as the sync loop does,
2313        // so we evaluate exactly the flags in use at each scope.
2314        let replica_param_names: Vec<&'static str> = system_config
2315            .iter_synced()
2316            .filter(|var| var.scope() == ParameterScope::Replica)
2317            .map(|var| var.name())
2318            .collect();
2319        let cluster_param_names: Vec<&'static str> = system_config
2320            .iter_synced()
2321            .filter(|var| var.scope() == ParameterScope::Cluster)
2322            .map(|var| var.name())
2323            .collect();
2324
2325        let params = SynchronizedParameters::new(system_config.clone());
2326        let mut evaluated = ScopedParameters::default();
2327        if !cluster_param_names.is_empty() && !clusters.is_empty() {
2328            evaluated.cluster =
2329                frontend.pull_cluster_overrides(&params, &cluster_param_names, clusters);
2330        }
2331        if !replica_param_names.is_empty() && !replicas.is_empty() {
2332            evaluated.replica =
2333                frontend.pull_replica_overrides(&params, &replica_param_names, replicas);
2334        }
2335        if evaluated.is_empty() {
2336            return None;
2337        }
2338
2339        // Prune only within the objects being created. They have no prior rows,
2340        // so nothing is removed, and this op never touches another object whose
2341        // override a concurrent reconcile may be writing.
2342        let prune_scope = ScopedParametersScope {
2343            clusters: clusters.iter().map(|cluster| cluster.cluster_id).collect(),
2344            replicas: replicas.iter().map(|replica| replica.replica_id).collect(),
2345        };
2346        Some(crate::catalog::Op::UpdateScopedSystemParameters {
2347            scoped: evaluated,
2348            prune_scope,
2349        })
2350    }
2351
2352    /// Renders the replica-local scoped overrides in the catalog working copy as
2353    /// per-replica [`ConfigUpdates`], grouped by cluster.
2354    ///
2355    /// Sparse: only replicas with an override are present. Parameters that are
2356    /// not dyncfgs are skipped, as are values that fail to parse.
2357    pub(crate) fn replica_dyncfg_overrides(
2358        &self,
2359    ) -> BTreeMap<ComputeInstanceId, BTreeMap<ReplicaId, ConfigUpdates>> {
2360        let replica_overrides = &self.catalog().state().scoped_system_parameters().replica;
2361
2362        let dyncfgs = self.catalog().system_config().dyncfgs();
2363        let mut instance_overrides: BTreeMap<
2364            ComputeInstanceId,
2365            BTreeMap<ReplicaId, ConfigUpdates>,
2366        > = BTreeMap::new();
2367        for cluster in self.catalog().clusters() {
2368            for replica in cluster.replicas() {
2369                let Some(values) = replica_overrides.get(&replica.replica_id) else {
2370                    continue;
2371                };
2372                let mut updates = ConfigUpdates::default();
2373                for (name, value) in values {
2374                    let Some(entry) = dyncfgs.entry(name) else {
2375                        // A replica-local parameter that is not a dyncfg has no
2376                        // per-replica realization, so skip it.
2377                        continue;
2378                    };
2379                    match entry.parse_val(value) {
2380                        Ok(val) => updates.add_dynamic(name, val),
2381                        Err(e) => {
2382                            tracing::warn!(%name, %value, "cannot parse scoped override: {e}")
2383                        }
2384                    }
2385                }
2386                if !updates.updates.is_empty() {
2387                    instance_overrides
2388                        .entry(cluster.id)
2389                        .or_default()
2390                        .insert(replica.replica_id, updates);
2391                }
2392            }
2393        }
2394
2395        instance_overrides
2396    }
2397
2398    /// Resolves the replica-local scoped overrides from the catalog working copy
2399    /// into the controllers' per-replica dyncfg layers, then re-pushes the
2400    /// environment-wide configuration so replicas observe the new values.
2401    /// Driven by the catalog implication for replica-scoped configuration
2402    /// changes, and called once on bootstrap.
2403    pub(crate) fn push_replica_dyncfg_overrides(&mut self) {
2404        let instance_overrides = self.replica_dyncfg_overrides();
2405
2406        // Both controllers carry a per-replica dyncfg layer, because the two
2407        // protocols realize configs in different worker `ConfigSet`s on
2408        // `clusterd`. The compute worker's `handle_update_configuration`
2409        // applies the pushed dyncfg updates to compute's own worker
2410        // `ConfigSet`, to the shared persist client `ConfigSet`
2411        // (`persist_clients.cfg()`) that the co-located storage server reads
2412        // from the same `Arc`, and to `mz_metrics`, which covers
2413        // persist-backed and process-global configs such as persist client
2414        // tuning and `lgalloc`. Configs realized from the storage worker's own
2415        // `ConfigSet` (read in its `UpdateConfiguration` handler) are reached
2416        // only by the storage controller's layer. A third class is not pushed
2417        // to a running replica at all but baked into its process configuration
2418        // when the controller provisions it, which is why the overrides also go
2419        // to the outer controller.
2420        self.controller
2421            .update_replica_dyncfg_overrides(instance_overrides);
2422        // Re-push the env-wide configs so existing replicas pick up their
2423        // (possibly changed) overrides. This also reverts a removed override:
2424        // the per-replica layer no longer carries the key, so the replica
2425        // falls back to the env-wide value, which both configs always include
2426        // because they render the full dyncfg set.
2427        let compute_config = crate::flags::compute_config(self.catalog().system_config());
2428        self.controller.compute.update_configuration(compute_config);
2429        let storage_config = crate::flags::storage_config(self.catalog().system_config());
2430        self.controller.storage.update_parameters(storage_config);
2431    }
2432
2433    /// Returns the cluster-coherent scoped optimizer-feature overrides for
2434    /// `cluster_id`. See
2435    /// [`CatalogState::cluster_scoped_optimizer_overrides`](crate::catalog::CatalogState::cluster_scoped_optimizer_overrides).
2436    pub(crate) fn cluster_scoped_optimizer_overrides(
2437        &self,
2438        cluster_id: ClusterId,
2439    ) -> OptimizerFeatureOverrides {
2440        self.catalog()
2441            .state()
2442            .cluster_scoped_optimizer_overrides(cluster_id)
2443    }
2444
2445    /// Initializes coordinator state based on the contained catalog. Must be
2446    /// called after creating the coordinator and before calling the
2447    /// `Coordinator::serve` method.
2448    #[instrument(name = "coord::bootstrap")]
2449    pub(crate) async fn bootstrap(
2450        &mut self,
2451        boot_ts: Timestamp,
2452        migrated_storage_collections_0dt: BTreeSet<CatalogItemId>,
2453        mut builtin_table_updates: Vec<BuiltinTableUpdate>,
2454        cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
2455        uncached_local_exprs: BTreeMap<GlobalId, LocalExpressions>,
2456    ) -> Result<(), AdapterError> {
2457        let bootstrap_start = Instant::now();
2458        info!("startup: coordinator init: bootstrap beginning");
2459        info!("startup: coordinator init: bootstrap: preamble beginning");
2460
2461        // Initialize cluster replica statuses.
2462        // Gross iterator is to avoid partial borrow issues.
2463        let cluster_statuses: Vec<(_, Vec<_>)> = self
2464            .catalog()
2465            .clusters()
2466            .map(|cluster| {
2467                (
2468                    cluster.id(),
2469                    cluster
2470                        .replicas()
2471                        .map(|replica| {
2472                            (replica.replica_id, replica.config.location.num_processes())
2473                        })
2474                        .collect(),
2475                )
2476            })
2477            .collect();
2478        let now = self.now_datetime();
2479        for (cluster_id, replica_statuses) in cluster_statuses {
2480            self.cluster_replica_statuses
2481                .initialize_cluster_statuses(cluster_id);
2482            for (replica_id, num_processes) in replica_statuses {
2483                self.cluster_replica_statuses
2484                    .initialize_cluster_replica_statuses(
2485                        cluster_id,
2486                        replica_id,
2487                        num_processes,
2488                        now,
2489                    );
2490            }
2491        }
2492
2493        let system_config = self.catalog().system_config();
2494
2495        // Inform metrics about the initial system configuration.
2496        mz_metrics::update_dyncfg(&system_config.dyncfg_updates());
2497
2498        // Inform the controllers about their initial configuration.
2499        let compute_config = flags::compute_config(system_config);
2500        let storage_config = flags::storage_config(system_config);
2501        let scheduling_config = flags::orchestrator_scheduling_config(system_config);
2502        let dyncfg_updates = system_config.dyncfg_updates();
2503        self.controller.compute.update_configuration(compute_config);
2504        self.controller.storage.update_parameters(storage_config);
2505        self.controller
2506            .update_orchestrator_scheduling_config(scheduling_config);
2507        self.controller.update_configuration(dyncfg_updates);
2508
2509        // Install the replica-local scoped overrides before creating any
2510        // replica below. Parts of a replica's configuration (its `TimelyConfig`,
2511        // its expiration offset) are resolved once, when the controller
2512        // provisions the replica, and must see its overrides at that point. The
2513        // push after the creation loop cannot serve this purpose, because those
2514        // values are frozen by then.
2515        let replica_dyncfg_overrides = self.replica_dyncfg_overrides();
2516        self.controller
2517            .update_replica_dyncfg_overrides(replica_dyncfg_overrides);
2518
2519        // Skip the credit consumption check at bootstrap under DisableClusterCreation behavior:
2520        // this codepath validates existing replicas at startup, not cluster creation, so it
2521        // must not block startup. New cluster creation is still gated by the DDL-time check.
2522        // The Disable case is already handled by a bail! in main.rs before we reach here.
2523        let enforce_credit_limit_at_bootstrap = !matches!(
2524            self.license_key.expiration_behavior,
2525            ExpirationBehavior::DisableClusterCreation,
2526        );
2527        if enforce_credit_limit_at_bootstrap {
2528            self.validate_resource_limit_numeric(
2529                Numeric::zero(),
2530                self.current_credit_consumption_rate(None),
2531                |system_vars| {
2532                    self.license_key
2533                        .max_credit_consumption_rate()
2534                        .map_or_else(|| system_vars.max_credit_consumption_rate(), Numeric::from)
2535                },
2536                "cluster replica",
2537                MAX_CREDIT_CONSUMPTION_RATE.name(),
2538            )?;
2539        }
2540
2541        let mut policies_to_set: BTreeMap<CompactionWindow, CollectionIdBundle> =
2542            Default::default();
2543
2544        let enable_worker_core_affinity =
2545            self.catalog().system_config().enable_worker_core_affinity();
2546        let enable_storage_introspection_logs = self
2547            .catalog()
2548            .system_config()
2549            .enable_storage_introspection_logs();
2550        for instance in self.catalog.clusters() {
2551            self.controller.create_cluster(
2552                instance.id,
2553                ClusterConfig {
2554                    arranged_logs: instance.log_indexes.clone(),
2555                    workload_class: instance.config.workload_class.clone(),
2556                },
2557            )?;
2558            for replica in instance.replicas() {
2559                let role = instance.role();
2560                self.controller.create_replica(
2561                    instance.id,
2562                    replica.replica_id,
2563                    instance.name.clone(),
2564                    replica.name.clone(),
2565                    role,
2566                    replica.config.clone(),
2567                    enable_worker_core_affinity,
2568                    enable_storage_introspection_logs,
2569                )?;
2570            }
2571        }
2572
2573        // Now that the compute instances and their replicas exist, push the
2574        // replica-local scoped overrides into the controllers so existing
2575        // replicas observe them at startup. The scoped (per-cluster and
2576        // per-replica) working copy was restored from the durable cache into
2577        // `CatalogState` while opening the catalog, so the last-known values are
2578        // in effect before the first parameter sync and through a sync outage.
2579        // This must run after the creation loop above: the push iterates the
2580        // controller's instances, so before they exist it is a no-op. It also
2581        // runs before dataflows are rendered later in bootstrap, so render-frozen
2582        // replica flags take effect. The cluster-coherent layer is read at plan
2583        // time.
2584        self.push_replica_dyncfg_overrides();
2585
2586        info!(
2587            "startup: coordinator init: bootstrap: preamble complete in {:?}",
2588            bootstrap_start.elapsed()
2589        );
2590
2591        let init_storage_collections_start = Instant::now();
2592        info!("startup: coordinator init: bootstrap: storage collections init beginning");
2593        self.bootstrap_storage_collections(&migrated_storage_collections_0dt)
2594            .await;
2595        info!(
2596            "startup: coordinator init: bootstrap: storage collections init complete in {:?}",
2597            init_storage_collections_start.elapsed()
2598        );
2599
2600        // The storage controller knows about the introspection collections now, so we can start
2601        // sinking introspection updates in the compute controller. It makes sense to do that as
2602        // soon as possible, to avoid updates piling up in the compute controller's internal
2603        // buffers.
2604        self.controller.start_compute_introspection_sink();
2605
2606        let sorting_start = Instant::now();
2607        info!("startup: coordinator init: bootstrap: sorting catalog entries");
2608        let entries = self.bootstrap_sort_catalog_entries();
2609        info!(
2610            "startup: coordinator init: bootstrap: sorting catalog entries complete in {:?}",
2611            sorting_start.elapsed()
2612        );
2613
2614        let optimize_dataflows_start = Instant::now();
2615        info!("startup: coordinator init: bootstrap: optimize dataflow plans beginning");
2616        let uncached_global_exps = self.bootstrap_dataflow_plans(&entries, cached_global_exprs)?;
2617        info!(
2618            "startup: coordinator init: bootstrap: optimize dataflow plans complete in {:?}",
2619            optimize_dataflows_start.elapsed()
2620        );
2621
2622        // We don't need to wait for the cache to update.
2623        let _fut = self.catalog().update_expression_cache(
2624            uncached_local_exprs.into_iter().collect(),
2625            uncached_global_exps.into_iter().collect(),
2626            Default::default(),
2627        );
2628
2629        // Select dataflow as-ofs. This step relies on the storage collections created by
2630        // `bootstrap_storage_collections` and the dataflow plans created by
2631        // `bootstrap_dataflow_plans`.
2632        let bootstrap_as_ofs_start = Instant::now();
2633        info!("startup: coordinator init: bootstrap: dataflow as-of bootstrapping beginning");
2634        let dataflow_read_holds = self.bootstrap_dataflow_as_ofs().await;
2635        info!(
2636            "startup: coordinator init: bootstrap: dataflow as-of bootstrapping complete in {:?}",
2637            bootstrap_as_ofs_start.elapsed()
2638        );
2639
2640        let postamble_start = Instant::now();
2641        info!("startup: coordinator init: bootstrap: postamble beginning");
2642
2643        let logs: BTreeSet<_> = BUILTINS::logs()
2644            .map(|log| self.catalog().resolve_builtin_log(log))
2645            .flat_map(|item_id| self.catalog().get_global_ids(&item_id))
2646            .collect();
2647
2648        let mut privatelink_connections = BTreeMap::new();
2649
2650        for entry in &entries {
2651            debug!(
2652                "coordinator init: installing {} {}",
2653                entry.item().typ(),
2654                entry.id()
2655            );
2656            let mut policy = entry.item().initial_logical_compaction_window();
2657            match entry.item() {
2658                // Currently catalog item rebuild assumes that sinks and
2659                // indexes are always built individually and does not store information
2660                // about how it was built. If we start building multiple sinks and/or indexes
2661                // using a single dataflow, we have to make sure the rebuild process re-runs
2662                // the same multiple-build dataflow.
2663                CatalogItem::Source(source) => {
2664                    // Propagate source compaction windows to subsources if needed.
2665                    if source.custom_logical_compaction_window.is_none() {
2666                        if let DataSourceDesc::IngestionExport { ingestion_id, .. } =
2667                            source.data_source
2668                        {
2669                            policy = Some(
2670                                self.catalog()
2671                                    .get_entry(&ingestion_id)
2672                                    .source()
2673                                    .expect("must be source")
2674                                    .custom_logical_compaction_window
2675                                    .unwrap_or_default(),
2676                            );
2677                        }
2678                    }
2679                    policies_to_set
2680                        .entry(policy.expect("sources have a compaction window"))
2681                        .or_insert_with(Default::default)
2682                        .storage_ids
2683                        .insert(source.global_id());
2684                }
2685                CatalogItem::Table(table) => {
2686                    policies_to_set
2687                        .entry(policy.expect("tables have a compaction window"))
2688                        .or_insert_with(Default::default)
2689                        .storage_ids
2690                        .extend(table.global_ids());
2691                }
2692                CatalogItem::Index(idx) => {
2693                    let policy_entry = policies_to_set
2694                        .entry(policy.expect("indexes have a compaction window"))
2695                        .or_insert_with(Default::default);
2696
2697                    if logs.contains(&idx.on) {
2698                        policy_entry
2699                            .compute_ids
2700                            .entry(idx.cluster_id)
2701                            .or_insert_with(BTreeSet::new)
2702                            .insert(idx.global_id());
2703                    } else {
2704                        let df_desc = self
2705                            .catalog()
2706                            .try_get_physical_plan(&idx.global_id())
2707                            .expect("added in `bootstrap_dataflow_plans`")
2708                            .clone();
2709
2710                        let df_meta = self
2711                            .catalog()
2712                            .try_get_dataflow_metainfo(&idx.global_id())
2713                            .expect("added in `bootstrap_dataflow_plans`");
2714
2715                        if self.catalog().state().system_config().enable_mz_notices() {
2716                            // Collect optimization hint updates.
2717                            self.catalog().state().pack_optimizer_notices(
2718                                &mut builtin_table_updates,
2719                                df_meta.optimizer_notices.iter(),
2720                                Diff::ONE,
2721                            );
2722                        }
2723
2724                        // What follows is morally equivalent to `self.ship_dataflow(df, idx.cluster_id)`,
2725                        // but we cannot call that as it will also downgrade the read hold on the index.
2726                        policy_entry
2727                            .compute_ids
2728                            .entry(idx.cluster_id)
2729                            .or_insert_with(Default::default)
2730                            .extend(df_desc.export_ids());
2731
2732                        self.controller
2733                            .compute
2734                            .create_dataflow(idx.cluster_id, df_desc, None)
2735                            .unwrap_or_terminate("cannot fail to create dataflows");
2736                    }
2737                }
2738                CatalogItem::View(_) => (),
2739                CatalogItem::MaterializedView(mview) => {
2740                    // Each version receives a read policy when it is created. Bootstrap
2741                    // must restore every policy because the oldest version owns the shared
2742                    // Persist shard and capability changes reach it through each newer
2743                    // version's primary link. A `NoPolicy` version would block that
2744                    // propagation and pin compaction.
2745                    policies_to_set
2746                        .entry(policy.expect("materialized views have a compaction window"))
2747                        .or_insert_with(Default::default)
2748                        .storage_ids
2749                        .extend(mview.global_ids());
2750
2751                    let mut df_desc = self
2752                        .catalog()
2753                        .try_get_physical_plan(&mview.global_id_writes())
2754                        .expect("added in `bootstrap_dataflow_plans`")
2755                        .clone();
2756
2757                    if let Some(initial_as_of) = mview.initial_as_of.clone() {
2758                        df_desc.set_initial_as_of(initial_as_of);
2759                    }
2760
2761                    // If we have a refresh schedule that has a last refresh, then set the `until` to the last refresh.
2762                    let until = mview
2763                        .refresh_schedule
2764                        .as_ref()
2765                        .and_then(|s| s.last_refresh())
2766                        .and_then(|r| r.try_step_forward());
2767                    if let Some(until) = until {
2768                        df_desc.until.meet_assign(&Antichain::from_elem(until));
2769                    }
2770
2771                    let df_meta = self
2772                        .catalog()
2773                        .try_get_dataflow_metainfo(&mview.global_id_writes())
2774                        .expect("added in `bootstrap_dataflow_plans`");
2775
2776                    if self.catalog().state().system_config().enable_mz_notices() {
2777                        // Collect optimization hint updates.
2778                        self.catalog().state().pack_optimizer_notices(
2779                            &mut builtin_table_updates,
2780                            df_meta.optimizer_notices.iter(),
2781                            Diff::ONE,
2782                        );
2783                    }
2784
2785                    self.ship_dataflow(df_desc, mview.cluster_id, mview.target_replica)
2786                        .await;
2787
2788                    // If this is a replacement MV, it must remain read-only until the replacement
2789                    // gets applied.
2790                    if mview.replacement_target.is_none() {
2791                        self.allow_writes(mview.cluster_id, mview.global_id_writes());
2792                    }
2793                }
2794                CatalogItem::MetricSink(metric_sink) => {
2795                    let df_desc = self
2796                        .catalog()
2797                        .try_get_physical_plan(&metric_sink.global_id)
2798                        .expect("added in `bootstrap_dataflow_plans`")
2799                        .clone();
2800
2801                    let df_meta = self
2802                        .catalog()
2803                        .try_get_dataflow_metainfo(&metric_sink.global_id)
2804                        .expect("added in `bootstrap_dataflow_plans`");
2805
2806                    if self.catalog().state().system_config().enable_mz_notices() {
2807                        // Collect optimization hint updates.
2808                        self.catalog().state().pack_optimizer_notices(
2809                            &mut builtin_table_updates,
2810                            df_meta.optimizer_notices.iter(),
2811                            Diff::ONE,
2812                        );
2813                    }
2814
2815                    // No read policy to set: the export is a sink, not a readable collection, so
2816                    // `ship_dataflow` has no index export to initialize a policy for.
2817                    self.ship_dataflow(df_desc, metric_sink.cluster_id, None)
2818                        .await;
2819                }
2820                CatalogItem::Sink(sink) => {
2821                    policies_to_set
2822                        .entry(CompactionWindow::Default)
2823                        .or_insert_with(Default::default)
2824                        .storage_ids
2825                        .insert(sink.global_id());
2826                }
2827                CatalogItem::Connection(catalog_connection) => {
2828                    if let ConnectionDetails::AwsPrivatelink(conn) = &catalog_connection.details {
2829                        privatelink_connections.insert(
2830                            entry.id(),
2831                            VpcEndpointConfig {
2832                                aws_service_name: conn.service_name.clone(),
2833                                availability_zone_ids: conn.availability_zones.clone(),
2834                            },
2835                        );
2836                    }
2837                }
2838                // Nothing to do for these cases
2839                CatalogItem::Log(_)
2840                | CatalogItem::Type(_)
2841                | CatalogItem::Func(_)
2842                | CatalogItem::Secret(_) => {}
2843            }
2844        }
2845
2846        if let Some(cloud_resource_controller) = &self.cloud_resource_controller {
2847            // Clean up any extraneous VpcEndpoints that shouldn't exist.
2848            let existing_vpc_endpoints = cloud_resource_controller
2849                .list_vpc_endpoints()
2850                .await
2851                .context("list vpc endpoints")?;
2852            let existing_vpc_endpoints = BTreeSet::from_iter(existing_vpc_endpoints.into_keys());
2853            let desired_vpc_endpoints = privatelink_connections.keys().cloned().collect();
2854            let vpc_endpoints_to_remove = existing_vpc_endpoints.difference(&desired_vpc_endpoints);
2855            for id in vpc_endpoints_to_remove {
2856                cloud_resource_controller
2857                    .delete_vpc_endpoint(*id)
2858                    .await
2859                    .context("deleting extraneous vpc endpoint")?;
2860            }
2861
2862            // Ensure desired VpcEndpoints are up to date.
2863            for (id, spec) in privatelink_connections {
2864                cloud_resource_controller
2865                    .ensure_vpc_endpoint(id, spec)
2866                    .await
2867                    .context("ensuring vpc endpoint")?;
2868            }
2869        }
2870
2871        // Having installed all entries, creating all constraints, we can now drop read holds and
2872        // relax read policies.
2873        drop(dataflow_read_holds);
2874        // TODO -- Improve `initialize_read_policies` API so we can avoid calling this in a loop.
2875        for (cw, policies) in policies_to_set {
2876            self.initialize_read_policies(&policies, cw).await;
2877        }
2878
2879        // Expose mapping from T-shirt sizes to actual sizes
2880        builtin_table_updates.extend(
2881            self.catalog().state().resolve_builtin_table_updates(
2882                self.catalog().state().pack_all_replica_size_updates(),
2883            ),
2884        );
2885
2886        debug!("startup: coordinator init: bootstrap: initializing migrated builtin tables");
2887        // When 0dt is enabled, we create new shards for any migrated builtin storage collections.
2888        // In read-only mode, the migrated builtin tables (which are a subset of migrated builtin
2889        // storage collections) need to be back-filled so that any dependent dataflow can be
2890        // hydrated. Additionally, these shards are not registered with the txn-shard, and cannot
2891        // be registered while in read-only, so they are written to directly.
2892        let migrated_updates_fut = if self.controller.read_only() {
2893            let min_timestamp = Timestamp::minimum();
2894            let migrated_builtin_table_updates: Vec<_> = builtin_table_updates
2895                .extract_if(.., |update| {
2896                    let gid = self.catalog().get_entry(&update.id).latest_global_id();
2897                    migrated_storage_collections_0dt.contains(&update.id)
2898                        && self
2899                            .controller
2900                            .storage_collections
2901                            .collection_frontiers(gid)
2902                            .expect("all tables are registered")
2903                            .write_frontier
2904                            .elements()
2905                            == &[min_timestamp]
2906                })
2907                .collect();
2908            if migrated_builtin_table_updates.is_empty() {
2909                futures::future::ready(()).boxed()
2910            } else {
2911                // Group all updates per-table.
2912                let mut grouped_appends: BTreeMap<GlobalId, Vec<TableData>> = BTreeMap::new();
2913                for update in migrated_builtin_table_updates {
2914                    let gid = self.catalog().get_entry(&update.id).latest_global_id();
2915                    grouped_appends.entry(gid).or_default().push(update.data);
2916                }
2917                info!(
2918                    "coordinator init: rehydrating migrated builtin tables in read-only mode: {:?}",
2919                    grouped_appends.keys().collect::<Vec<_>>()
2920                );
2921
2922                // Consolidate Row data, staged batches must already be consolidated.
2923                let mut all_appends = Vec::with_capacity(grouped_appends.len());
2924                for (item_id, table_data) in grouped_appends.into_iter() {
2925                    let mut all_rows = Vec::new();
2926                    let mut all_data = Vec::new();
2927                    for data in table_data {
2928                        match data {
2929                            TableData::Rows(rows) => all_rows.extend(rows),
2930                            TableData::Batches(_) => all_data.push(data),
2931                        }
2932                    }
2933                    differential_dataflow::consolidation::consolidate(&mut all_rows);
2934                    all_data.push(TableData::Rows(all_rows));
2935
2936                    // TODO(parkmycar): Use SmallVec throughout.
2937                    all_appends.push((item_id, all_data));
2938                }
2939
2940                let fut = self
2941                    .controller
2942                    .storage
2943                    .append_table(min_timestamp, boot_ts.step_forward(), all_appends)
2944                    .expect("cannot fail to append");
2945                async {
2946                    fut.await
2947                        .expect("One-shot shouldn't be dropped during bootstrap")
2948                        .unwrap_or_terminate("cannot fail to append")
2949                }
2950                .boxed()
2951            }
2952        } else {
2953            futures::future::ready(()).boxed()
2954        };
2955
2956        info!(
2957            "startup: coordinator init: bootstrap: postamble complete in {:?}",
2958            postamble_start.elapsed()
2959        );
2960
2961        let builtin_update_start = Instant::now();
2962        info!("startup: coordinator init: bootstrap: generate builtin updates beginning");
2963
2964        if self.controller.read_only() {
2965            info!(
2966                "coordinator init: bootstrap: stashing builtin table updates while in read-only mode"
2967            );
2968
2969            self.buffered_builtin_table_updates
2970                .as_mut()
2971                .expect("in read-only mode")
2972                .append(&mut builtin_table_updates);
2973        } else {
2974            self.bootstrap_tables(&entries, builtin_table_updates).await;
2975        };
2976        info!(
2977            "startup: coordinator init: bootstrap: generate builtin updates complete in {:?}",
2978            builtin_update_start.elapsed()
2979        );
2980
2981        let cleanup_secrets_start = Instant::now();
2982        info!("startup: coordinator init: bootstrap: generate secret cleanup beginning");
2983        // Cleanup orphaned secrets. Errors during list() or delete() do not
2984        // need to prevent bootstrap from succeeding; we will retry next
2985        // startup.
2986        {
2987            // Destructure Self so we can selectively move fields into the async
2988            // task.
2989            let Self {
2990                secrets_controller,
2991                catalog,
2992                ..
2993            } = self;
2994
2995            let next_user_item_id = catalog.get_next_user_item_id().await?;
2996            let next_system_item_id = catalog.get_next_system_item_id().await?;
2997            let read_only = self.controller.read_only();
2998            // Fetch all IDs from the catalog to future-proof against other
2999            // things using secrets. Today, SECRET and CONNECTION objects use
3000            // secrets_controller.ensure, but more things could in the future
3001            // that would be easy to miss adding here.
3002            let catalog_ids: BTreeSet<CatalogItemId> =
3003                catalog.entries().map(|entry| entry.id()).collect();
3004            let secrets_controller = Arc::clone(secrets_controller);
3005
3006            spawn(|| "cleanup-orphaned-secrets", async move {
3007                if read_only {
3008                    info!(
3009                        "coordinator init: not cleaning up orphaned secrets while in read-only mode"
3010                    );
3011                    return;
3012                }
3013                info!("coordinator init: cleaning up orphaned secrets");
3014
3015                match secrets_controller.list().await {
3016                    Ok(controller_secrets) => {
3017                        let controller_secrets: BTreeSet<CatalogItemId> =
3018                            controller_secrets.into_iter().collect();
3019                        let orphaned = controller_secrets.difference(&catalog_ids);
3020                        for id in orphaned {
3021                            let id_too_large = match id {
3022                                CatalogItemId::System(id) => *id >= next_system_item_id,
3023                                CatalogItemId::User(id) => *id >= next_user_item_id,
3024                                CatalogItemId::IntrospectionSourceIndex(_)
3025                                | CatalogItemId::Transient(_) => false,
3026                            };
3027                            if id_too_large {
3028                                info!(
3029                                    %next_user_item_id, %next_system_item_id,
3030                                    "coordinator init: not deleting orphaned secret {id} that was likely created by a newer deploy generation"
3031                                );
3032                            } else {
3033                                info!("coordinator init: deleting orphaned secret {id}");
3034                                fail_point!("orphan_secrets");
3035                                if let Err(e) = secrets_controller.delete(*id).await {
3036                                    warn!(
3037                                        "Dropping orphaned secret has encountered an error: {}",
3038                                        e
3039                                    );
3040                                }
3041                            }
3042                        }
3043                    }
3044                    Err(e) => warn!("Failed to list secrets during orphan cleanup: {:?}", e),
3045                }
3046            });
3047        }
3048        info!(
3049            "startup: coordinator init: bootstrap: generate secret cleanup complete in {:?}",
3050            cleanup_secrets_start.elapsed()
3051        );
3052
3053        // Run all of our final steps concurrently.
3054        let final_steps_start = Instant::now();
3055        info!(
3056            "startup: coordinator init: bootstrap: migrate builtin tables in read-only mode beginning"
3057        );
3058        migrated_updates_fut
3059            .instrument(info_span!("coord::bootstrap::final"))
3060            .await;
3061
3062        debug!(
3063            "startup: coordinator init: bootstrap: announcing completion of initialization to controller"
3064        );
3065        // Announce the completion of initialization.
3066        self.controller.initialization_complete();
3067
3068        // Initialize unified introspection.
3069        self.bootstrap_introspection_subscribes().await;
3070
3071        info!(
3072            "startup: coordinator init: bootstrap: migrate builtin tables in read-only mode complete in {:?}",
3073            final_steps_start.elapsed()
3074        );
3075
3076        info!(
3077            "startup: coordinator init: bootstrap complete in {:?}",
3078            bootstrap_start.elapsed()
3079        );
3080        Ok(())
3081    }
3082
3083    /// Prepares tables for writing by resetting them to a known state and
3084    /// appending the given builtin table updates. The timestamp oracle
3085    /// will be advanced to the write timestamp of the append when this
3086    /// method returns.
3087    #[allow(clippy::async_yields_async)]
3088    #[instrument]
3089    async fn bootstrap_tables(
3090        &mut self,
3091        entries: &[CatalogEntry],
3092        mut builtin_table_updates: Vec<BuiltinTableUpdate>,
3093    ) {
3094        /// Smaller helper struct of metadata for bootstrapping tables.
3095        struct TableMetadata<'a> {
3096            id: CatalogItemId,
3097            name: &'a QualifiedItemName,
3098            table: &'a Table,
3099        }
3100
3101        // Filter our entries down to just tables.
3102        let table_metas: Vec<_> = entries
3103            .into_iter()
3104            .filter_map(|entry| {
3105                entry.table().map(|table| TableMetadata {
3106                    id: entry.id(),
3107                    name: entry.name(),
3108                    table,
3109                })
3110            })
3111            .collect();
3112
3113        // Append empty batches to advance the timestamp of all tables.
3114        debug!("coordinator init: advancing all tables to current timestamp");
3115        let WriteTimestamp {
3116            timestamp: write_ts,
3117            advance_to,
3118        } = self.get_local_write_ts().await;
3119        let appends = table_metas
3120            .iter()
3121            .map(|meta| (meta.table.global_id_writes(), Vec::new()))
3122            .collect();
3123        // Append the tables in the background. We apply the write timestamp before getting a read
3124        // timestamp and reading a snapshot of each table, so the snapshots will block on their own
3125        // until the appends are complete.
3126        let table_fence_rx = self
3127            .controller
3128            .storage
3129            .append_table(write_ts.clone(), advance_to, appends)
3130            .expect("invalid updates");
3131
3132        self.apply_local_write(write_ts).await;
3133
3134        // Add builtin table updates the clear the contents of all system tables
3135        debug!("coordinator init: resetting system tables");
3136        let read_ts = self.get_local_read_ts().await;
3137
3138        // Filter out tables whose contents must survive restarts:
3139        // 'mz_storage_usage_by_shard' for billing, and
3140        // 'mz_object_arrangement_size_history', which accumulates history that
3141        // is pruned by its own retention period instead.
3142        let mz_storage_usage_by_shard_schema: SchemaSpecifier = self
3143            .catalog()
3144            .resolve_system_schema(MZ_STORAGE_USAGE_BY_SHARD.schema)
3145            .into();
3146        let arrangement_size_history_schema: SchemaSpecifier = self
3147            .catalog()
3148            .resolve_system_schema(MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY.schema)
3149            .into();
3150        let is_retained_across_restarts = |meta: &TableMetadata| -> bool {
3151            (meta.name.item == MZ_STORAGE_USAGE_BY_SHARD.name
3152                && meta.name.qualifiers.schema_spec == mz_storage_usage_by_shard_schema)
3153                || (meta.name.item == MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY.name
3154                    && meta.name.qualifiers.schema_spec == arrangement_size_history_schema)
3155        };
3156
3157        let mut retraction_tasks = Vec::new();
3158        let system_tables: Vec<_> = table_metas
3159            .iter()
3160            .filter(|meta| meta.id.is_system() && !is_retained_across_restarts(meta))
3161            .collect();
3162
3163        for system_table in system_tables {
3164            let table_id = system_table.id;
3165            let full_name = self.catalog().resolve_full_name(system_table.name, None);
3166            debug!("coordinator init: resetting system table {full_name} ({table_id})");
3167
3168            // Fetch the current contents of the table for retraction.
3169            let snapshot_fut = self
3170                .controller
3171                .storage_collections
3172                .snapshot_cursor(system_table.table.global_id_writes(), read_ts);
3173            let batch_fut = self
3174                .controller
3175                .storage_collections
3176                .create_update_builder(system_table.table.global_id_writes());
3177
3178            let task = spawn(|| format!("snapshot-{table_id}"), async move {
3179                // Create a TimestamplessUpdateBuilder.
3180                let mut batch = batch_fut
3181                    .await
3182                    .unwrap_or_terminate("cannot fail to create a batch for a BuiltinTable");
3183                tracing::info!(?table_id, "starting snapshot");
3184                // Get a cursor which will emit a consolidated snapshot.
3185                let mut snapshot_cursor = snapshot_fut
3186                    .await
3187                    .unwrap_or_terminate("cannot fail to snapshot");
3188
3189                // Retract the current contents, spilling into our builder.
3190                while let Some(values) = snapshot_cursor.next().await {
3191                    for (key, _t, d) in values {
3192                        let d_invert = d.neg();
3193                        batch.add(&key, &(), &d_invert).await;
3194                    }
3195                }
3196                tracing::info!(?table_id, "finished snapshot");
3197
3198                let batch = batch.finish().await;
3199                BuiltinTableUpdate::batch(table_id, batch)
3200            });
3201            retraction_tasks.push(task);
3202        }
3203
3204        let retractions_res = futures::future::join_all(retraction_tasks).await;
3205        for retractions in retractions_res {
3206            builtin_table_updates.push(retractions);
3207        }
3208
3209        // Now that the snapshots are complete, the appends must also be complete.
3210        table_fence_rx
3211            .await
3212            .expect("One-shot shouldn't be dropped during bootstrap")
3213            .unwrap_or_terminate("cannot fail to append");
3214
3215        info!("coordinator init: sending builtin table updates");
3216        let builtin_updates_fut = self.builtin_table_update().execute(builtin_table_updates);
3217        // Wait for the committer to apply the write, so the builtin tables are readable before
3218        // we start serving. The committer allocates the timestamp and advances the oracle.
3219        builtin_updates_fut.await;
3220    }
3221
3222    /// Initializes all storage collections required by catalog objects in the storage controller.
3223    ///
3224    /// This method takes care of collection creation, as well as migration of existing
3225    /// collections.
3226    ///
3227    /// Creating all storage collections in a single `create_collections` call, rather than on
3228    /// demand, is more efficient as it reduces the number of writes to durable storage. It also
3229    /// allows subsequent bootstrap logic to fetch metadata (such as frontiers) of arbitrary
3230    /// storage collections, without needing to worry about dependency order.
3231    ///
3232    /// `migrated_storage_collections` is a set of builtin storage collections that have been
3233    /// migrated and should be handled specially.
3234    #[instrument]
3235    async fn bootstrap_storage_collections(
3236        &mut self,
3237        migrated_storage_collections: &BTreeSet<CatalogItemId>,
3238    ) {
3239        let catalog = self.catalog();
3240
3241        let source_desc = |object_id: GlobalId,
3242                           data_source: &DataSourceDesc,
3243                           desc: &RelationDesc,
3244                           timeline: &Timeline| {
3245            let data_source = match data_source.clone() {
3246                // Re-announce the source description.
3247                DataSourceDesc::Ingestion { desc, cluster_id } => {
3248                    let desc = desc.into_inline_connection(catalog.state());
3249                    let ingestion = IngestionDescription::new(desc, cluster_id, object_id);
3250                    DataSource::Ingestion(ingestion)
3251                }
3252                DataSourceDesc::OldSyntaxIngestion {
3253                    desc,
3254                    progress_subsource,
3255                    data_config,
3256                    details,
3257                    cluster_id,
3258                } => {
3259                    let desc = desc.into_inline_connection(catalog.state());
3260                    let data_config = data_config.into_inline_connection(catalog.state());
3261                    // TODO(parkmycar): We should probably check the type here, but I'm not sure if
3262                    // this will always be a Source or a Table.
3263                    let progress_subsource =
3264                        catalog.get_entry(&progress_subsource).latest_global_id();
3265                    let mut ingestion =
3266                        IngestionDescription::new(desc, cluster_id, progress_subsource);
3267                    let legacy_export = SourceExport {
3268                        storage_metadata: (),
3269                        data_config,
3270                        details,
3271                    };
3272                    ingestion.source_exports.insert(object_id, legacy_export);
3273
3274                    DataSource::Ingestion(ingestion)
3275                }
3276                DataSourceDesc::IngestionExport {
3277                    ingestion_id,
3278                    external_reference: _,
3279                    details,
3280                    data_config,
3281                } => {
3282                    // TODO(parkmycar): We should probably check the type here, but I'm not sure if
3283                    // this will always be a Source or a Table.
3284                    let ingestion_id = catalog.get_entry(&ingestion_id).latest_global_id();
3285
3286                    DataSource::IngestionExport {
3287                        ingestion_id,
3288                        details,
3289                        data_config: data_config.into_inline_connection(catalog.state()),
3290                    }
3291                }
3292                DataSourceDesc::Webhook { .. } => DataSource::Webhook,
3293                DataSourceDesc::Progress => DataSource::Progress,
3294                DataSourceDesc::Introspection(introspection) => {
3295                    DataSource::Introspection(introspection)
3296                }
3297                DataSourceDesc::Catalog => DataSource::Other,
3298            };
3299            CollectionDescription {
3300                desc: desc.clone(),
3301                data_source,
3302                since: None,
3303                timeline: Some(timeline.clone()),
3304                primary: None,
3305            }
3306        };
3307
3308        let mut compute_collections = vec![];
3309        let mut collections = vec![];
3310        for entry in catalog.entries() {
3311            match entry.item() {
3312                CatalogItem::Source(source) => {
3313                    collections.push((
3314                        source.global_id(),
3315                        source_desc(
3316                            source.global_id(),
3317                            &source.data_source,
3318                            &source.desc,
3319                            &source.timeline,
3320                        ),
3321                    ));
3322                }
3323                CatalogItem::Table(table) => {
3324                    match &table.data_source {
3325                        TableDataSource::TableWrites { defaults: _ } => {
3326                            let versions: BTreeMap<_, _> = table
3327                                .collection_descs()
3328                                .map(|(gid, version, desc)| (version, (gid, desc)))
3329                                .collect();
3330                            let collection_descs = versions.iter().map(|(version, (gid, desc))| {
3331                                let next_version = version.bump();
3332                                let primary_collection =
3333                                    versions.get(&next_version).map(|(gid, _desc)| gid).copied();
3334                                let mut collection_desc =
3335                                    CollectionDescription::for_table(desc.clone());
3336                                collection_desc.primary = primary_collection;
3337
3338                                (*gid, collection_desc)
3339                            });
3340                            collections.extend(collection_descs);
3341                        }
3342                        TableDataSource::DataSource {
3343                            desc: data_source_desc,
3344                            timeline,
3345                        } => {
3346                            // TODO(alter_table): Support versioning tables that read from sources.
3347                            soft_assert_eq_or_log!(table.collections.len(), 1);
3348                            let collection_descs =
3349                                table.collection_descs().map(|(gid, _version, desc)| {
3350                                    (
3351                                        gid,
3352                                        source_desc(
3353                                            entry.latest_global_id(),
3354                                            data_source_desc,
3355                                            &desc,
3356                                            timeline,
3357                                        ),
3358                                    )
3359                                });
3360                            collections.extend(collection_descs);
3361                        }
3362                    };
3363                }
3364                CatalogItem::MaterializedView(mv) => {
3365                    // Applying a replacement preserves the ownership link established when the
3366                    // replacement was created. The oldest collection owns the shard, each applied
3367                    // replacement points to its predecessor, and a pending replacement starts by
3368                    // pointing to its target's latest collection.
3369                    //
3370                    // NOTE: Versioned tables chain in the opposite direction because their latest
3371                    // version owns the shard. Each chain matches its runtime replacement path.
3372                    let mut primary = mv
3373                        .replacement_target
3374                        .map(|target_id| catalog.get_entry(&target_id).latest_global_id());
3375                    let collection_descs = mv.collection_descs().map(|(gid, _version, desc)| {
3376                        let mut collection_desc =
3377                            CollectionDescription::for_other(desc, mv.initial_as_of.clone());
3378                        collection_desc.primary = primary;
3379                        primary = Some(gid);
3380                        (gid, collection_desc)
3381                    });
3382
3383                    collections.extend(collection_descs);
3384                    compute_collections.push((mv.global_id_writes(), mv.desc.latest()));
3385                }
3386                CatalogItem::Sink(sink) => {
3387                    let storage_sink_from_entry = self.catalog().get_entry_by_global_id(&sink.from);
3388                    let from_desc = storage_sink_from_entry
3389                        .relation_desc()
3390                        .expect("sinks can only be built on items with descs")
3391                        .into_owned();
3392                    let collection_desc = CollectionDescription {
3393                        // TODO(sinks): make generic once we have more than one sink type.
3394                        desc: KAFKA_PROGRESS_DESC.clone(),
3395                        data_source: DataSource::Sink {
3396                            desc: ExportDescription {
3397                                sink: StorageSinkDesc {
3398                                    from: sink.from,
3399                                    from_desc,
3400                                    connection: sink
3401                                        .connection
3402                                        .clone()
3403                                        .into_inline_connection(self.catalog().state()),
3404                                    envelope: sink.envelope,
3405                                    as_of: Antichain::from_elem(Timestamp::minimum()),
3406                                    with_snapshot: sink.with_snapshot,
3407                                    version: sink.version,
3408                                    from_storage_metadata: (),
3409                                    to_storage_metadata: (),
3410                                    commit_interval: sink.commit_interval,
3411                                },
3412                                instance_id: sink.cluster_id,
3413                            },
3414                        },
3415                        since: None,
3416                        timeline: None,
3417                        primary: None,
3418                    };
3419                    collections.push((sink.global_id, collection_desc));
3420                }
3421                CatalogItem::Log(_)
3422                | CatalogItem::View(_)
3423                | CatalogItem::Index(_)
3424                | CatalogItem::Type(_)
3425                | CatalogItem::Func(_)
3426                | CatalogItem::Secret(_)
3427                | CatalogItem::Connection(_)
3428                // Nothing to bootstrap: a metric sink has no storage collection, it publishes
3429                // into the replica's metrics registry.
3430                | CatalogItem::MetricSink(_) => (),
3431            }
3432        }
3433
3434        let register_ts = if self.controller.read_only() {
3435            self.get_local_read_ts().await
3436        } else {
3437            // Getting a write timestamp bumps the write timestamp in the
3438            // oracle, which we're not allowed in read-only mode.
3439            self.get_local_write_ts().await.timestamp
3440        };
3441
3442        let storage_metadata = self.catalog.state().storage_metadata();
3443        let migrated_storage_collections = migrated_storage_collections
3444            .into_iter()
3445            .flat_map(|item_id| self.catalog.get_entry(item_id).global_ids())
3446            .collect();
3447
3448        // Before possibly creating collections, make sure their schemas are correct.
3449        //
3450        // Across different versions of Materialize the nullability of columns can change based on
3451        // updates to our optimizer.
3452        self.controller
3453            .storage
3454            .evolve_nullability_for_bootstrap(storage_metadata, compute_collections)
3455            .await
3456            .unwrap_or_terminate("cannot fail to evolve collections");
3457
3458        // New builtin storage collections are by default created with [0] since/upper frontiers.
3459        // For collections that have dependencies on other collections (MVs, CTs), this can violate
3460        // the frontier invariants assumed by as-of selection. For example, as-of selection expects
3461        // to be able to pick up computing a materialized view from its most recent upper, but if
3462        // that upper is [0] it's likely that the required times are not available anymore in the
3463        // MV inputs.
3464        //
3465        // To avoid violating frontier invariants, we need to bump their sinces to times greater
3466        // than all of their upstream storage inputs. To know the since of a storage input, it has
3467        // to be registered with the storage controller first. Thus we register collections in
3468        // layers: Each iteration registers the collections whose dependencies are all already
3469        // registered.
3470        let mut pending: BTreeMap<_, _> = collections.into_iter().collect();
3471
3472        // Precompute storage-collection dependencies for each collection.
3473        let transitive_dep_gids: BTreeMap<_, _> = pending
3474            .keys()
3475            .map(|gid| {
3476                let entry = self.catalog.get_entry_by_global_id(gid);
3477                let item_id = entry.id();
3478                let deps = self.catalog.state().transitive_uses(item_id);
3479                let dep_gids: BTreeSet<_> = deps
3480                    // Ignore self-dependencies. For example, `transitive_uses` includes the input ID,
3481                    // and CTs can depend on themselves.
3482                    .filter(|dep_id| *dep_id != item_id)
3483                    .map(|dep_id| self.catalog.get_entry(&dep_id).latest_global_id())
3484                    // Ignore dependencies on objects that are not storage collections.
3485                    .filter(|dep_gid| pending.contains_key(dep_gid))
3486                    .collect();
3487                (*gid, dep_gids)
3488            })
3489            .collect();
3490
3491        let mut created_gids = Vec::new();
3492
3493        while !pending.is_empty() {
3494            // Drain collections whose dependencies have all been registered already
3495            // (i.e., are not in `pending`).
3496            let ready_gids: BTreeSet<_> = pending
3497                .keys()
3498                .filter(|gid| {
3499                    let mut deps = transitive_dep_gids[gid].iter();
3500                    !deps.any(|dep_gid| pending.contains_key(dep_gid))
3501                })
3502                .copied()
3503                .collect();
3504            let mut ready: Vec<_> = pending
3505                .extract_if(.., |gid, _| ready_gids.contains(gid))
3506                .collect();
3507
3508            // Bump sinces of builtin collections.
3509            for (gid, collection) in &mut ready {
3510                // Don't silently overwrite an explicitly specified `since`.
3511                if !gid.is_system() || collection.since.is_some() {
3512                    continue;
3513                }
3514
3515                let mut derived_since = Antichain::from_elem(Timestamp::MIN);
3516                for dep_gid in &transitive_dep_gids[gid] {
3517                    let (since, _) = self
3518                        .controller
3519                        .storage
3520                        .collection_frontiers(*dep_gid)
3521                        .expect("previously registered");
3522                    derived_since.join_assign(&since);
3523                }
3524                collection.since = Some(derived_since);
3525            }
3526
3527            if ready.is_empty() {
3528                soft_panic_or_log!(
3529                    "cycle in storage collections: {:?}",
3530                    pending.keys().collect::<Vec<_>>(),
3531                );
3532                // We get here only due to a bug. Rather than crash-looping, we try our best to
3533                // reach a sane state by attempting to register all the remaining collections at
3534                // once.
3535                ready = mem::take(&mut pending).into_iter().collect();
3536            }
3537
3538            created_gids.extend(ready.iter().map(|(gid, _collection)| *gid));
3539
3540            self.controller
3541                .storage
3542                .create_collections_for_bootstrap(
3543                    storage_metadata,
3544                    Some(register_ts),
3545                    ready,
3546                    &migrated_storage_collections,
3547                )
3548                .await
3549                .unwrap_or_terminate("cannot fail to create collections");
3550        }
3551
3552        // Register txn-wal tables before the later system-table snapshot.
3553        self.controller
3554            .storage
3555            .register_table_collections(register_ts, created_gids)
3556            .await
3557            .unwrap_or_terminate("cannot fail to register tables");
3558
3559        if !self.controller.read_only() {
3560            self.apply_local_write(register_ts).await;
3561        }
3562    }
3563
3564    /// Returns the current list of catalog entries, sorted into an appropriate order for
3565    /// bootstrapping.
3566    ///
3567    /// The returned entries are in dependency order. Indexes are sorted immediately after the
3568    /// objects they index, to ensure that all dependants of these indexed objects can make use of
3569    /// the respective indexes.
3570    fn bootstrap_sort_catalog_entries(&self) -> Vec<CatalogEntry> {
3571        let mut indexes_on = BTreeMap::<_, Vec<_>>::new();
3572        let mut non_indexes = Vec::new();
3573        for entry in self.catalog().entries().cloned() {
3574            if let Some(index) = entry.index() {
3575                let on = self.catalog().get_entry_by_global_id(&index.on);
3576                indexes_on.entry(on.id()).or_default().push(entry);
3577            } else {
3578                non_indexes.push(entry);
3579            }
3580        }
3581
3582        let key_fn = |entry: &CatalogEntry| entry.id;
3583        let dependencies_fn = |entry: &CatalogEntry| entry.uses();
3584        sort_topological(&mut non_indexes, key_fn, dependencies_fn);
3585
3586        let mut result = Vec::new();
3587        for entry in non_indexes {
3588            let id = entry.id();
3589            result.push(entry);
3590            if let Some(mut indexes) = indexes_on.remove(&id) {
3591                result.append(&mut indexes);
3592            }
3593        }
3594
3595        soft_assert_or_log!(
3596            indexes_on.is_empty(),
3597            "indexes with missing dependencies: {indexes_on:?}",
3598        );
3599
3600        result
3601    }
3602
3603    /// Invokes the optimizer on all indexes and materialized views in the catalog and inserts the
3604    /// resulting dataflow plans into the catalog state.
3605    ///
3606    /// `ordered_catalog_entries` must be sorted in dependency order, with dependencies ordered
3607    /// before their dependants.
3608    ///
3609    /// This method does not perform timestamp selection for the dataflows, nor does it create them
3610    /// in the compute controller. Both of these steps happen later during bootstrapping.
3611    ///
3612    /// Returns a map of expressions that were not cached.
3613    #[instrument]
3614    fn bootstrap_dataflow_plans(
3615        &mut self,
3616        ordered_catalog_entries: &[CatalogEntry],
3617        mut cached_global_exprs: BTreeMap<GlobalId, GlobalExpressions>,
3618    ) -> Result<BTreeMap<GlobalId, GlobalExpressions>, AdapterError> {
3619        // The optimizer expects to be able to query its `ComputeInstanceSnapshot` for
3620        // collections the current dataflow can depend on. But since we don't yet install anything
3621        // on compute instances, the snapshot information is incomplete. We fix that by manually
3622        // updating `ComputeInstanceSnapshot` objects to ensure they contain collections previously
3623        // optimized.
3624        let mut instance_snapshots = BTreeMap::new();
3625        let mut uncached_expressions = BTreeMap::new();
3626
3627        let optimizer_config = |catalog: &Catalog, cluster_id| {
3628            let system_config = catalog.system_config();
3629            let overrides = catalog.get_cluster(cluster_id).config.features();
3630            OptimizerConfig::from(system_config)
3631                .override_from(&overrides)
3632                // A cluster-scoped LaunchDarkly rule beats a manual `FEATURES`
3633                // pin.
3634                .override_from(
3635                    &catalog
3636                        .state()
3637                        .cluster_scoped_optimizer_overrides(cluster_id),
3638                )
3639        };
3640
3641        for entry in ordered_catalog_entries {
3642            match entry.item() {
3643                CatalogItem::Index(idx) => {
3644                    // Collect optimizer parameters.
3645                    let compute_instance =
3646                        instance_snapshots.entry(idx.cluster_id).or_insert_with(|| {
3647                            self.instance_snapshot(idx.cluster_id)
3648                                .expect("compute instance exists")
3649                        });
3650                    let global_id = idx.global_id();
3651
3652                    // The index may already be installed on the compute instance. For example,
3653                    // this is the case for introspection indexes.
3654                    if compute_instance.contains_collection(&global_id) {
3655                        continue;
3656                    }
3657
3658                    let optimizer_config = optimizer_config(&self.catalog, idx.cluster_id);
3659
3660                    let (optimized_plan, physical_plan, metainfo) =
3661                        match cached_global_exprs.remove(&global_id) {
3662                            Some(global_expressions)
3663                                if global_expressions.optimizer_features
3664                                    == optimizer_config.features =>
3665                            {
3666                                debug!("global expression cache hit for {global_id:?}");
3667                                (
3668                                    global_expressions.global_mir,
3669                                    global_expressions.physical_plan,
3670                                    global_expressions.dataflow_metainfos,
3671                                )
3672                            }
3673                            Some(_) | None => {
3674                                let (optimized_plan, global_lir_plan) = {
3675                                    // Build an optimizer for this INDEX.
3676                                    let mut optimizer = optimize::index::Optimizer::new(
3677                                        self.owned_catalog(),
3678                                        compute_instance.clone(),
3679                                        global_id,
3680                                        optimizer_config.clone(),
3681                                        self.optimizer_metrics(),
3682                                    );
3683
3684                                    // MIR ⇒ MIR optimization (global)
3685                                    let index_plan = optimize::index::Index::new(
3686                                        entry.name().clone(),
3687                                        idx.on,
3688                                        idx.keys.to_vec(),
3689                                    );
3690                                    let global_mir_plan = optimizer.optimize(index_plan)?;
3691                                    let optimized_plan = global_mir_plan.df_desc().clone();
3692
3693                                    // MIR ⇒ LIR lowering and LIR ⇒ LIR optimization (global)
3694                                    let global_lir_plan = optimizer.optimize(global_mir_plan)?;
3695
3696                                    (optimized_plan, global_lir_plan)
3697                                };
3698
3699                                let (physical_plan, metainfo) = global_lir_plan.unapply();
3700                                let metainfo = {
3701                                    // Pre-allocate a vector of transient GlobalIds for each notice.
3702                                    let notice_ids =
3703                                        std::iter::repeat_with(|| self.allocate_transient_id())
3704                                            .map(|(_item_id, gid)| gid)
3705                                            .take(metainfo.optimizer_notices.len())
3706                                            .collect::<Vec<_>>();
3707                                    // Return a metainfo with rendered notices.
3708                                    self.catalog().render_notices(
3709                                        metainfo,
3710                                        notice_ids,
3711                                        Some(idx.global_id()),
3712                                    )
3713                                };
3714                                uncached_expressions.insert(
3715                                    global_id,
3716                                    GlobalExpressions {
3717                                        global_mir: optimized_plan.clone(),
3718                                        physical_plan: physical_plan.clone(),
3719                                        dataflow_metainfos: metainfo.clone(),
3720                                        optimizer_features: optimizer_config.features.clone(),
3721                                    },
3722                                );
3723                                (optimized_plan, physical_plan, metainfo)
3724                            }
3725                        };
3726
3727                    let catalog = self.catalog_mut();
3728                    catalog.set_optimized_plan(idx.global_id(), optimized_plan);
3729                    catalog.set_physical_plan(idx.global_id(), physical_plan);
3730                    catalog.set_dataflow_metainfo(idx.global_id(), metainfo);
3731
3732                    compute_instance.insert_collection(idx.global_id());
3733                }
3734                CatalogItem::MaterializedView(mv) => {
3735                    // Collect optimizer parameters.
3736                    let compute_instance =
3737                        instance_snapshots.entry(mv.cluster_id).or_insert_with(|| {
3738                            self.instance_snapshot(mv.cluster_id)
3739                                .expect("compute instance exists")
3740                        });
3741                    let global_id = mv.global_id_writes();
3742
3743                    let optimizer_config = optimizer_config(&self.catalog, mv.cluster_id);
3744
3745                    let (optimized_plan, physical_plan, metainfo) = match cached_global_exprs
3746                        .remove(&global_id)
3747                    {
3748                        Some(global_expressions)
3749                            if global_expressions.optimizer_features
3750                                == optimizer_config.features =>
3751                        {
3752                            debug!("global expression cache hit for {global_id:?}");
3753                            (
3754                                global_expressions.global_mir,
3755                                global_expressions.physical_plan,
3756                                global_expressions.dataflow_metainfos,
3757                            )
3758                        }
3759                        Some(_) | None => {
3760                            let (_, internal_view_id) = self.allocate_transient_id();
3761                            let debug_name = self
3762                                .catalog()
3763                                .resolve_full_name(entry.name(), None)
3764                                .to_string();
3765
3766                            let (optimized_plan, global_lir_plan) = {
3767                                // Build an optimizer for this MATERIALIZED VIEW.
3768                                let mut optimizer = optimize::materialized_view::Optimizer::new(
3769                                    self.owned_catalog().as_optimizer_catalog(),
3770                                    compute_instance.clone(),
3771                                    global_id,
3772                                    internal_view_id,
3773                                    mv.desc.latest().iter_names().cloned().collect(),
3774                                    mv.non_null_assertions.clone(),
3775                                    mv.refresh_schedule.clone(),
3776                                    debug_name,
3777                                    optimizer_config.clone(),
3778                                    self.optimizer_metrics(),
3779                                );
3780
3781                                // MIR ⇒ MIR optimization (global)
3782                                // We make sure to use the HIR SQL type (since MIR SQL types may not be coherent).
3783                                let typ = infer_sql_type_for_catalog(
3784                                    &mv.raw_expr,
3785                                    &mv.locally_optimized_expr.as_ref().clone(),
3786                                );
3787                                let global_mir_plan = optimizer
3788                                    .optimize((mv.locally_optimized_expr.as_ref().clone(), typ))?;
3789                                let optimized_plan = global_mir_plan.df_desc().clone();
3790
3791                                // MIR ⇒ LIR lowering and LIR ⇒ LIR optimization (global)
3792                                let global_lir_plan = optimizer.optimize(global_mir_plan)?;
3793
3794                                (optimized_plan, global_lir_plan)
3795                            };
3796
3797                            let (physical_plan, metainfo) = global_lir_plan.unapply();
3798                            let metainfo = {
3799                                // Pre-allocate a vector of transient GlobalIds for each notice.
3800                                let notice_ids =
3801                                    std::iter::repeat_with(|| self.allocate_transient_id())
3802                                        .map(|(_item_id, global_id)| global_id)
3803                                        .take(metainfo.optimizer_notices.len())
3804                                        .collect::<Vec<_>>();
3805                                // Return a metainfo with rendered notices.
3806                                self.catalog().render_notices(
3807                                    metainfo,
3808                                    notice_ids,
3809                                    Some(mv.global_id_writes()),
3810                                )
3811                            };
3812                            uncached_expressions.insert(
3813                                global_id,
3814                                GlobalExpressions {
3815                                    global_mir: optimized_plan.clone(),
3816                                    physical_plan: physical_plan.clone(),
3817                                    dataflow_metainfos: metainfo.clone(),
3818                                    optimizer_features: optimizer_config.features.clone(),
3819                                },
3820                            );
3821                            (optimized_plan, physical_plan, metainfo)
3822                        }
3823                    };
3824
3825                    let catalog = self.catalog_mut();
3826                    catalog.set_optimized_plan(mv.global_id_writes(), optimized_plan);
3827                    catalog.set_physical_plan(mv.global_id_writes(), physical_plan);
3828                    catalog.set_dataflow_metainfo(mv.global_id_writes(), metainfo);
3829
3830                    compute_instance.insert_collection(mv.global_id_writes());
3831                }
3832                CatalogItem::MetricSink(metric_sink) => {
3833                    // Collect optimizer parameters.
3834                    let compute_instance = instance_snapshots
3835                        .entry(metric_sink.cluster_id)
3836                        .or_insert_with(|| {
3837                            self.instance_snapshot(metric_sink.cluster_id)
3838                                .expect("compute instance exists")
3839                        });
3840                    let global_id = metric_sink.global_id;
3841                    let optimizer_config = optimizer_config(&self.catalog, metric_sink.cluster_id);
3842
3843                    let (optimized_plan, physical_plan, metainfo) = match cached_global_exprs
3844                        .remove(&global_id)
3845                    {
3846                        Some(global_expressions)
3847                            if global_expressions.optimizer_features
3848                                == optimizer_config.features =>
3849                        {
3850                            debug!("global expression cache hit for {global_id:?}");
3851                            (
3852                                global_expressions.global_mir,
3853                                global_expressions.physical_plan,
3854                                global_expressions.dataflow_metainfos,
3855                            )
3856                        }
3857                        Some(_) | None => {
3858                            // A transient id for the view the optimizer builds over `from` to
3859                            // shape its rows (see `optimize::metric_sink::shape_metric_sink_source`).
3860                            // The id only needs to be unique within this dataflow, so a cached plan
3861                            // reusing a transient id from a previous boot is safe: build ids are
3862                            // dataflow-local on the worker and never registered in the controller's
3863                            // instance-global collections (only export ids are).
3864                            let (_, view_id) = self.allocate_transient_id();
3865
3866                            let (optimized_plan, global_lir_plan) = {
3867                                let mut optimizer = optimize::metric_sink::Optimizer::new(
3868                                    self.owned_catalog(),
3869                                    compute_instance.clone(),
3870                                    view_id,
3871                                    global_id,
3872                                    optimizer_config.clone(),
3873                                    self.optimizer_metrics(),
3874                                );
3875
3876                                // MIR ⇒ MIR optimization (global)
3877                                let metric_sink_plan = optimize::metric_sink::MetricSink::new(
3878                                    entry.name().clone(),
3879                                    metric_sink.from,
3880                                    metric_sink.prefix.clone(),
3881                                );
3882                                let global_mir_plan = optimizer.optimize(metric_sink_plan)?;
3883                                let optimized_plan = global_mir_plan.df_desc().clone();
3884
3885                                // MIR ⇒ LIR lowering and LIR ⇒ LIR optimization (global)
3886                                let global_lir_plan = optimizer.optimize(global_mir_plan)?;
3887
3888                                (optimized_plan, global_lir_plan)
3889                            };
3890
3891                            let (physical_plan, metainfo) = global_lir_plan.unapply();
3892                            let metainfo = {
3893                                // Pre-allocate a vector of transient GlobalIds for each notice.
3894                                let notice_ids =
3895                                    std::iter::repeat_with(|| self.allocate_transient_id())
3896                                        .map(|(_item_id, gid)| gid)
3897                                        .take(metainfo.optimizer_notices.len())
3898                                        .collect::<Vec<_>>();
3899                                // Return a metainfo with rendered notices.
3900                                self.catalog()
3901                                    .render_notices(metainfo, notice_ids, Some(global_id))
3902                            };
3903                            uncached_expressions.insert(
3904                                global_id,
3905                                GlobalExpressions {
3906                                    global_mir: optimized_plan.clone(),
3907                                    physical_plan: physical_plan.clone(),
3908                                    dataflow_metainfos: metainfo.clone(),
3909                                    optimizer_features: optimizer_config.features.clone(),
3910                                },
3911                            );
3912                            (optimized_plan, physical_plan, metainfo)
3913                        }
3914                    };
3915
3916                    let catalog = self.catalog_mut();
3917                    catalog.set_optimized_plan(global_id, optimized_plan);
3918                    catalog.set_physical_plan(global_id, physical_plan);
3919                    catalog.set_dataflow_metainfo(global_id, metainfo);
3920
3921                    // NOTE: No `insert_collection` for the export. A metric sink writes to the
3922                    // metrics registry rather than to a readable collection, so no later dataflow
3923                    // can import it.
3924                }
3925                CatalogItem::Table(_)
3926                | CatalogItem::Source(_)
3927                | CatalogItem::Log(_)
3928                | CatalogItem::View(_)
3929                | CatalogItem::Sink(_)
3930                | CatalogItem::Type(_)
3931                | CatalogItem::Func(_)
3932                | CatalogItem::Secret(_)
3933                | CatalogItem::Connection(_) => (),
3934            }
3935        }
3936
3937        Ok(uncached_expressions)
3938    }
3939
3940    /// Selects for each compute dataflow an as-of suitable for bootstrapping it.
3941    ///
3942    /// Returns a set of [`ReadHold`]s that ensures the read frontiers of involved collections stay
3943    /// in place and that must not be dropped before all compute dataflows have been created with
3944    /// the compute controller.
3945    ///
3946    /// This method expects all storage collections and dataflow plans to be available, so it must
3947    /// run after [`Coordinator::bootstrap_storage_collections`] and
3948    /// [`Coordinator::bootstrap_dataflow_plans`].
3949    async fn bootstrap_dataflow_as_ofs(&mut self) -> BTreeMap<GlobalId, ReadHold> {
3950        let mut catalog_ids = Vec::new();
3951        let mut dataflows = Vec::new();
3952        let mut read_policies = BTreeMap::new();
3953        for entry in self.catalog.entries() {
3954            let gid = match entry.item() {
3955                CatalogItem::Index(idx) => idx.global_id(),
3956                CatalogItem::MaterializedView(mv) => mv.global_id_writes(),
3957                CatalogItem::MetricSink(metric_sink) => metric_sink.global_id,
3958                CatalogItem::Table(_)
3959                | CatalogItem::Source(_)
3960                | CatalogItem::Log(_)
3961                | CatalogItem::View(_)
3962                | CatalogItem::Sink(_)
3963                | CatalogItem::Type(_)
3964                | CatalogItem::Func(_)
3965                | CatalogItem::Secret(_)
3966                | CatalogItem::Connection(_) => continue,
3967            };
3968            if let Some(plan) = self.catalog.try_get_physical_plan(&gid) {
3969                catalog_ids.push(gid);
3970                dataflows.push(plan.clone());
3971
3972                if let Some(compaction_window) = entry.item().initial_logical_compaction_window() {
3973                    read_policies.insert(gid, compaction_window.into());
3974                }
3975            }
3976        }
3977
3978        let read_ts = self.get_local_read_ts().await;
3979        let read_holds = as_of_selection::run(
3980            &mut dataflows,
3981            &read_policies,
3982            &*self.controller.storage_collections,
3983            read_ts,
3984            self.controller.read_only(),
3985        );
3986
3987        let catalog = self.catalog_mut();
3988        for (id, plan) in catalog_ids.into_iter().zip_eq(dataflows) {
3989            catalog.set_physical_plan(id, plan);
3990        }
3991
3992        read_holds
3993    }
3994
3995    /// Serves the coordinator, receiving commands from users over `cmd_rx`
3996    /// and feedback from dataflow workers over `feedback_rx`.
3997    ///
3998    /// You must call `bootstrap` before calling this method.
3999    ///
4000    /// BOXED FUTURE: As of Nov 2023 the returned Future from this function was 92KB. This would
4001    /// get stored on the stack which is bad for runtime performance, and blow up our stack usage.
4002    /// Because of that we purposefully move this Future onto the heap (i.e. Box it).
4003    fn serve(
4004        mut self,
4005        mut internal_cmd_rx: mpsc::UnboundedReceiver<Message>,
4006        mut strict_serializable_reads_rx: mpsc::UnboundedReceiver<(ConnectionId, PendingReadTxn)>,
4007        mut cmd_rx: mpsc::UnboundedReceiver<(OpenTelemetryContext, Command)>,
4008        group_commit_rx: appends::GroupCommitWaiter,
4009    ) -> LocalBoxFuture<'static, ()> {
4010        async move {
4011            // Watcher that listens for and reports cluster service status changes.
4012            let mut cluster_events = self.controller.events_stream();
4013            let last_message = Arc::new(Mutex::new(LastMessage {
4014                kind: "none",
4015                stmt: None,
4016            }));
4017
4018            let (idle_tx, mut idle_rx) = tokio::sync::mpsc::channel(1);
4019            let idle_metric = self.metrics.queue_busy_seconds.clone();
4020            let last_message_watchdog = Arc::clone(&last_message);
4021
4022            spawn(|| "coord watchdog", async move {
4023                // Every 5 seconds, attempt to measure how long it takes for the
4024                // coord select loop to be empty, because this message is the last
4025                // processed. If it is idle, this will result in some microseconds
4026                // of measurement.
4027                let mut interval = tokio::time::interval(Duration::from_secs(5));
4028                // If we end up having to wait more than 5 seconds for the coord to respond, then the
4029                // behavior of Delay results in the interval "restarting" from whenever we yield
4030                // instead of trying to catch up.
4031                interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
4032
4033                // Track if we become stuck to de-dupe error reporting.
4034                let mut coord_stuck = false;
4035
4036                loop {
4037                    interval.tick().await;
4038
4039                    // Wait for space in the channel, if we timeout then the coordinator is stuck!
4040                    let duration = tokio::time::Duration::from_secs(30);
4041                    let timeout = tokio::time::timeout(duration, idle_tx.reserve()).await;
4042                    let Ok(maybe_permit) = timeout else {
4043                        // Only log if we're newly stuck, to prevent logging repeatedly.
4044                        if !coord_stuck {
4045                            let last_message = last_message_watchdog.lock().expect("poisoned");
4046                            tracing::warn!(
4047                                last_message_kind = %last_message.kind,
4048                                last_message_sql = %last_message.stmt_to_string(),
4049                                "coordinator stuck for {duration:?}",
4050                            );
4051                        }
4052                        coord_stuck = true;
4053
4054                        continue;
4055                    };
4056
4057                    // We got a permit, we're not stuck!
4058                    if coord_stuck {
4059                        tracing::info!("Coordinator became unstuck");
4060                    }
4061                    coord_stuck = false;
4062
4063                    // If we failed to acquire a permit it's because we're shutting down.
4064                    let Ok(permit) = maybe_permit else {
4065                        break;
4066                    };
4067
4068                    permit.send(idle_metric.start_timer());
4069                }
4070            });
4071
4072            self.schedule_storage_usage_collection().await;
4073            self.schedule_arrangement_sizes_collection().await;
4074            self.spawn_privatelink_vpc_endpoints_watch_task();
4075            self.spawn_statement_logging_task();
4076            self.spawn_catalog_info_metrics_task();
4077            self.spawn_cluster_controller_task();
4078            flags::tracing_config(self.catalog.system_config()).apply(&self.tracing_handle);
4079
4080            // Report if the handling of a single message takes longer than this threshold.
4081            let warn_threshold = self
4082                .catalog()
4083                .system_config()
4084                .coord_slow_message_warn_threshold();
4085
4086            // How many messages we'd like to batch up before processing them. Must be > 0.
4087            const MESSAGE_BATCH: usize = 64;
4088            let mut messages = Vec::with_capacity(MESSAGE_BATCH);
4089            let mut cmd_messages = Vec::with_capacity(MESSAGE_BATCH);
4090
4091            let message_batch = self.metrics.message_batch.clone();
4092
4093            // A persisted `Notified` future for the linearize re-check signal.
4094            // It must outlive a single loop iteration and be re-`set` only after
4095            // it completes: a fresh `notified()` per iteration could drop a
4096            // wakeup that arrives while a higher-priority branch wins the same
4097            // poll, stranding pending reads. Keeping it pinned across iterations
4098            // leaves it registered, so no wakeup is lost.
4099            let linearize_reads_notify = Arc::clone(&self.linearize_reads_notify);
4100            let linearize_reads_notified = linearize_reads_notify.notified();
4101            tokio::pin!(linearize_reads_notified);
4102
4103            loop {
4104                // Before adding a branch to this select loop, please ensure that the branch is
4105                // cancellation safe and add a comment explaining why. You can refer here for more
4106                // info: https://docs.rs/tokio/latest/tokio/macro.select.html#cancellation-safety
4107                select! {
4108                    // We prioritize internal commands over other commands. However, we work through
4109                    // batches of commands in some branches of this select, which means that even if
4110                    // a command generates internal commands, we will work through the current batch
4111                    // before receiving a new batch of commands.
4112                    biased;
4113
4114                    // `recv_many()` on `UnboundedReceiver` is cancellation safe:
4115                    // https://docs.rs/tokio/1.38.0/tokio/sync/mpsc/struct.UnboundedReceiver.html#cancel-safety-1
4116                    // Receive a batch of commands.
4117                    _ = internal_cmd_rx.recv_many(&mut messages, MESSAGE_BATCH) => {},
4118                    // `next()` on any stream is cancel-safe:
4119                    // https://docs.rs/tokio-stream/0.1.9/tokio_stream/trait.StreamExt.html#cancel-safety
4120                    // Receive a single command.
4121                    Some(event) = cluster_events.next() => {
4122                        messages.push(Message::ClusterEvent(event))
4123                    },
4124                    // See [`mz_controller::Controller::Controller::ready`] for notes
4125                    // on why this is cancel-safe.
4126                    // Receive a single command.
4127                    () = self.controller.ready() => {
4128                        // NOTE: We don't get a `Readiness` back from `ready()`
4129                        // because the controller wants to keep it and it's not
4130                        // trivially `Clone` or `Copy`. Hence this accessor.
4131                        let controller = match self.controller.get_readiness() {
4132                            Readiness::Storage => ControllerReadiness::Storage,
4133                            Readiness::Compute => ControllerReadiness::Compute,
4134                            Readiness::Metrics(_) => ControllerReadiness::Metrics,
4135                            Readiness::Internal(_) => ControllerReadiness::Internal,
4136                            Readiness::NotReady => unreachable!("just signaled as ready"),
4137                        };
4138                        messages.push(Message::ControllerReady { controller });
4139                    }
4140                    // See [`appends::GroupCommitWaiter`] for notes on why this is cancel safe.
4141                    // Receive a single command.
4142                    permit = group_commit_rx.ready() => {
4143                        // If we happen to have batched exactly one user write, use
4144                        // that span so the `emit_trace_id_notice` hooks up.
4145                        // Otherwise, the best we can do is invent a new root span
4146                        // and make it follow from all the Spans in the pending
4147                        // writes.
4148                        let user_write_spans = self.pending_writes.iter().flat_map(|x| match x {
4149                            PendingWriteTxn::User { span, .. } => Some(span),
4150                            PendingWriteTxn::System { .. } => None,
4151                        });
4152                        let span = match user_write_spans.exactly_one() {
4153                            Ok(span) => span.clone(),
4154                            Err(user_write_spans) => {
4155                                let span = info_span!(parent: None, "group_commit_notify");
4156                                for s in user_write_spans {
4157                                    span.follows_from(s);
4158                                }
4159                                span
4160                            }
4161                        };
4162                        messages.push(Message::GroupCommitInitiate(span, Some(permit)));
4163                    },
4164                    // `recv_many()` on `UnboundedReceiver` is cancellation safe:
4165                    // https://docs.rs/tokio/1.38.0/tokio/sync/mpsc/struct.UnboundedReceiver.html#cancel-safety-1
4166                    // Receive a batch of commands.
4167                    count = cmd_rx.recv_many(&mut cmd_messages, MESSAGE_BATCH) => {
4168                        if count == 0 {
4169                            break;
4170                        } else {
4171                            messages.extend(cmd_messages.drain(..).map(
4172                                |(otel_ctx, cmd)| Message::Command(otel_ctx, cmd),
4173                            ));
4174                        }
4175                    },
4176                    // `recv()` on `UnboundedReceiver` is cancellation safe:
4177                    // https://docs.rs/tokio/1.38.0/tokio/sync/mpsc/struct.UnboundedReceiver.html#cancel-safety
4178                    // Receive a single command.
4179                    Some(pending_read_txn) = strict_serializable_reads_rx.recv() => {
4180                        let mut pending_read_txns = vec![pending_read_txn];
4181                        while let Ok(pending_read_txn) = strict_serializable_reads_rx.try_recv() {
4182                            pending_read_txns.push(pending_read_txn);
4183                        }
4184                        for (conn_id, pending_read_txn) in pending_read_txns {
4185                            let prev = self
4186                                .pending_linearize_read_txns
4187                                .insert(conn_id, pending_read_txn);
4188                            soft_assert_or_log!(
4189                                prev.is_none(),
4190                                "connections can not have multiple concurrent reads, prev: {prev:?}"
4191                            )
4192                        }
4193                        messages.push(Message::LinearizeReads);
4194                    }
4195                    // `tick()` on `Interval` is cancel-safe:
4196                    // https://docs.rs/tokio/1.19.2/tokio/time/struct.Interval.html#cancel-safety
4197                    // Receive a single command.
4198                    _ = self.advance_timelines_interval.tick() => {
4199                        // Writable keepalives use the committer to advance tables and read holds.
4200                        // Its permit coalesces ticks behind a slow oracle. Read-only mode advances
4201                        // timelines directly.
4202                        if self.controller.read_only() {
4203                            messages.push(Message::AdvanceTimelines);
4204                        } else {
4205                            self.group_commit_tx.notify();
4206                        }
4207                    },
4208                    // Re-check pending strict serializable reads. Deliberately
4209                    // placed below the group commit branches above: a re-check
4210                    // only makes a read ready if the timestamp oracle has
4211                    // advanced, and the oracle only advances via group commit, so
4212                    // this must never win over (and thereby starve) group commit.
4213                    // `Notify` coalesces re-arms into a single wakeup, so even
4214                    // when a pending read sits just behind the oracle (re-armed
4215                    // sub-millisecond), the lower branches (including the idle
4216                    // watchdog) stay reachable. See the pin above for why the
4217                    // future is persisted rather than recreated per iteration.
4218                    () = linearize_reads_notified.as_mut() => {
4219                        linearize_reads_notified.set(linearize_reads_notify.notified());
4220                        messages.push(Message::LinearizeReads);
4221                    }
4222                    // `tick()` on `Interval` is cancel-safe:
4223                    // https://docs.rs/tokio/1.19.2/tokio/time/struct.Interval.html#cancel-safety
4224                    // Receive a single command.
4225                    _ = self.caught_up_check_interval.tick() => {
4226                        // We do this directly on the main loop instead of
4227                        // firing off a message. We are still in read-only mode,
4228                        // so optimizing for latency, not blocking the main loop
4229                        // is not that important.
4230                        self.maybe_check_caught_up().await;
4231
4232                        continue;
4233                    },
4234
4235                    // Process the idle metric at the lowest priority to sample queue non-idle time.
4236                    // `recv()` on `Receiver` is cancellation safe:
4237                    // https://docs.rs/tokio/1.8.0/tokio/sync/mpsc/struct.Receiver.html#cancel-safety
4238                    // Receive a single command.
4239                    timer = idle_rx.recv() => {
4240                        timer.expect("does not drop").observe_duration();
4241                        self.metrics
4242                            .message_handling
4243                            .with_label_values(&["watchdog"])
4244                            .observe(0.0);
4245                        continue;
4246                    }
4247                };
4248
4249                // Observe the number of messages we're processing at once.
4250                message_batch.observe(f64::cast_lossy(messages.len()));
4251
4252                for msg in messages.drain(..) {
4253                    // All message processing functions trace. Start a parent span
4254                    // for them to make it easy to find slow messages.
4255                    let msg_kind = msg.kind();
4256                    let span = span!(
4257                        target: "mz_adapter::coord::handle_message_loop",
4258                        Level::INFO,
4259                        "coord::handle_message",
4260                        kind = msg_kind
4261                    );
4262                    let otel_context = span.context().span().span_context().clone();
4263
4264                    // Record the last kind of message in case we get stuck. For
4265                    // execute commands, we additionally stash the user's SQL,
4266                    // statement, so we can log it in case we get stuck.
4267                    *last_message.lock().expect("poisoned") = LastMessage {
4268                        kind: msg_kind,
4269                        stmt: match &msg {
4270                            Message::Command(
4271                                _,
4272                                Command::Execute {
4273                                    portal_name,
4274                                    session,
4275                                    ..
4276                                },
4277                            ) => session
4278                                .get_portal_unverified(portal_name)
4279                                .and_then(|p| p.stmt.as_ref().map(Arc::clone)),
4280                            _ => None,
4281                        },
4282                    };
4283
4284                    let start = Instant::now();
4285                    self.handle_message(msg).instrument(span).await;
4286                    let duration = start.elapsed();
4287
4288                    self.metrics
4289                        .message_handling
4290                        .with_label_values(&[msg_kind])
4291                        .observe(duration.as_secs_f64());
4292
4293                    // If something is _really_ slow, print a trace id for debugging, if OTEL is enabled.
4294                    if duration > warn_threshold {
4295                        let trace_id = otel_context.is_valid().then(|| otel_context.trace_id());
4296                        tracing::error!(
4297                            ?msg_kind,
4298                            ?trace_id,
4299                            ?duration,
4300                            "very slow coordinator message"
4301                        );
4302                    }
4303                }
4304            }
4305            // Try and cleanup as a best effort. There may be some async tasks out there holding a
4306            // reference that prevents us from cleaning up.
4307            if let Some(catalog) = Arc::into_inner(self.catalog) {
4308                catalog.expire().await;
4309            }
4310        }
4311        .boxed_local()
4312    }
4313
4314    /// Obtain a read-only Catalog reference.
4315    fn catalog(&self) -> &Catalog {
4316        &self.catalog
4317    }
4318
4319    /// Obtain a read-only Catalog snapshot, suitable for giving out to
4320    /// non-Coordinator thread tasks.
4321    fn owned_catalog(&self) -> Arc<Catalog> {
4322        Arc::clone(&self.catalog)
4323    }
4324
4325    /// Obtain a handle to the optimizer metrics, suitable for giving
4326    /// out to non-Coordinator thread tasks.
4327    fn optimizer_metrics(&self) -> OptimizerMetrics {
4328        self.optimizer_metrics.clone()
4329    }
4330
4331    /// Obtain a writeable Catalog reference.
4332    fn catalog_mut(&mut self) -> &mut Catalog {
4333        // make_mut will cause any other Arc references (from owned_catalog) to
4334        // continue to be valid by cloning the catalog, putting it in a new Arc,
4335        // which lives at self._catalog. If there are no other Arc references,
4336        // then no clone is made, and it returns a reference to the existing
4337        // object. This makes this method and owned_catalog both very cheap: at
4338        // most one clone per catalog mutation, but only if there's a read-only
4339        // reference to it.
4340        Arc::make_mut(&mut self.catalog)
4341    }
4342
4343    /// Refills the user ID pool by allocating IDs from the catalog.
4344    ///
4345    /// Requests `max(min_count, batch_size)` IDs so the pool is never
4346    /// under-filled relative to the configured batch size.
4347    async fn refill_user_id_pool(&mut self, min_count: u64) -> Result<(), AdapterError> {
4348        let batch_size = USER_ID_POOL_BATCH_SIZE.get(self.catalog().system_config().dyncfgs());
4349        let to_allocate = min_count.max(u64::from(batch_size));
4350        let id_ts = self.get_catalog_write_ts().await;
4351        let ids = self.catalog().allocate_user_ids(to_allocate, id_ts).await?;
4352        if let (Some((first_id, _)), Some((last_id, _))) = (ids.first(), ids.last()) {
4353            let start = match first_id {
4354                CatalogItemId::User(id) => *id,
4355                other => {
4356                    return Err(AdapterError::Internal(format!(
4357                        "expected User CatalogItemId, got {other:?}"
4358                    )));
4359                }
4360            };
4361            let end = match last_id {
4362                CatalogItemId::User(id) => *id + 1, // exclusive upper bound
4363                other => {
4364                    return Err(AdapterError::Internal(format!(
4365                        "expected User CatalogItemId, got {other:?}"
4366                    )));
4367                }
4368            };
4369            self.user_id_pool.refill(start, end);
4370        } else {
4371            return Err(AdapterError::Internal(
4372                "catalog returned no user IDs".into(),
4373            ));
4374        }
4375        Ok(())
4376    }
4377
4378    /// Allocates a single user ID, refilling the pool from the catalog if needed.
4379    async fn allocate_user_id(&mut self) -> Result<(CatalogItemId, GlobalId), AdapterError> {
4380        if let Some(id) = self.user_id_pool.allocate() {
4381            return Ok((CatalogItemId::User(id), GlobalId::User(id)));
4382        }
4383        self.refill_user_id_pool(1).await?;
4384        let id = self.user_id_pool.allocate().expect("ID pool just refilled");
4385        Ok((CatalogItemId::User(id), GlobalId::User(id)))
4386    }
4387
4388    /// Allocates `count` user IDs, refilling the pool from the catalog if needed.
4389    async fn allocate_user_ids(
4390        &mut self,
4391        count: u64,
4392    ) -> Result<Vec<(CatalogItemId, GlobalId)>, AdapterError> {
4393        if self.user_id_pool.remaining() < count {
4394            self.refill_user_id_pool(count).await?;
4395        }
4396        let raw_ids = self
4397            .user_id_pool
4398            .allocate_many(count)
4399            .expect("pool has enough IDs after refill");
4400        Ok(raw_ids
4401            .into_iter()
4402            .map(|id| (CatalogItemId::User(id), GlobalId::User(id)))
4403            .collect())
4404    }
4405
4406    /// Obtain a reference to the coordinator's connection context.
4407    fn connection_context(&self) -> &ConnectionContext {
4408        self.controller.connection_context()
4409    }
4410
4411    /// Obtain a reference to the coordinator's secret reader, in an `Arc`.
4412    fn secrets_reader(&self) -> &Arc<dyn SecretsReader> {
4413        &self.connection_context().secrets_reader
4414    }
4415
4416    /// Publishes a notice message to all sessions.
4417    ///
4418    /// TODO(parkmycar): This code is dead, but is a nice parallel to [`Coordinator::broadcast_notice_tx`]
4419    /// so we keep it around.
4420    #[allow(dead_code)]
4421    pub(crate) fn broadcast_notice(&self, notice: AdapterNotice) {
4422        for meta in self.active_conns.values() {
4423            let _ = meta.notice_tx.send(notice.clone());
4424        }
4425    }
4426
4427    /// Returns a closure that will publish a notice to all sessions that were active at the time
4428    /// this method was called.
4429    pub(crate) fn broadcast_notice_tx(
4430        &self,
4431    ) -> Box<dyn FnOnce(AdapterNotice) -> () + Send + 'static> {
4432        let senders: Vec<_> = self
4433            .active_conns
4434            .values()
4435            .map(|meta| meta.notice_tx.clone())
4436            .collect();
4437        Box::new(move |notice| {
4438            for tx in senders {
4439                let _ = tx.send(notice.clone());
4440            }
4441        })
4442    }
4443
4444    pub(crate) fn active_conns(&self) -> &BTreeMap<ConnectionId, ConnMeta> {
4445        &self.active_conns
4446    }
4447
4448    #[instrument(level = "debug")]
4449    pub(crate) fn retire_execution(
4450        &mut self,
4451        reason: StatementEndedExecutionReason,
4452        ctx_extra: ExecuteContextExtra,
4453    ) {
4454        if let Some(uuid) = ctx_extra.retire() {
4455            let ended_at = self.now();
4456            self.end_statement_execution(uuid, reason, ended_at);
4457        }
4458    }
4459
4460    /// Creates a new dataflow builder from the catalog and indexes in `self`.
4461    #[instrument(level = "debug")]
4462    pub fn dataflow_builder(&self, instance: ComputeInstanceId) -> DataflowBuilder<'_> {
4463        let compute = self
4464            .instance_snapshot(instance)
4465            .expect("compute instance does not exist");
4466        DataflowBuilder::new(self.catalog().state(), compute)
4467    }
4468
4469    /// Return a reference-less snapshot to the indicated compute instance.
4470    pub fn instance_snapshot(
4471        &self,
4472        id: ComputeInstanceId,
4473    ) -> Result<ComputeInstanceSnapshot, InstanceMissing> {
4474        ComputeInstanceSnapshot::new(&self.controller, id)
4475    }
4476
4477    /// Call into the compute controller to install a finalized dataflow, and
4478    /// initialize the read policies for its exported readable objects.
4479    ///
4480    /// # Panics
4481    ///
4482    /// Panics if dataflow creation fails.
4483    pub(crate) async fn ship_dataflow(
4484        &mut self,
4485        dataflow: DataflowDescription<LirRelationExpr>,
4486        instance: ComputeInstanceId,
4487        target_replica: Option<ReplicaId>,
4488    ) {
4489        self.try_ship_dataflow(dataflow, instance, target_replica)
4490            .await
4491            .unwrap_or_terminate("dataflow creation cannot fail");
4492    }
4493
4494    /// Call into the compute controller to install a finalized dataflow, and
4495    /// initialize the read policies for its exported readable objects.
4496    pub(crate) async fn try_ship_dataflow(
4497        &mut self,
4498        dataflow: DataflowDescription<LirRelationExpr>,
4499        instance: ComputeInstanceId,
4500        target_replica: Option<ReplicaId>,
4501    ) -> Result<(), DataflowCreationError> {
4502        // We must only install read policies for indexes, not for sinks.
4503        // Sinks are write-only compute collections that don't have read policies.
4504        let export_ids = dataflow.exported_index_ids().collect();
4505
4506        self.controller
4507            .compute
4508            .create_dataflow(instance, dataflow, target_replica)?;
4509
4510        self.initialize_compute_read_policies(export_ids, instance, CompactionWindow::Default)
4511            .await;
4512
4513        Ok(())
4514    }
4515
4516    /// Call into the compute controller to allow writes to the specified IDs
4517    /// from the specified instance. Calling this function multiple times and
4518    /// calling it on a read-only instance has no effect.
4519    pub(crate) fn allow_writes(&mut self, instance: ComputeInstanceId, id: GlobalId) {
4520        self.controller
4521            .compute
4522            .allow_writes(instance, id)
4523            .unwrap_or_terminate("allow_writes cannot fail");
4524    }
4525
4526    /// Like `ship_dataflow`, but also await on builtin table updates.
4527    pub(crate) async fn ship_dataflow_and_notice_builtin_table_updates(
4528        &mut self,
4529        dataflow: DataflowDescription<LirRelationExpr>,
4530        instance: ComputeInstanceId,
4531        notice_builtin_updates_fut: Option<BuiltinTableAppendNotify>,
4532        target_replica: Option<ReplicaId>,
4533    ) {
4534        if let Some(notice_builtin_updates_fut) = notice_builtin_updates_fut {
4535            let ship_dataflow_fut = self.ship_dataflow(dataflow, instance, target_replica);
4536            let ((), ()) =
4537                futures::future::join(notice_builtin_updates_fut, ship_dataflow_fut).await;
4538        } else {
4539            self.ship_dataflow(dataflow, instance, target_replica).await;
4540        }
4541    }
4542
4543    /// Install a _watch set_ in the controller that is automatically associated with the given
4544    /// connection id. The watchset will be automatically cleared if the connection terminates
4545    /// before the watchset completes.
4546    pub fn install_compute_watch_set(
4547        &mut self,
4548        conn_id: ConnectionId,
4549        objects: BTreeSet<GlobalId>,
4550        t: Timestamp,
4551        state: WatchSetResponse,
4552    ) -> Result<(), CollectionLookupError> {
4553        let ws_id = self.controller.install_compute_watch_set(objects, t)?;
4554        self.connection_watch_sets
4555            .entry(conn_id.clone())
4556            .or_default()
4557            .insert(ws_id);
4558        self.installed_watch_sets.insert(ws_id, (conn_id, state));
4559        Ok(())
4560    }
4561
4562    /// Install a _watch set_ in the controller that is automatically associated with the given
4563    /// connection id. The watchset will be automatically cleared if the connection terminates
4564    /// before the watchset completes.
4565    pub fn install_storage_watch_set(
4566        &mut self,
4567        conn_id: ConnectionId,
4568        objects: BTreeSet<GlobalId>,
4569        t: Timestamp,
4570        state: WatchSetResponse,
4571    ) -> Result<(), CollectionMissing> {
4572        let ws_id = self.controller.install_storage_watch_set(objects, t)?;
4573        self.connection_watch_sets
4574            .entry(conn_id.clone())
4575            .or_default()
4576            .insert(ws_id);
4577        self.installed_watch_sets.insert(ws_id, (conn_id, state));
4578        Ok(())
4579    }
4580
4581    /// Cancels pending watchsets associated with the provided connection id.
4582    pub fn cancel_pending_watchsets(&mut self, conn_id: &ConnectionId) {
4583        if let Some(ws_ids) = self.connection_watch_sets.remove(conn_id) {
4584            for ws_id in ws_ids {
4585                self.installed_watch_sets.remove(&ws_id);
4586            }
4587        }
4588    }
4589
4590    /// Returns the state of the [`Coordinator`] formatted as JSON.
4591    ///
4592    /// The returned value is not guaranteed to be stable and may change at any point in time.
4593    pub async fn dump(&self) -> Result<serde_json::Value, anyhow::Error> {
4594        // Note: We purposefully use the `Debug` formatting for the value of all fields in the
4595        // returned object as a tradeoff between usability and stability. `serde_json` will fail
4596        // to serialize an object if the keys aren't strings, so `Debug` formatting the values
4597        // prevents a future unrelated change from silently breaking this method.
4598
4599        let global_timelines: BTreeMap<_, _> = self
4600            .global_timelines
4601            .iter()
4602            .map(|(timeline, state)| (timeline.to_string(), format!("{state:?}")))
4603            .collect();
4604        let active_conns: BTreeMap<_, _> = self
4605            .active_conns
4606            .iter()
4607            .map(|(id, meta)| (id.unhandled().to_string(), format!("{meta:?}")))
4608            .collect();
4609        let txn_read_holds: BTreeMap<_, _> = self
4610            .txn_read_holds
4611            .iter()
4612            .map(|(id, capability)| (id.unhandled().to_string(), format!("{capability:?}")))
4613            .collect();
4614        let pending_peeks: BTreeMap<_, _> = self
4615            .pending_peeks
4616            .iter()
4617            .map(|(id, peek)| (id.to_string(), format!("{peek:?}")))
4618            .collect();
4619        let client_pending_peeks: BTreeMap<_, _> = self
4620            .client_pending_peeks
4621            .iter()
4622            .map(|(id, peek)| {
4623                let peek: BTreeMap<_, _> = peek
4624                    .iter()
4625                    .map(|(uuid, storage_id)| (uuid.to_string(), storage_id))
4626                    .collect();
4627                (id.to_string(), peek)
4628            })
4629            .collect();
4630        let pending_linearize_read_txns: BTreeMap<_, _> = self
4631            .pending_linearize_read_txns
4632            .iter()
4633            .map(|(id, read_txn)| (id.unhandled().to_string(), format!("{read_txn:?}")))
4634            .collect();
4635
4636        Ok(serde_json::json!({
4637            "global_timelines": global_timelines,
4638            "active_conns": active_conns,
4639            "txn_read_holds": txn_read_holds,
4640            "pending_peeks": pending_peeks,
4641            "client_pending_peeks": client_pending_peeks,
4642            "pending_linearize_read_txns": pending_linearize_read_txns,
4643            "controller": self.controller.dump().await?,
4644        }))
4645    }
4646
4647    /// Prune all storage usage events from the [`MZ_STORAGE_USAGE_BY_SHARD`] table that are older
4648    /// than `retention_period`.
4649    ///
4650    /// This method will read the entire contents of [`MZ_STORAGE_USAGE_BY_SHARD`] into memory
4651    /// which can be expensive.
4652    ///
4653    /// DO NOT call this method outside of startup. The safety of reading at the current oracle read
4654    /// timestamp and then writing at whatever the current write timestamp is (instead of
4655    /// `read_ts + 1`) relies on the fact that there are no outstanding writes during startup.
4656    ///
4657    /// Group commit, which this method uses to write the retractions, has builtin fencing, and we
4658    /// never commit retractions to [`MZ_STORAGE_USAGE_BY_SHARD`] outside of this method, which is
4659    /// only called once during startup. So we don't have to worry about double/invalid retractions.
4660    async fn prune_storage_usage_events_on_startup(&self, retention_period: Duration) {
4661        let item_id = self
4662            .catalog()
4663            .resolve_builtin_table(&MZ_STORAGE_USAGE_BY_SHARD);
4664        let global_id = self.catalog.get_entry(&item_id).latest_global_id();
4665        let read_ts = self.get_local_read_ts().await;
4666        let current_contents_fut = self
4667            .controller
4668            .storage_collections
4669            .snapshot(global_id, read_ts);
4670        let internal_cmd_tx = self.internal_cmd_tx.clone();
4671        spawn(|| "storage_usage_prune", async move {
4672            let mut current_contents = current_contents_fut
4673                .await
4674                .unwrap_or_terminate("cannot fail to fetch snapshot");
4675            differential_dataflow::consolidation::consolidate(&mut current_contents);
4676
4677            let cutoff_ts = u128::from(read_ts).saturating_sub(retention_period.as_millis());
4678            let mut expired = Vec::new();
4679            for (row, diff) in current_contents {
4680                assert_eq!(
4681                    diff, 1,
4682                    "consolidated contents should not contain retractions: ({row:#?}, {diff:#?})"
4683                );
4684                // This logic relies on the definition of `mz_storage_usage_by_shard` not changing.
4685                let collection_timestamp = row
4686                    .unpack()
4687                    .get(3)
4688                    .expect("definition of mz_storage_by_shard changed")
4689                    .unwrap_timestamptz();
4690                let collection_timestamp = collection_timestamp.timestamp_millis();
4691                let collection_timestamp: u128 = collection_timestamp
4692                    .try_into()
4693                    .expect("all collections happen after Jan 1 1970");
4694                if collection_timestamp < cutoff_ts {
4695                    debug!("pruning storage event {row:?}");
4696                    let builtin_update = BuiltinTableUpdate::row(item_id, row, Diff::MINUS_ONE);
4697                    expired.push(builtin_update);
4698                }
4699            }
4700
4701            // main thread has shut down.
4702            let _ = internal_cmd_tx.send(Message::StorageUsagePrune(expired));
4703        });
4704    }
4705
4706    /// Retracts `mz_object_arrangement_size_history` rows older than the
4707    /// `arrangement_size_history_retention_period` dyncfg.
4708    ///
4709    /// Must only run at startup: it reads at the oracle read timestamp and
4710    /// writes retractions at the current write timestamp, which is only safe
4711    /// when no other writes are in flight. See [the equivalent storage-usage
4712    /// pruner](Self::prune_storage_usage_events_on_startup) for the same
4713    /// reasoning.
4714    async fn prune_arrangement_sizes_history_on_startup(&self) {
4715        // The catalog server is not writable in read-only mode.
4716        if self.controller.read_only() {
4717            return;
4718        }
4719
4720        let retention_period = mz_adapter_types::dyncfgs::ARRANGEMENT_SIZE_HISTORY_RETENTION_PERIOD
4721            .get(self.catalog().system_config().dyncfgs());
4722        let item_id = self
4723            .catalog()
4724            .resolve_builtin_table(&mz_catalog::builtin::MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY);
4725        let global_id = self.catalog.get_entry(&item_id).latest_global_id();
4726        let read_ts = self.get_local_read_ts().await;
4727        let current_contents_fut = self
4728            .controller
4729            .storage_collections
4730            .snapshot(global_id, read_ts);
4731        let internal_cmd_tx = self.internal_cmd_tx.clone();
4732        spawn(|| "arrangement_sizes_history_prune", async move {
4733            let mut current_contents = current_contents_fut
4734                .await
4735                .unwrap_or_terminate("cannot fail to fetch snapshot");
4736            differential_dataflow::consolidation::consolidate(&mut current_contents);
4737
4738            let cutoff_ts = u128::from(read_ts).saturating_sub(retention_period.as_millis());
4739            let expired =
4740                arrangement_sizes_expired_retractions(current_contents, cutoff_ts, item_id);
4741
4742            // TODO(arrangement-sizes): when the writeable-catalog-server
4743            // plumbing in https://github.com/MaterializeInc/materialize/pull/35436
4744            // lands, retract directly on `mz_catalog_server`.
4745            let _ = internal_cmd_tx.send(Message::ArrangementSizesPrune(expired));
4746        });
4747    }
4748
4749    /// The environment's current credit consumption rate, summed over all user
4750    /// cluster replicas except those of `exclude_cluster`.
4751    fn current_credit_consumption_rate(&self, exclude_cluster: Option<ClusterId>) -> Numeric {
4752        self.catalog()
4753            .user_cluster_replicas()
4754            .filter(|replica| Some(replica.cluster_id) != exclude_cluster)
4755            .filter_map(|replica| match &replica.config.location {
4756                ReplicaLocation::Managed(location) => Some(location.size_for_billing()),
4757                ReplicaLocation::Unmanaged(_) => None,
4758            })
4759            .map(|size| {
4760                self.catalog()
4761                    .cluster_replica_sizes()
4762                    .0
4763                    .get(size)
4764                    .expect("location size is validated against the cluster replica sizes")
4765                    .credits_per_hour
4766            })
4767            .sum()
4768    }
4769}
4770
4771/// Returns retraction updates for rows in a consolidated
4772/// `mz_object_arrangement_size_history` snapshot whose `collection_timestamp`
4773/// (column 3) is strictly before `cutoff_ts`.
4774///
4775/// Panics if any input row has `diff != 1`: the caller must consolidate first,
4776/// and a consolidated history table should never contain retractions because
4777/// the only source of retractions is this function itself.
4778fn arrangement_sizes_expired_retractions(
4779    rows: impl IntoIterator<Item = (mz_repr::Row, i64)>,
4780    cutoff_ts: u128,
4781    item_id: CatalogItemId,
4782) -> Vec<BuiltinTableUpdate> {
4783    let mut expired = Vec::new();
4784    for (row, diff) in rows {
4785        assert_eq!(
4786            diff, 1,
4787            "consolidated contents should not contain retractions: ({row:#?}, {diff:#?})"
4788        );
4789        let collection_timestamp = row
4790            .unpack()
4791            .get(3)
4792            .expect("definition of mz_object_arrangement_size_history changed")
4793            .unwrap_timestamptz()
4794            .timestamp_millis();
4795        let collection_timestamp: u128 = collection_timestamp
4796            .try_into()
4797            .expect("all collections happen after Jan 1 1970");
4798        if collection_timestamp < cutoff_ts {
4799            expired.push(BuiltinTableUpdate::row(item_id, row, Diff::MINUS_ONE));
4800        }
4801    }
4802    expired
4803}
4804
4805#[cfg(test)]
4806impl Coordinator {
4807    #[allow(dead_code)]
4808    async fn verify_ship_dataflow_no_error(
4809        &mut self,
4810        dataflow: DataflowDescription<LirRelationExpr>,
4811    ) {
4812        // `ship_dataflow_new` is not allowed to have a `Result` return because this function is
4813        // called after `catalog_transact`, after which no errors are allowed. This test exists to
4814        // prevent us from incorrectly teaching those functions how to return errors (which has
4815        // happened twice and is the motivation for this test).
4816
4817        // An arbitrary compute instance ID to satisfy the function calls below. Note that
4818        // this only works because this function will never run.
4819        let compute_instance = ComputeInstanceId::user(1).expect("1 is a valid ID");
4820
4821        let _: () = self.ship_dataflow(dataflow, compute_instance, None).await;
4822    }
4823}
4824
4825/// Contains information about the last message the [`Coordinator`] processed.
4826struct LastMessage {
4827    kind: &'static str,
4828    stmt: Option<Arc<Statement<Raw>>>,
4829}
4830
4831impl LastMessage {
4832    /// Returns a redacted version of the statement that is safe for logs.
4833    fn stmt_to_string(&self) -> Cow<'static, str> {
4834        self.stmt
4835            .as_ref()
4836            .map(|stmt| stmt.to_ast_string_redacted().into())
4837            .unwrap_or(Cow::Borrowed("<none>"))
4838    }
4839}
4840
4841impl fmt::Debug for LastMessage {
4842    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4843        f.debug_struct("LastMessage")
4844            .field("kind", &self.kind)
4845            .field("stmt", &self.stmt_to_string())
4846            .finish()
4847    }
4848}
4849
4850impl Drop for LastMessage {
4851    fn drop(&mut self) {
4852        // Only print the last message if we're currently panicking, otherwise we'd spam our logs.
4853        if std::thread::panicking() {
4854            // If we're panicking theres no guarantee `tracing` still works, so print to stderr.
4855            eprintln!("Coordinator panicking, dumping last message\n{self:?}",);
4856        }
4857    }
4858}
4859
4860/// Serves the coordinator based on the provided configuration.
4861///
4862/// For a high-level description of the coordinator, see the [crate
4863/// documentation](crate).
4864///
4865/// Returns a handle to the coordinator and a client to communicate with the
4866/// coordinator.
4867///
4868/// BOXED FUTURE: As of Nov 2023 the returned Future from this function was 42KB. This would
4869/// get stored on the stack which is bad for runtime performance, and blow up our stack usage.
4870/// Because of that we purposefully move this Future onto the heap (i.e. Box it).
4871pub fn serve(
4872    Config {
4873        controller_config,
4874        controller_envd_epoch,
4875        mut storage,
4876        timestamp_oracle_url,
4877        unsafe_mode,
4878        all_features,
4879        build_info,
4880        environment_id,
4881        metrics_registry,
4882        now,
4883        secrets_controller,
4884        cloud_resource_controller,
4885        cluster_replica_sizes,
4886        builtin_system_cluster_config,
4887        builtin_catalog_server_cluster_config,
4888        builtin_probe_cluster_config,
4889        builtin_support_cluster_config,
4890        builtin_analytics_cluster_config,
4891        system_parameter_defaults,
4892        availability_zones,
4893        storage_usage_client,
4894        storage_usage_collection_interval,
4895        storage_usage_retention_period,
4896        segment_client,
4897        egress_addresses,
4898        aws_account_id,
4899        aws_privatelink_availability_zones,
4900        connection_context,
4901        connection_limit_callback,
4902        remote_system_parameters,
4903        webhook_concurrency_limit,
4904        http_host_name,
4905        tracing_handle,
4906        read_only_controllers,
4907        caught_up_trigger: clusters_caught_up_trigger,
4908        helm_chart_version,
4909        license_key,
4910        external_login_password_mz_system,
4911        force_builtin_schema_migration,
4912    }: Config,
4913) -> BoxFuture<'static, Result<(Handle, Client), AdapterError>> {
4914    async move {
4915        let coord_start = Instant::now();
4916        info!("startup: coordinator init: beginning");
4917        info!("startup: coordinator init: preamble beginning");
4918
4919        // Initializing the builtins can be an expensive process and consume a lot of memory. We
4920        // forcibly initialize it early while the stack is relatively empty to avoid stack
4921        // overflows later.
4922        let _builtins = LazyLock::force(&BUILTINS_STATIC);
4923
4924        let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
4925        let (internal_cmd_tx, internal_cmd_rx) = mpsc::unbounded_channel();
4926        let (strict_serializable_reads_tx, strict_serializable_reads_rx) =
4927            mpsc::unbounded_channel();
4928
4929        // Validate and process availability zones.
4930        if !availability_zones.iter().all_unique() {
4931            coord_bail!("availability zones must be unique");
4932        }
4933
4934        let aws_principal_context = match (
4935            aws_account_id,
4936            connection_context.aws_external_id_prefix.clone(),
4937        ) {
4938            (Some(aws_account_id), Some(aws_external_id_prefix)) => Some(AwsPrincipalContext {
4939                aws_account_id,
4940                aws_external_id_prefix,
4941            }),
4942            _ => None,
4943        };
4944
4945        let aws_privatelink_availability_zones = aws_privatelink_availability_zones
4946            .map(|azs_vec| BTreeSet::from_iter(azs_vec.iter().cloned()));
4947
4948        info!(
4949            "startup: coordinator init: preamble complete in {:?}",
4950            coord_start.elapsed()
4951        );
4952        let oracle_init_start = Instant::now();
4953        info!("startup: coordinator init: timestamp oracle init beginning");
4954
4955        let timestamp_oracle_config = timestamp_oracle_url
4956            .map(|url| TimestampOracleConfig::from_url(&url, &metrics_registry))
4957            .transpose()?;
4958        let mut initial_timestamps =
4959            get_initial_oracle_timestamps(&timestamp_oracle_config).await?;
4960
4961        // Insert an entry for the `EpochMilliseconds` timeline if one doesn't exist,
4962        // which will ensure that the timeline is initialized since it's required
4963        // by the system.
4964        initial_timestamps
4965            .entry(Timeline::EpochMilliseconds)
4966            .or_insert_with(mz_repr::Timestamp::minimum);
4967        let mut timestamp_oracles = BTreeMap::new();
4968        for (timeline, initial_timestamp) in initial_timestamps {
4969            Coordinator::ensure_timeline_state_with_initial_time(
4970                &timeline,
4971                initial_timestamp,
4972                now.clone(),
4973                timestamp_oracle_config.clone(),
4974                &mut timestamp_oracles,
4975                read_only_controllers,
4976            )
4977            .await;
4978        }
4979
4980        // Opening the durable catalog uses one or more timestamps without communicating with
4981        // the timestamp oracle. Here we make sure to apply the catalog upper with the timestamp
4982        // oracle to linearize future operations with opening the catalog.
4983        let catalog_upper = storage.current_upper().await;
4984        // Choose a time at which to boot. This is used, for example, to prune
4985        // old storage usage data or migrate audit log entries.
4986        //
4987        // This time is usually the current system time, but with protection
4988        // against backwards time jumps, even across restarts.
4989        let epoch_millis_oracle = &timestamp_oracles
4990            .get(&Timeline::EpochMilliseconds)
4991            .expect("inserted above")
4992            .oracle;
4993
4994        // The catalog shard's upper is durable, so a write that once landed far ahead of the
4995        // clock is re-applied to the oracle here on every boot and cannot be waited out. We
4996        // report it rather than refusing to start: the timeline is stalled either way, and a
4997        // process that will not boot turns that into a total outage plus a crash loop.
4998        let boot_now: mz_repr::Timestamp = (now)().into();
4999        if catalog_upper > timeline::write_ts_upper_bound(&boot_now) {
5000            tracing::error!(
5001                %catalog_upper, %boot_now,
5002                "catalog upper is far ahead of the wall clock, so writes and \
5003                strict-serializable reads on the EpochMilliseconds timeline will block \
5004                until the clock catches up",
5005            );
5006        }
5007
5008        let mut boot_ts = if read_only_controllers {
5009            let read_ts = epoch_millis_oracle.read_ts().await;
5010            std::cmp::max(read_ts, catalog_upper)
5011        } else {
5012            // Getting/applying a write timestamp bumps the write timestamp in the
5013            // oracle, which we're not allowed in read-only mode.
5014            epoch_millis_oracle.apply_write(catalog_upper).await;
5015            epoch_millis_oracle.write_ts().await.timestamp
5016        };
5017
5018        info!(
5019            "startup: coordinator init: timestamp oracle init complete in {:?}",
5020            oracle_init_start.elapsed()
5021        );
5022
5023        let catalog_open_start = Instant::now();
5024        info!("startup: coordinator init: catalog open beginning");
5025        let persist_client = controller_config
5026            .persist_clients
5027            .open(controller_config.persist_location.clone())
5028            .await
5029            .context("opening persist client")?;
5030        let builtin_item_migration_config =
5031            BuiltinItemMigrationConfig {
5032                persist_client: persist_client.clone(),
5033                read_only: read_only_controllers,
5034                force_migration: force_builtin_schema_migration,
5035            }
5036        ;
5037        let OpenCatalogResult {
5038            mut catalog,
5039            migrated_storage_collections_0dt,
5040            new_builtin_collections,
5041            builtin_table_updates,
5042            cached_global_exprs,
5043            uncached_local_exprs,
5044        } = Catalog::open(mz_catalog::config::Config {
5045            storage,
5046            metrics_registry: &metrics_registry,
5047            state: mz_catalog::config::StateConfig {
5048                unsafe_mode,
5049                all_features,
5050                build_info,
5051                environment_id: environment_id.clone(),
5052                read_only: read_only_controllers,
5053                now: now.clone(),
5054                boot_ts: boot_ts.clone(),
5055                skip_migrations: false,
5056                cluster_replica_sizes,
5057                builtin_system_cluster_config,
5058                builtin_catalog_server_cluster_config,
5059                builtin_probe_cluster_config,
5060                builtin_support_cluster_config,
5061                builtin_analytics_cluster_config,
5062                system_parameter_defaults,
5063                remote_system_parameters,
5064                availability_zones,
5065                egress_addresses,
5066                aws_principal_context,
5067                aws_privatelink_availability_zones,
5068                connection_context,
5069                http_host_name,
5070                builtin_item_migration_config,
5071                persist_client: persist_client.clone(),
5072                enable_expression_cache_override: None,
5073                helm_chart_version,
5074                external_login_password_mz_system,
5075                license_key: license_key.clone(),
5076            },
5077        })
5078        .await?;
5079
5080        // Opening the catalog uses one or more timestamps, so push the boot timestamp up to the
5081        // current catalog upper.
5082        let catalog_upper = catalog.current_upper().await;
5083        boot_ts = std::cmp::max(boot_ts, catalog_upper);
5084
5085        if !read_only_controllers {
5086            epoch_millis_oracle.apply_write(boot_ts).await;
5087        }
5088
5089        info!(
5090            "startup: coordinator init: catalog open complete in {:?}",
5091            catalog_open_start.elapsed()
5092        );
5093
5094        let coord_thread_start = Instant::now();
5095        info!("startup: coordinator init: coordinator thread start beginning");
5096
5097        let session_id = catalog.config().session_id;
5098        let start_instant = catalog.config().start_instant;
5099
5100        // In order for the coordinator to support Rc and Refcell types, it cannot be
5101        // sent across threads. Spawn it in a thread and have this parent thread wait
5102        // for bootstrap completion before proceeding.
5103        let (bootstrap_tx, bootstrap_rx) = oneshot::channel();
5104        let handle = TokioHandle::current();
5105
5106        let metrics = Metrics::register_into(&metrics_registry);
5107        let metrics_clone = metrics.clone();
5108        let optimizer_metrics = OptimizerMetrics::register_into(
5109            &metrics_registry,
5110            catalog.system_config().optimizer_e2e_latency_warning_threshold(),
5111        );
5112        let segment_client_clone = segment_client.clone();
5113        let coord_now = now.clone();
5114        let advance_timelines_interval =
5115            tokio::time::interval(catalog.system_config().default_timestamp_interval());
5116
5117        let clusters_caught_up_check_interval = if read_only_controllers {
5118            let dyncfgs = catalog.system_config().dyncfgs();
5119            let interval = WITH_0DT_DEPLOYMENT_CAUGHT_UP_CHECK_INTERVAL.get(dyncfgs);
5120
5121            let mut interval = tokio::time::interval(interval);
5122            interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
5123            interval
5124        } else {
5125            // When not in read-only mode, we don't do hydration checks. But we
5126            // still have to provide _some_ interval. This is large enough that
5127            // it doesn't matter.
5128            //
5129            // TODO(aljoscha): We cannot use Duration::MAX right now because of
5130            // https://github.com/tokio-rs/tokio/issues/6634. Use that once it's
5131            // fixed for good.
5132            let mut interval = tokio::time::interval(Duration::from_secs(60 * 60));
5133            interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
5134            interval
5135        };
5136
5137        let clusters_caught_up_check =
5138            clusters_caught_up_trigger.map(|trigger| {
5139                let mut exclude_collections: BTreeSet<GlobalId> =
5140                    new_builtin_collections.iter().copied().collect();
5141
5142                // A collection that can't advance its write frontier in read-only mode
5143                // stalls its transitive dependents too, so exclude those from the caught-up
5144                // check as well. That's migrated MVs (their dataflows don't write in
5145                // read-only mode) and new builtin MVs (their fresh shard has no writer until
5146                // this deployment promotes). An excluded dependent may still be hydrating
5147                // right after promotion, a brief blip we accept because these MVs are small
5148                // and get a writer at cut-over.
5149                //
5150                // TODO: Consider sending `allow_writes` for the dataflows of migrated MVs, which
5151                //       would allow them to make progress even in read-only mode. This doesn't
5152                //       work for MVs based on `mz_catalog_raw`, if the leader's version is less
5153                //       than v26.17, since before that version the catalog shard's frontier wasn't
5154                //       kept up-to-date with the current time. So this workaround has to remain in
5155                //       place upgrades from a version less than v26.17 are no longer supported.
5156                let new_builtin_mvs = new_builtin_collections
5157                    .iter()
5158                    .map(|global_id| {
5159                        catalog
5160                            .state()
5161                            .try_get_entry_by_global_id(global_id)
5162                            .expect("new builtin collections have catalog entries")
5163                    })
5164                    .filter(|entry| entry.is_materialized_view())
5165                    .map(|entry| entry.id());
5166                let mut todo: Vec<_> = migrated_storage_collections_0dt
5167                    .iter()
5168                    .copied()
5169                    .filter(|id| catalog.state().get_entry(id).is_materialized_view())
5170                    .chain(new_builtin_mvs)
5171                    .collect();
5172                while let Some(item_id) = todo.pop() {
5173                    let entry = catalog.state().get_entry(&item_id);
5174                    exclude_collections.extend(entry.global_ids());
5175                    todo.extend_from_slice(entry.used_by());
5176                }
5177
5178                CaughtUpCheckContext {
5179                    trigger,
5180                    exclude_collections,
5181                    cluster_stability: BTreeMap::new(),
5182                }
5183            });
5184
5185        if let Some(TimestampOracleConfig::Postgres(pg_config)) =
5186            timestamp_oracle_config.as_ref()
5187        {
5188            // Apply settings from system vars as early as possible because some
5189            // of them are locked in right when an oracle is first opened!
5190            let pg_timestamp_oracle_params =
5191                flags::timestamp_oracle_config(catalog.system_config());
5192            pg_timestamp_oracle_params.apply(pg_config);
5193        }
5194
5195        // Register a callback so whenever the MAX_CONNECTIONS or SUPERUSER_RESERVED_CONNECTIONS
5196        // system variables change, we update our connection limits.
5197        let connection_limit_callback: Arc<dyn Fn(&SystemVars) + Send + Sync> =
5198            Arc::new(move |system_vars: &SystemVars| {
5199                let limit: u64 = system_vars.max_connections().cast_into();
5200                let superuser_reserved: u64 =
5201                    system_vars.superuser_reserved_connections().cast_into();
5202
5203                // If superuser_reserved > max_connections, prefer max_connections.
5204                //
5205                // In this scenario all normal users would be locked out because all connections
5206                // would be reserved for superusers so complain if this is the case.
5207                let superuser_reserved = if superuser_reserved >= limit {
5208                    tracing::warn!(
5209                        "superuser_reserved ({superuser_reserved}) is greater than max connections ({limit})!"
5210                    );
5211                    limit
5212                } else {
5213                    superuser_reserved
5214                };
5215
5216                (connection_limit_callback)(limit, superuser_reserved);
5217            });
5218        catalog.system_config_mut().register_callback(
5219            &mz_sql::session::vars::MAX_CONNECTIONS,
5220            Arc::clone(&connection_limit_callback),
5221        );
5222        catalog.system_config_mut().register_callback(
5223            &mz_sql::session::vars::SUPERUSER_RESERVED_CONNECTIONS,
5224            connection_limit_callback,
5225        );
5226
5227        let (group_commit_tx, group_commit_rx) = appends::notifier();
5228
5229        let parent_span = tracing::Span::current();
5230        let thread = thread::Builder::new()
5231            // The Coordinator thread tends to keep a lot of data on its stack. To
5232            // prevent a stack overflow we allocate a stack three times as big as the default
5233            // stack.
5234            .stack_size(3 * stack::STACK_SIZE)
5235            .name("coordinator".to_string())
5236            .spawn(move || {
5237                let span = info_span!(parent: parent_span, "coord::coordinator").entered();
5238
5239                let controller = handle
5240                    .block_on({
5241                        catalog.initialize_controller(
5242                            controller_config,
5243                            controller_envd_epoch,
5244                            read_only_controllers,
5245                        )
5246                    })
5247                    .unwrap_or_terminate("failed to initialize storage_controller");
5248                // Initializing the controller uses one or more timestamps, so push the boot timestamp up to the
5249                // current catalog upper.
5250                let catalog_upper = handle.block_on(catalog.current_upper());
5251                boot_ts = std::cmp::max(boot_ts, catalog_upper);
5252                if !read_only_controllers {
5253                    let epoch_millis_oracle = &timestamp_oracles
5254                        .get(&Timeline::EpochMilliseconds)
5255                        .expect("inserted above")
5256                        .oracle;
5257                    handle.block_on(epoch_millis_oracle.apply_write(boot_ts));
5258                }
5259
5260                let catalog = Arc::new(catalog);
5261                // Both are read once at startup, see the field docs on
5262                // `occ_write_semaphore` and `frontend_read_then_write_enabled`.
5263                let max_concurrent_occ_writes =
5264                    usize::cast_from(catalog.system_config().max_concurrent_occ_writes());
5265                let frontend_read_then_write_enabled = {
5266                                FRONTEND_READ_THEN_WRITE.get(catalog.system_config().dyncfgs())
5267                };
5268
5269                let caching_secrets_reader = CachingSecretsReader::new(secrets_controller.reader());
5270                let (group_committer_tx, group_committer_rx) = mpsc::unbounded_channel();
5271                let mut coord = Coordinator {
5272                    controller,
5273                    catalog,
5274                    internal_cmd_tx,
5275                    group_commit_tx,
5276                    reconcile_now: Arc::new(Notify::new()),
5277                    group_committer_tx,
5278                    strict_serializable_reads_tx,
5279                    linearize_reads_notify: Arc::new(Notify::new()),
5280                    global_timelines: timestamp_oracles,
5281                    transient_id_gen: Arc::new(TransientIdGen::new()),
5282                    active_conns: BTreeMap::new(),
5283                    txn_read_holds: Default::default(),
5284                    pending_peeks: BTreeMap::new(),
5285                    client_pending_peeks: BTreeMap::new(),
5286                    pending_linearize_read_txns: BTreeMap::new(),
5287                    serialized_ddl: LockedVecDeque::new(),
5288                    active_compute_sinks: BTreeMap::new(),
5289                    active_webhooks: BTreeMap::new(),
5290                    active_copies: BTreeMap::new(),
5291                    connection_cancel_watches: BTreeMap::new(),
5292                    introspection_subscribes: BTreeMap::new(),
5293                    write_locks: BTreeMap::new(),
5294                    deferred_write_ops: BTreeMap::new(),
5295                    pending_writes: Vec::new(),
5296                    occ_write_semaphore: Arc::new(Semaphore::new(max_concurrent_occ_writes)),
5297                    frontend_read_then_write_enabled,
5298                    advance_timelines_interval,
5299                    secrets_controller,
5300                    caching_secrets_reader,
5301                    cloud_resource_controller,
5302                    storage_usage_client,
5303                    storage_usage_collection_interval,
5304                    segment_client,
5305                    metrics,
5306                    catalog_info_metrics_registry: metrics_registry.clone(),
5307                    scoped_frontend: None,
5308                    optimizer_metrics,
5309                    tracing_handle,
5310                    statement_logging: StatementLogging::new(coord_now.clone()),
5311                    webhook_concurrency_limit,
5312                    timestamp_oracle_config,
5313                    caught_up_check_interval: clusters_caught_up_check_interval,
5314                    caught_up_check: clusters_caught_up_check,
5315                    installed_watch_sets: BTreeMap::new(),
5316                    connection_watch_sets: BTreeMap::new(),
5317                    cluster_replica_statuses: ClusterReplicaStatuses::new(),
5318                    read_only_controllers,
5319                    buffered_builtin_table_updates: Some(Vec::new()),
5320                    license_key,
5321                    user_id_pool: IdPool::empty(),
5322                    persist_client,
5323                };
5324
5325                // Read-only promotion restarts the process and creates a fresh committer.
5326                handle.block_on(async {
5327                    appends::spawn_group_committer(
5328                        group_committer_rx,
5329                        coord.get_local_timestamp_oracle(),
5330                        coord.controller.storage.table_write_handle(),
5331                        coord.catalog().upper_handle(),
5332                        coord.internal_cmd_tx.clone(),
5333                        coord.catalog().config().now.clone(),
5334                        coord.metrics.clone(),
5335                        coord.catalog().system_config().dyncfgs(),
5336                    );
5337                });
5338
5339                let bootstrap = handle.block_on(async {
5340                    coord
5341                        .bootstrap(
5342                            boot_ts,
5343                            migrated_storage_collections_0dt,
5344                            builtin_table_updates,
5345                            cached_global_exprs,
5346                            uncached_local_exprs,
5347                        )
5348                        .await?;
5349                    coord
5350                        .controller
5351                        .remove_orphaned_replicas(
5352                            coord.catalog().get_next_user_replica_id().await?,
5353                            coord.catalog().get_next_system_replica_id().await?,
5354                        )
5355                        .await
5356                        .map_err(AdapterError::Orchestrator)?;
5357
5358                    if let Some(retention_period) = storage_usage_retention_period {
5359                        coord
5360                            .prune_storage_usage_events_on_startup(retention_period)
5361                            .await;
5362                    }
5363
5364                    coord.prune_arrangement_sizes_history_on_startup().await;
5365
5366                    Ok(())
5367                });
5368                let ok = bootstrap.is_ok();
5369                drop(span);
5370                bootstrap_tx
5371                    .send(bootstrap)
5372                    .expect("bootstrap_rx is not dropped until it receives this message");
5373                if ok {
5374                    handle.block_on(coord.serve(
5375                        internal_cmd_rx,
5376                        strict_serializable_reads_rx,
5377                        cmd_rx,
5378                        group_commit_rx,
5379                    ));
5380                }
5381            })
5382            .expect("failed to create coordinator thread");
5383        match bootstrap_rx
5384            .await
5385            .expect("bootstrap_tx always sends a message or panics/halts")
5386        {
5387            Ok(()) => {
5388                info!(
5389                    "startup: coordinator init: coordinator thread start complete in {:?}",
5390                    coord_thread_start.elapsed()
5391                );
5392                info!(
5393                    "startup: coordinator init: complete in {:?}",
5394                    coord_start.elapsed()
5395                );
5396                let handle = Handle {
5397                    session_id,
5398                    start_instant,
5399                    _thread: thread.join_on_drop(),
5400                };
5401                let client = Client::new(
5402                    build_info,
5403                    cmd_tx,
5404                    metrics_clone,
5405                    now,
5406                    environment_id,
5407                    segment_client_clone,
5408                );
5409                Ok((handle, client))
5410            }
5411            Err(e) => Err(e),
5412        }
5413    }
5414    .boxed()
5415}
5416
5417// Determines and returns the highest timestamp for each timeline, for all known
5418// timestamp oracle implementations.
5419//
5420// Initially, we did this so that we can switch between implementations of
5421// timestamp oracle, but now we also do this to determine a monotonic boot
5422// timestamp, a timestamp that does not regress across reboots.
5423//
5424// This mostly works, but there can be linearizability violations, because there
5425// is no central moment where we do distributed coordination for all oracle
5426// types. Working around this seems prohibitively hard, maybe even impossible so
5427// we have to live with this window of potential violations during the upgrade
5428// window (which is the only point where we should switch oracle
5429// implementations).
5430async fn get_initial_oracle_timestamps(
5431    timestamp_oracle_config: &Option<TimestampOracleConfig>,
5432) -> Result<BTreeMap<Timeline, Timestamp>, AdapterError> {
5433    let mut initial_timestamps = BTreeMap::new();
5434
5435    if let Some(config) = timestamp_oracle_config {
5436        let oracle_timestamps = config.get_all_timelines().await?;
5437
5438        let debug_msg = || {
5439            oracle_timestamps
5440                .iter()
5441                .map(|(timeline, ts)| format!("{:?} -> {}", timeline, ts))
5442                .join(", ")
5443        };
5444        info!(
5445            "current timestamps from the timestamp oracle: {}",
5446            debug_msg()
5447        );
5448
5449        for (timeline, ts) in oracle_timestamps {
5450            let entry = initial_timestamps
5451                .entry(Timeline::from_str(&timeline).expect("could not parse timeline"));
5452
5453            entry
5454                .and_modify(|current_ts| *current_ts = std::cmp::max(*current_ts, ts))
5455                .or_insert(ts);
5456        }
5457    } else {
5458        info!("no timestamp oracle configured!");
5459    };
5460
5461    let debug_msg = || {
5462        initial_timestamps
5463            .iter()
5464            .map(|(timeline, ts)| format!("{:?}: {}", timeline, ts))
5465            .join(", ")
5466    };
5467    info!("initial oracle timestamps: {}", debug_msg());
5468
5469    Ok(initial_timestamps)
5470}
5471
5472#[instrument]
5473pub async fn load_remote_system_parameters(
5474    storage: &mut Box<dyn OpenableDurableCatalogState>,
5475    system_parameter_sync_config: Option<SystemParameterSyncConfig>,
5476    system_parameter_sync_timeout: Duration,
5477) -> Result<Option<BTreeMap<String, String>>, AdapterError> {
5478    if let Some(system_parameter_sync_config) = system_parameter_sync_config {
5479        tracing::info!("parameter sync on boot: start sync");
5480
5481        // We intentionally block initial startup, potentially forever,
5482        // on initializing LaunchDarkly. This may seem scary, but the
5483        // alternative is even scarier. Over time, we expect that the
5484        // compiled-in default values for the system parameters will
5485        // drift substantially from the defaults configured in
5486        // LaunchDarkly, to the point that starting an environment
5487        // without loading the latest values from LaunchDarkly will
5488        // result in running an untested configuration.
5489        //
5490        // Note this only applies during initial startup. Restarting
5491        // after we've synced once only blocks for a maximum of
5492        // `FRONTEND_SYNC_TIMEOUT` on LaunchDarkly, as it seems
5493        // reasonable to assume that the last-synced configuration was
5494        // valid enough.
5495        //
5496        // This philosophy appears to provide a good balance between not
5497        // running untested configurations in production while also not
5498        // making LaunchDarkly a "tier 1" dependency for existing
5499        // environments.
5500        //
5501        // If this proves to be an issue, we could seek to address the
5502        // configuration drift in a different way--for example, by
5503        // writing a script that runs in CI nightly and checks for
5504        // deviation between the compiled Rust code and LaunchDarkly.
5505        //
5506        // If it is absolutely necessary to bring up a new environment
5507        // while LaunchDarkly is down, the following manual mitigation
5508        // can be performed:
5509        //
5510        //    1. Edit the environmentd startup parameters to omit the
5511        //       LaunchDarkly configuration.
5512        //    2. Boot environmentd.
5513        //    3. Use the catalog-debug tool to run `edit config "{\"key\":\"system_config_synced\"}" "{\"value\": 1}"`.
5514        //    4. Adjust any other parameters as necessary to avoid
5515        //       running a nonstandard configuration in production.
5516        //    5. Edit the environmentd startup parameters to restore the
5517        //       LaunchDarkly configuration, for when LaunchDarkly comes
5518        //       back online.
5519        //    6. Reboot environmentd.
5520        let mut params = SynchronizedParameters::new(SystemVars::default());
5521        let frontend_sync = async {
5522            let frontend = SystemParameterFrontend::from(&system_parameter_sync_config).await?;
5523            frontend.pull(&mut params);
5524            let ops = params
5525                .modified()
5526                .into_iter()
5527                .map(|param| {
5528                    let name = param.name;
5529                    let value = param.value;
5530                    tracing::info!(name, value, initial = true, "sync parameter");
5531                    (name, value)
5532                })
5533                .collect();
5534            tracing::info!("parameter sync on boot: end sync");
5535            Ok(Some(ops))
5536        };
5537        if !storage.has_system_config_synced_once().await? {
5538            frontend_sync.await
5539        } else {
5540            match mz_ore::future::timeout(system_parameter_sync_timeout, frontend_sync).await {
5541                Ok(ops) => Ok(ops),
5542                Err(TimeoutError::Inner(e)) => Err(e),
5543                Err(TimeoutError::DeadlineElapsed) => {
5544                    tracing::info!("parameter sync on boot: sync has timed out");
5545                    Ok(None)
5546                }
5547            }
5548        }
5549    } else {
5550        Ok(None)
5551    }
5552}
5553
5554#[derive(Debug)]
5555pub enum WatchSetResponse {
5556    StatementDependenciesReady(StatementLoggingId, StatementLifecycleEvent),
5557    AlterSinkReady(AlterSinkReadyContext),
5558    AlterMaterializedViewReady(AlterMaterializedViewReadyContext),
5559}
5560
5561#[derive(Debug)]
5562pub struct AlterSinkReadyContext {
5563    ctx: Option<ExecuteContext>,
5564    otel_ctx: OpenTelemetryContext,
5565    plan: AlterSinkPlan,
5566    plan_validity: PlanValidity,
5567    read_hold: ReadHolds,
5568}
5569
5570impl AlterSinkReadyContext {
5571    fn ctx(&mut self) -> &mut ExecuteContext {
5572        self.ctx.as_mut().expect("only cleared on drop")
5573    }
5574
5575    fn retire(mut self, result: Result<ExecuteResponse, AdapterError>) {
5576        self.ctx
5577            .take()
5578            .expect("only cleared on drop")
5579            .retire(result);
5580    }
5581}
5582
5583impl Drop for AlterSinkReadyContext {
5584    fn drop(&mut self) {
5585        if let Some(ctx) = self.ctx.take() {
5586            ctx.retire(Err(AdapterError::Canceled));
5587        }
5588    }
5589}
5590
5591#[derive(Debug)]
5592pub struct AlterMaterializedViewReadyContext {
5593    ctx: Option<ExecuteContext>,
5594    otel_ctx: OpenTelemetryContext,
5595    plan: plan::AlterMaterializedViewApplyReplacementPlan,
5596    plan_validity: PlanValidity,
5597}
5598
5599impl AlterMaterializedViewReadyContext {
5600    fn ctx(&mut self) -> &mut ExecuteContext {
5601        self.ctx.as_mut().expect("only cleared on drop")
5602    }
5603
5604    fn retire(mut self, result: Result<ExecuteResponse, AdapterError>) {
5605        self.ctx
5606            .take()
5607            .expect("only cleared on drop")
5608            .retire(result);
5609    }
5610}
5611
5612impl Drop for AlterMaterializedViewReadyContext {
5613    fn drop(&mut self) {
5614        if let Some(ctx) = self.ctx.take() {
5615            ctx.retire(Err(AdapterError::Canceled));
5616        }
5617    }
5618}
5619
5620/// A struct for tracking the ownership of a lock and a VecDeque to store to-be-done work after the
5621/// lock is freed.
5622#[derive(Debug)]
5623struct LockedVecDeque<T> {
5624    items: VecDeque<T>,
5625    lock: Arc<tokio::sync::Mutex<()>>,
5626}
5627
5628impl<T> LockedVecDeque<T> {
5629    pub fn new() -> Self {
5630        Self {
5631            items: VecDeque::new(),
5632            lock: Arc::new(tokio::sync::Mutex::new(())),
5633        }
5634    }
5635
5636    pub fn try_lock_owned(&self) -> Result<OwnedMutexGuard<()>, tokio::sync::TryLockError> {
5637        Arc::clone(&self.lock).try_lock_owned()
5638    }
5639
5640    pub fn is_empty(&self) -> bool {
5641        self.items.is_empty()
5642    }
5643
5644    pub fn push_back(&mut self, value: T) {
5645        self.items.push_back(value)
5646    }
5647
5648    pub fn pop_front(&mut self) -> Option<T> {
5649        self.items.pop_front()
5650    }
5651
5652    pub fn remove(&mut self, index: usize) -> Option<T> {
5653        self.items.remove(index)
5654    }
5655
5656    pub fn iter(&self) -> std::collections::vec_deque::Iter<'_, T> {
5657        self.items.iter()
5658    }
5659}
5660
5661#[derive(Debug)]
5662struct DeferredPlanStatement {
5663    ctx: ExecuteContext,
5664    ps: PlanStatement,
5665}
5666
5667#[derive(Debug)]
5668enum PlanStatement {
5669    Statement {
5670        stmt: Arc<Statement<Raw>>,
5671        params: Params,
5672    },
5673    Plan {
5674        plan: mz_sql::plan::Plan,
5675        resolved_ids: ResolvedIds,
5676        sql_impl_resolved_ids: ResolvedIds,
5677    },
5678}
5679
5680#[derive(Debug, Error)]
5681pub enum NetworkPolicyError {
5682    #[error("Access denied for address {0}")]
5683    AddressDenied(IpAddr),
5684    #[error("Access denied missing IP address")]
5685    MissingIp,
5686}
5687
5688pub(crate) fn validate_ip_with_policy_rules(
5689    ip: &IpAddr,
5690    rules: &Vec<NetworkPolicyRule>,
5691) -> Result<(), NetworkPolicyError> {
5692    // At the moment we're not handling action or direction
5693    // as those are only able to be "allow" and "ingress" respectively
5694    if rules.iter().any(|r| r.address.0.contains(ip)) {
5695        Ok(())
5696    } else {
5697        Err(NetworkPolicyError::AddressDenied(ip.clone()))
5698    }
5699}
5700
5701pub(crate) fn infer_sql_type_for_catalog(
5702    hir_expr: &HirRelationExpr,
5703    mir_expr: &MirRelationExpr,
5704) -> SqlRelationType {
5705    let mut typ = hir_expr.top_level_typ();
5706    typ.backport_nullability_and_keys(&mir_expr.typ());
5707    typ
5708}
5709
5710#[cfg(test)]
5711mod execute_context_tests {
5712    use tokio::sync::{mpsc, oneshot};
5713
5714    use super::*;
5715    use crate::session::Session;
5716    use crate::util::ClientTransmitter;
5717
5718    /// Runtime shutdown drops the barrier-waiting task that `retire` spawns. The context's `Drop`
5719    /// backstop must answer the client, rather than panicking on an unsent `ClientTransmitter`.
5720    #[mz_ore::test]
5721    fn test_retire_answers_client_when_runtime_shuts_down() {
5722        let runtime = tokio::runtime::Runtime::new().expect("can build runtime");
5723
5724        let (client_tx, mut client_rx) = oneshot::channel();
5725        let (internal_cmd_tx, _internal_cmd_rx) = mpsc::unbounded_channel();
5726
5727        runtime.block_on(async {
5728            let ctx = ExecuteContext::from_parts_with_response_barriers(
5729                ClientTransmitter::new(client_tx, internal_cmd_tx.clone()),
5730                internal_cmd_tx,
5731                Session::dummy(),
5732                ExecuteContextGuard::default(),
5733                // Stands in for a group commit that shutdown will never apply.
5734                vec![Box::pin(std::future::pending())],
5735            );
5736            ctx.retire(Ok(ExecuteResponse::StartedTransaction));
5737        });
5738
5739        drop(runtime);
5740
5741        let response = client_rx.try_recv().expect("client must be answered");
5742        assert!(
5743            matches!(response.result, Err(AdapterError::Internal(_))),
5744            "expected an internal error, got {:?}",
5745            response.result
5746        );
5747    }
5748}
5749
5750#[cfg(test)]
5751mod id_pool_tests {
5752    use super::IdPool;
5753
5754    #[mz_ore::test]
5755    fn test_empty_pool() {
5756        let mut pool = IdPool::empty();
5757        assert_eq!(pool.remaining(), 0);
5758        assert_eq!(pool.allocate(), None);
5759        assert_eq!(pool.allocate_many(1), None);
5760    }
5761
5762    #[mz_ore::test]
5763    fn test_allocate_single() {
5764        let mut pool = IdPool::empty();
5765        pool.refill(10, 13);
5766        assert_eq!(pool.remaining(), 3);
5767        assert_eq!(pool.allocate(), Some(10));
5768        assert_eq!(pool.allocate(), Some(11));
5769        assert_eq!(pool.allocate(), Some(12));
5770        assert_eq!(pool.remaining(), 0);
5771        assert_eq!(pool.allocate(), None);
5772    }
5773
5774    #[mz_ore::test]
5775    fn test_allocate_many() {
5776        let mut pool = IdPool::empty();
5777        pool.refill(100, 105);
5778        assert_eq!(pool.allocate_many(3), Some(vec![100, 101, 102]));
5779        assert_eq!(pool.remaining(), 2);
5780        // Not enough remaining for 3 more.
5781        assert_eq!(pool.allocate_many(3), None);
5782        // But 2 works.
5783        assert_eq!(pool.allocate_many(2), Some(vec![103, 104]));
5784        assert_eq!(pool.remaining(), 0);
5785    }
5786
5787    #[mz_ore::test]
5788    fn test_allocate_many_zero() {
5789        let mut pool = IdPool::empty();
5790        pool.refill(1, 5);
5791        assert_eq!(pool.allocate_many(0), Some(vec![]));
5792        assert_eq!(pool.remaining(), 4);
5793    }
5794
5795    #[mz_ore::test]
5796    fn test_refill_resets_pool() {
5797        let mut pool = IdPool::empty();
5798        pool.refill(0, 2);
5799        assert_eq!(pool.allocate(), Some(0));
5800        // Refill before exhaustion replaces the range.
5801        pool.refill(50, 52);
5802        assert_eq!(pool.allocate(), Some(50));
5803        assert_eq!(pool.allocate(), Some(51));
5804        assert_eq!(pool.allocate(), None);
5805    }
5806
5807    #[mz_ore::test]
5808    fn test_mixed_allocate_and_allocate_many() {
5809        let mut pool = IdPool::empty();
5810        pool.refill(0, 10);
5811        assert_eq!(pool.allocate(), Some(0));
5812        assert_eq!(pool.allocate_many(3), Some(vec![1, 2, 3]));
5813        assert_eq!(pool.allocate(), Some(4));
5814        assert_eq!(pool.remaining(), 5);
5815    }
5816
5817    #[mz_ore::test]
5818    #[should_panic(expected = "invalid pool range")]
5819    fn test_refill_invalid_range_panics() {
5820        let mut pool = IdPool::empty();
5821        pool.refill(10, 5);
5822    }
5823}
5824
5825#[cfg(test)]
5826mod arrangement_sizes_pruner_tests {
5827    use mz_repr::catalog_item_id::CatalogItemId;
5828    use mz_repr::{Datum, Row};
5829
5830    use super::arrangement_sizes_expired_retractions;
5831
5832    // Pack a row shaped like `mz_object_arrangement_size_history`: the pruner
5833    // only cares about column 3 (`collection_timestamp`), but we stuff the
5834    // other three columns with realistic values so shape changes would fail.
5835    fn history_row(ts_ms: i64) -> Row {
5836        let dt = mz_ore::now::to_datetime(ts_ms.try_into().expect("non-negative"));
5837        Row::pack_slice(&[
5838            Datum::String("r1"),
5839            Datum::String("u1"),
5840            Datum::Int64(123),
5841            Datum::TimestampTz(dt.try_into().expect("fits in TimestampTz")),
5842        ])
5843    }
5844
5845    fn item_id() -> CatalogItemId {
5846        // Any CatalogItemId will do; tests don't dispatch on it.
5847        CatalogItemId::User(42)
5848    }
5849
5850    #[mz_ore::test]
5851    fn empty_input_produces_no_retractions() {
5852        let out = arrangement_sizes_expired_retractions(Vec::new(), 1_000, item_id());
5853        assert!(out.is_empty());
5854    }
5855
5856    #[mz_ore::test]
5857    fn retracts_only_rows_strictly_before_cutoff() {
5858        // Mixes both sides of the filter and includes a row at exactly
5859        // the cutoff timestamp to pin down the strict-less-than boundary.
5860        let rows = vec![
5861            (history_row(100), 1),
5862            (history_row(500), 1),
5863            (history_row(1_000), 1), // at cutoff: kept (strict <)
5864            (history_row(5_000), 1),
5865        ];
5866        let out = arrangement_sizes_expired_retractions(rows, 1_000, item_id());
5867        assert_eq!(out.len(), 2);
5868    }
5869
5870    #[mz_ore::test]
5871    #[should_panic(expected = "consolidated contents should not contain retractions")]
5872    fn retraction_in_input_panics() {
5873        let rows = vec![(history_row(100), -1)];
5874        let _ = arrangement_sizes_expired_retractions(rows, 1_000, item_id());
5875    }
5876}