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