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