1use std::collections::{BTreeMap, BTreeSet, btree_map};
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16
17use futures::FutureExt;
18use maplit::btreemap;
19use mz_audit_log::VersionedStorageUsage;
20use mz_catalog::memory::objects::ClusterReplicaProcessStatus;
21use mz_controller::ControllerResponse;
22use mz_controller::clusters::{ClusterEvent, ClusterStatus};
23use mz_ore::cast::CastFrom;
24use mz_ore::instrument;
25use mz_ore::now::EpochMillis;
26use mz_ore::option::OptionExt;
27use mz_ore::tracing::OpenTelemetryContext;
28use mz_ore::{soft_assert_or_log, task};
29use mz_persist_client::usage::ShardsUsageReferenced;
30use mz_repr::{Datum, Diff, Row};
31use mz_sql::ast::Statement;
32use mz_sql::names::ResolvedIds;
33use mz_sql::pure::PurifiedStatement;
34use mz_storage_client::controller::IntrospectionType;
35use opentelemetry::trace::TraceContextExt;
36use rand::{Rng, SeedableRng, rngs};
37use serde_json::json;
38use tracing::{Instrument, Level, event, info_span, warn};
39use tracing_opentelemetry::OpenTelemetrySpanExt;
40
41use crate::active_compute_sink::{ActiveComputeSink, ActiveComputeSinkRetireReason};
42use crate::catalog::BuiltinTableUpdate;
43use crate::command::Command;
44use crate::coord::{
45 AlterConnectionValidationReady, ClusterReplicaStatuses, Coordinator,
46 CreateConnectionValidationReady, Message, PurifiedStatementReady, WatchSetResponse,
47};
48use crate::telemetry::{EventDetails, SegmentClientExt};
49use crate::{AdapterNotice, TimestampContext};
50
51impl Coordinator {
52 #[instrument]
57 pub(crate) async fn handle_message(&mut self, msg: Message) -> () {
58 match msg {
59 Message::Command(otel_ctx, cmd) => {
60 let span = tracing::info_span!("message_command").or_current();
64 span.in_scope(|| otel_ctx.attach_as_parent());
65 self.message_command(cmd).instrument(span).await
66 }
67 Message::ControllerReady { controller: _ } => {
68 let Coordinator {
69 controller,
70 catalog,
71 ..
72 } = self;
73 let storage_metadata = catalog.state().storage_metadata();
74 if let Some(m) = controller
75 .process(storage_metadata)
76 .expect("`process` never returns an error")
77 {
78 self.message_controller(m).boxed_local().await
79 }
80 }
81 Message::PurifiedStatementReady(ready) => {
82 self.message_purified_statement_ready(ready)
83 .boxed_local()
84 .await
85 }
86 Message::CreateConnectionValidationReady(ready) => {
87 self.message_create_connection_validation_ready(ready)
88 .boxed_local()
89 .await
90 }
91 Message::AlterConnectionValidationReady(ready) => {
92 self.message_alter_connection_validation_ready(ready)
93 .boxed_local()
94 .await
95 }
96 Message::TryDeferred {
97 conn_id,
98 acquired_lock,
99 } => self.try_deferred(conn_id, acquired_lock).await,
100 Message::GroupCommitInitiate(span, permit) => {
101 tracing::Span::current().add_link(span.context().span().span_context().clone());
103 self.try_group_commit(permit)
104 .instrument(span)
105 .boxed_local()
106 .await
107 }
108 Message::AdvanceTimelines => {
109 self.advance_timelines().boxed_local().await;
110 }
111 Message::ClusterEvent(event) => self.message_cluster_event(event).boxed_local().await,
112 Message::CancelPendingPeeks { conn_id } => {
113 self.cancel_pending_peeks(&conn_id);
114 }
115 Message::LinearizeReads => {
116 self.message_linearize_reads().boxed_local().await;
117 }
118 Message::StagedBatches {
119 conn_id,
120 table_id,
121 batches,
122 } => {
123 self.commit_staged_batches(conn_id, table_id, batches);
124 }
125 Message::StorageUsageSchedule => {
126 self.schedule_storage_usage_collection().boxed_local().await;
127 }
128 Message::StorageUsageFetch => {
129 self.storage_usage_fetch().boxed_local().await;
130 }
131 Message::StorageUsageUpdate(sizes) => {
132 self.storage_usage_update(sizes).boxed_local().await;
133 }
134 Message::StorageUsagePrune(expired) => {
135 self.storage_usage_prune(expired).boxed_local().await;
136 }
137 Message::ArrangementSizesSchedule => {
138 self.schedule_arrangement_sizes_collection()
139 .boxed_local()
140 .await;
141 }
142 Message::ArrangementSizesSnapshot => {
143 self.arrangement_sizes_snapshot().boxed_local().await;
144 }
145 Message::ArrangementSizesPrune(expired) => {
146 self.arrangement_sizes_prune(expired).boxed_local().await;
147 }
148 Message::RetireExecute {
149 otel_ctx,
150 data,
151 reason,
152 } => {
153 otel_ctx.attach_as_parent();
154 self.retire_execution(reason, data);
155 }
156 Message::ExecuteSingleStatementTransaction {
157 ctx,
158 otel_ctx,
159 stmt,
160 params,
161 } => {
162 otel_ctx.attach_as_parent();
163 self.sequence_execute_single_statement_transaction(ctx, stmt, params)
164 .boxed_local()
165 .await;
166 }
167 Message::PeekStageReady { ctx, span, stage } => {
168 self.sequence_staged(ctx, span, stage).boxed_local().await;
169 }
170 Message::CreateIndexStageReady { ctx, span, stage } => {
171 self.sequence_staged(ctx, span, stage).boxed_local().await;
172 }
173 Message::CreateViewStageReady { ctx, span, stage } => {
174 self.sequence_staged(ctx, span, stage).boxed_local().await;
175 }
176 Message::CreateMaterializedViewStageReady { ctx, span, stage } => {
177 self.sequence_staged(ctx, span, stage).boxed_local().await;
178 }
179 Message::SubscribeStageReady { ctx, span, stage } => {
180 self.sequence_staged(ctx, span, stage).boxed_local().await;
181 }
182 Message::IntrospectionSubscribeStageReady { span, stage } => {
183 self.sequence_staged((), span, stage).boxed_local().await;
184 }
185 Message::ExplainTimestampStageReady { ctx, span, stage } => {
186 self.sequence_staged(ctx, span, stage).boxed_local().await;
187 }
188 Message::SecretStageReady { ctx, span, stage } => {
189 self.sequence_staged(ctx, span, stage).boxed_local().await;
190 }
191 Message::ClusterStageReady { ctx, span, stage } => {
192 self.sequence_staged(ctx, span, stage).boxed_local().await;
193 }
194 Message::DrainStatementLog => {
195 self.drain_statement_log();
196 }
197 Message::PrivateLinkVpcEndpointEvents(events) => {
198 if !self.controller.read_only() {
199 self.controller.storage.append_introspection_updates(
200 IntrospectionType::PrivatelinkConnectionStatusHistory,
201 events
202 .into_iter()
203 .map(|e| (mz_repr::Row::from(e), Diff::ONE))
204 .collect(),
205 );
206 }
207 }
208 Message::CheckSchedulingPolicies => {
209 self.check_scheduling_policies().boxed_local().await;
210 }
211 Message::SchedulingDecisions(decisions) => {
212 self.handle_scheduling_decisions(decisions)
213 .boxed_local()
214 .await;
215 }
216 Message::ClusterControllerRequest(request) => {
217 self.handle_cluster_controller_request(request)
218 .boxed_local()
219 .await;
220 }
221 Message::DeferredStatementReady => {
222 self.handle_deferred_statement().boxed_local().await;
223 }
224 }
225 }
226
227 #[mz_ore::instrument(level = "debug")]
228 pub async fn storage_usage_fetch(&self) {
229 if self.controller.read_only() {
236 tracing::info!("skipping storage usage collection in read-only mode");
237 if let Err(e) = self.internal_cmd_tx.send(Message::StorageUsageSchedule) {
238 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
239 }
240 return;
241 }
242
243 let internal_cmd_tx = self.internal_cmd_tx.clone();
244 let client = self.storage_usage_client.clone();
245
246 let live_shards: BTreeSet<_> = self
248 .controller
249 .storage
250 .active_collection_metadatas()
251 .into_iter()
252 .map(|(_id, m)| m.data_shard)
253 .collect();
254
255 let collection_metric = self.metrics.storage_usage_collection_time_seconds.clone();
256
257 task::spawn(|| "storage_usage_fetch", async move {
260 let collection_metric_timer = collection_metric.start_timer();
261 let shard_sizes = client.shards_usage_referenced(live_shards).await;
262 collection_metric_timer.observe_duration();
263
264 if let Err(e) = internal_cmd_tx.send(Message::StorageUsageUpdate(shard_sizes)) {
267 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
268 }
269 });
270 }
271
272 #[mz_ore::instrument(level = "debug")]
273 async fn storage_usage_update(&mut self, shards_usage: ShardsUsageReferenced) {
274 let write_ts = self.get_local_write_ts().await.timestamp;
282 let collection_timestamp: EpochMillis = write_ts.into();
283
284 let batch_id = match self.catalog().allocate_storage_usage_id(write_ts).await {
290 Ok(id) => id,
291 Err(err) => {
292 tracing::warn!("failed to allocate storage usage batch id: {:?}", err);
293 return;
294 }
295 };
296
297 let updates: Vec<_> = shards_usage
298 .by_shard
299 .into_iter()
300 .map(|(shard_id, shard_usage)| {
301 let event = VersionedStorageUsage::new(
302 batch_id,
303 Some(shard_id.to_string()),
304 shard_usage.size_bytes(),
305 collection_timestamp,
306 );
307 self.catalog().pack_storage_usage_update(event, Diff::ONE)
308 })
309 .collect();
310
311 let (table_updates, _) = self.builtin_table_update().execute(updates).await;
312
313 let internal_cmd_tx = self.internal_cmd_tx.clone();
314 let task_span = info_span!(parent: None, "coord::storage_usage_update::table_updates");
315 OpenTelemetryContext::obtain().attach_as_parent_to(&task_span);
316 task::spawn(|| "storage_usage_update_table_updates", async move {
317 table_updates.instrument(task_span).await;
318 if let Err(e) = internal_cmd_tx.send(Message::StorageUsageSchedule) {
320 warn!("internal_cmd_rx dropped before we could send: {e:?}");
321 }
322 });
323 }
324
325 #[mz_ore::instrument(level = "debug")]
326 async fn storage_usage_prune(&mut self, expired: Vec<BuiltinTableUpdate>) {
327 let (fut, _) = self.builtin_table_update().execute(expired).await;
328 task::spawn(|| "storage_usage_pruning_apply", async move {
329 fut.await;
330 });
331 }
332
333 pub async fn schedule_storage_usage_collection(&self) {
334 const SEED_LEN: usize = 32;
342 let mut seed = [0; SEED_LEN];
343 for (i, byte) in self
344 .catalog()
345 .state()
346 .config()
347 .environment_id
348 .organization_id()
349 .as_bytes()
350 .into_iter()
351 .take(SEED_LEN)
352 .enumerate()
353 {
354 seed[i] = *byte;
355 }
356 let storage_usage_collection_interval_ms: EpochMillis =
357 EpochMillis::try_from(self.storage_usage_collection_interval.as_millis())
358 .expect("storage usage collection interval must fit into u64");
359 let offset =
360 rngs::SmallRng::from_seed(seed).random_range(0..storage_usage_collection_interval_ms);
361 let now_ts: EpochMillis = self.peek_local_write_ts().await.into();
362
363 let previous_collection_ts =
365 (now_ts - (now_ts % storage_usage_collection_interval_ms)) + offset;
366 let next_collection_ts = if previous_collection_ts > now_ts {
367 previous_collection_ts
368 } else {
369 previous_collection_ts + storage_usage_collection_interval_ms
370 };
371 let next_collection_interval = Duration::from_millis(next_collection_ts - now_ts);
372
373 let internal_cmd_tx = self.internal_cmd_tx.clone();
375 task::spawn(|| "storage_usage_collection", async move {
376 tokio::time::sleep(next_collection_interval).await;
377 if internal_cmd_tx.send(Message::StorageUsageFetch).is_err() {
378 }
380 });
381 }
382
383 pub async fn schedule_arrangement_sizes_collection(&self) {
391 const MAX_SLEEP: Duration = Duration::from_secs(60);
392
393 let interval_duration =
394 mz_adapter_types::dyncfgs::ARRANGEMENT_SIZE_HISTORY_COLLECTION_INTERVAL
395 .get(self.catalog().system_config().dyncfgs());
396
397 if interval_duration.is_zero() {
400 let internal_cmd_tx = self.internal_cmd_tx.clone();
401 task::spawn(|| "arrangement_sizes_collection_disabled", async move {
402 tokio::time::sleep(MAX_SLEEP).await;
403 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
404 });
405 return;
406 }
407
408 const SEED_LEN: usize = 32;
409 let mut seed = [0; SEED_LEN];
410 for (i, byte) in self
411 .catalog()
412 .state()
413 .config()
414 .environment_id
415 .organization_id()
416 .as_bytes()
417 .into_iter()
418 .take(SEED_LEN)
419 .enumerate()
420 {
421 seed[i] = *byte;
422 }
423 let interval_ms: EpochMillis = EpochMillis::try_from(interval_duration.as_millis())
424 .expect("arrangement_size_history_collection_interval must fit into u64");
425 let interval_ms = interval_ms.max(1);
427 let offset = rngs::SmallRng::from_seed(seed).random_range(0..interval_ms);
428 let now_ts: EpochMillis = self.peek_local_write_ts().await.into();
429
430 let previous_collection_ts = (now_ts - (now_ts % interval_ms)) + offset;
431 let next_collection_ts = if previous_collection_ts > now_ts {
432 previous_collection_ts
433 } else {
434 previous_collection_ts + interval_ms
435 };
436 let sleep_for = Duration::from_millis(next_collection_ts - now_ts);
437
438 let (capped_sleep, fire_snapshot) = if sleep_for <= MAX_SLEEP {
442 (sleep_for, true)
443 } else {
444 (MAX_SLEEP, false)
445 };
446
447 let internal_cmd_tx = self.internal_cmd_tx.clone();
448 task::spawn(|| "arrangement_sizes_collection", async move {
449 tokio::time::sleep(capped_sleep).await;
450 let msg = if fire_snapshot {
451 Message::ArrangementSizesSnapshot
452 } else {
453 Message::ArrangementSizesSchedule
454 };
455 let _ = internal_cmd_tx.send(msg);
457 });
458 }
459
460 #[mz_ore::instrument(level = "debug")]
470 async fn arrangement_sizes_snapshot(&mut self) {
471 if self.controller.read_only() {
473 self.schedule_arrangement_sizes_collection().await;
474 return;
475 }
476
477 let collection_timer = self
478 .metrics
479 .arrangement_sizes_collection_time_seconds
480 .start_timer();
481
482 let live_item_id = self.catalog().resolve_builtin_storage_collection(
483 &mz_catalog::builtin::MZ_OBJECT_ARRANGEMENT_SIZES_UNIFIED,
484 );
485 let live_global_id = self.catalog.get_entry(&live_item_id).latest_global_id();
486 let hydration_item_id = self
487 .catalog()
488 .resolve_builtin_storage_collection(&mz_catalog::builtin::MZ_COMPUTE_HYDRATION_TIMES);
489 let hydration_global_id = self
490 .catalog
491 .get_entry(&hydration_item_id)
492 .latest_global_id();
493 let history_item_id = self
494 .catalog()
495 .resolve_builtin_table(&mz_catalog::builtin::MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY);
496
497 let read_ts = self.get_local_read_ts().await;
498 let snapshot = match self
499 .controller
500 .storage_collections
501 .snapshot(live_global_id, read_ts)
502 .await
503 {
504 Ok(s) => s,
505 Err(e) => {
506 tracing::warn!("arrangement sizes snapshot failed: {e:?}");
507 drop(collection_timer);
508 self.schedule_arrangement_sizes_collection().await;
509 return;
510 }
511 };
512 let mut hydration_snapshot = match self
513 .controller
514 .storage_collections
515 .snapshot(hydration_global_id, read_ts)
516 .await
517 {
518 Ok(s) => s,
519 Err(e) => {
520 tracing::warn!("arrangement sizes hydration snapshot failed: {e:?}");
521 drop(collection_timer);
522 self.schedule_arrangement_sizes_collection().await;
523 return;
524 }
525 };
526 differential_dataflow::consolidation::consolidate(&mut hydration_snapshot);
527
528 let mut datum_vec = mz_repr::DatumVec::new();
532 let mut hydrated: BTreeSet<(String, String)> = BTreeSet::new();
533 const HYDRATION_COL_REPLICA_ID: usize = 0;
534 const HYDRATION_COL_OBJECT_ID: usize = 1;
535 const HYDRATION_COL_TIME_NS: usize = 2;
536 const HYDRATION_COL_COUNT: usize = 3;
537 for (row, diff) in &hydration_snapshot {
538 if *diff != 1 {
539 continue;
540 }
541 let datums = datum_vec.borrow_with(row);
542 if datums.len() < HYDRATION_COL_COUNT {
543 continue;
544 }
545 if datums[HYDRATION_COL_TIME_NS].is_null() {
546 continue;
547 }
548 hydrated.insert((
549 datums[HYDRATION_COL_REPLICA_ID].unwrap_str().to_string(),
550 datums[HYDRATION_COL_OBJECT_ID].unwrap_str().to_string(),
551 ));
552 }
553
554 let collection_ts: EpochMillis = self.get_local_write_ts().await.timestamp.into();
559 let collection_datum = Datum::TimestampTz(
560 mz_ore::now::to_datetime(collection_ts)
561 .try_into()
562 .expect("collection_timestamp must fit into TimestampTz"),
563 );
564
565 let mut consolidated = snapshot;
566 differential_dataflow::consolidation::consolidate(&mut consolidated);
567
568 const LIVE_COL_REPLICA_ID: usize = 0;
570 const LIVE_COL_OBJECT_ID: usize = 1;
571 const LIVE_COL_SIZE: usize = 2;
572 const LIVE_COL_COUNT: usize = 3;
573
574 let mut skipped_malformed: u64 = 0;
575 let mut skipped_null_size: u64 = 0;
576 let mut updates: Vec<BuiltinTableUpdate> = Vec::with_capacity(consolidated.len());
577 for (row, diff) in consolidated.iter() {
578 if *diff != 1 {
579 continue;
580 }
581 let datums = datum_vec.borrow_with(row);
582 if datums.len() != LIVE_COL_COUNT {
585 skipped_malformed += 1;
586 continue;
587 }
588 let replica_id = datums[LIVE_COL_REPLICA_ID].unwrap_str();
589 let object_id = datums[LIVE_COL_OBJECT_ID].unwrap_str();
590 let size_datum = datums[LIVE_COL_SIZE];
591 if size_datum.is_null() {
594 skipped_null_size += 1;
595 continue;
596 }
597 let size = size_datum.unwrap_int64();
598 let hydration_complete =
602 hydrated.contains(&(replica_id.to_string(), object_id.to_string()));
603 let new_row = Row::pack_slice(&[
604 Datum::String(replica_id),
605 Datum::String(object_id),
606 Datum::Int64(size),
607 collection_datum,
608 Datum::from(hydration_complete),
609 ]);
610 updates.push(BuiltinTableUpdate::row(history_item_id, new_row, Diff::ONE));
611 }
612 if skipped_malformed > 0 {
613 warn!(
614 "mz_object_arrangement_sizes schema drift: skipped {skipped_malformed} rows \
615 with unexpected arity"
616 );
617 }
618 if skipped_null_size > 0 {
619 tracing::debug!("skipped {skipped_null_size} live rows with null size");
620 }
621
622 let row_count = updates.len();
623 collection_timer.observe_duration();
626
627 if !updates.is_empty() {
628 self.metrics
629 .arrangement_sizes_rows_written
630 .inc_by(u64::cast_from(row_count));
631 let (fut, _) = self.builtin_table_update().execute(updates).await;
636 let internal_cmd_tx = self.internal_cmd_tx.clone();
637 let task_span =
638 info_span!(parent: None, "coord::arrangement_sizes_snapshot::table_updates");
639 OpenTelemetryContext::obtain().attach_as_parent_to(&task_span);
640 task::spawn(|| "arrangement_sizes_snapshot_apply", async move {
641 fut.instrument(task_span).await;
642 if let Err(e) = internal_cmd_tx.send(Message::ArrangementSizesSchedule) {
643 warn!("internal_cmd_rx dropped before we could send: {e:?}");
644 }
645 });
646 } else {
647 self.schedule_arrangement_sizes_collection().await;
648 }
649
650 tracing::debug!(
651 "appended {row_count} rows to mz_object_arrangement_size_history at ts {collection_ts}"
652 );
653 }
654
655 #[mz_ore::instrument(level = "debug")]
656 async fn arrangement_sizes_prune(&mut self, expired: Vec<BuiltinTableUpdate>) {
657 let (fut, _) = self.builtin_table_update().execute(expired).await;
658 task::spawn(|| "arrangement_sizes_pruning_apply", async move {
659 fut.await;
660 });
661 }
662
663 #[mz_ore::instrument(level = "debug")]
664 async fn message_command(&mut self, cmd: Command) {
665 self.handle_command(cmd).await;
666 }
667
668 #[mz_ore::instrument(level = "debug")]
669 async fn message_controller(&mut self, message: ControllerResponse) {
670 event!(Level::TRACE, message = format!("{:?}", message));
671 match message {
672 ControllerResponse::PeekNotification(uuid, response, otel_ctx) => {
673 self.handle_peek_notification(uuid, response, otel_ctx);
674 }
675 ControllerResponse::SubscribeResponse(sink_id, response) => {
676 if let Some(ActiveComputeSink::Subscribe(active_subscribe)) =
677 self.active_compute_sinks.get_mut(&sink_id)
678 {
679 let finished = active_subscribe.process_response(response);
680 if finished {
681 let retire_notify = self
682 .retire_compute_sinks(btreemap! {
683 sink_id => ActiveComputeSinkRetireReason::Finished,
684 })
685 .await;
686 drop(retire_notify);
689 }
690
691 soft_assert_or_log!(
692 !self.introspection_subscribes.contains_key(&sink_id),
693 "`sink_id` {sink_id} unexpectedly found in both `active_subscribes` \
694 and `introspection_subscribes`",
695 );
696 } else if self.introspection_subscribes.contains_key(&sink_id) {
697 self.handle_introspection_subscribe_batch(sink_id, response)
698 .await;
699 } else {
700 }
703 }
704 ControllerResponse::CopyToResponse(sink_id, response) => {
705 match self.drop_compute_sink(sink_id).await {
706 Some((ActiveComputeSink::CopyTo(active_copy_to), _write_notify)) => {
707 active_copy_to.retire_with_response(response);
708 }
709 _ => {
710 }
713 }
714 }
715 ControllerResponse::WatchSetFinished(ws_ids) => {
716 let now = self.now();
717 for ws_id in ws_ids {
718 let Some((conn_id, rsp)) = self.installed_watch_sets.remove(&ws_id) else {
719 continue;
720 };
721 self.connection_watch_sets
722 .get_mut(&conn_id)
723 .expect("corrupted coordinator state: unknown connection id")
724 .remove(&ws_id);
725 if self.connection_watch_sets[&conn_id].is_empty() {
726 self.connection_watch_sets.remove(&conn_id);
727 }
728
729 match rsp {
730 WatchSetResponse::StatementDependenciesReady(id, ev) => {
731 self.record_statement_lifecycle_event(&id, &ev, now);
732 }
733 WatchSetResponse::AlterSinkReady(ctx) => {
734 self.sequence_alter_sink_finish(ctx).await;
735 }
736 WatchSetResponse::AlterMaterializedViewReady(ctx) => {
737 self.sequence_alter_materialized_view_apply_replacement_finish(ctx)
738 .await;
739 }
740 }
741 }
742 }
743 }
744 }
745
746 #[mz_ore::instrument(level = "debug")]
747 async fn message_purified_statement_ready(
748 &mut self,
749 PurifiedStatementReady {
750 ctx,
751 result,
752 params,
753 mut plan_validity,
754 original_stmt,
755 otel_ctx,
756 }: PurifiedStatementReady,
757 ) {
758 otel_ctx.attach_as_parent();
759
760 if plan_validity.check(self.catalog()).is_err() {
771 self.handle_execute_inner(original_stmt, params, ctx).await;
772 return;
773 }
774
775 let purified_statement = match result {
776 Ok(ok) => ok,
777 Err(e) => return ctx.retire(Err(e)),
778 };
779
780 let plan = match purified_statement {
781 PurifiedStatement::PurifiedCreateSource {
782 create_progress_subsource_stmt,
783 create_source_stmt,
784 subsources,
785 available_source_references,
786 } => self
787 .plan_purified_create_source(
788 &ctx,
789 params,
790 create_progress_subsource_stmt,
791 create_source_stmt,
792 subsources,
793 available_source_references,
794 )
795 .await
796 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
797 PurifiedStatement::PurifiedAlterSourceAddSubsources {
798 source_name,
799 options,
800 subsources,
801 } => self
802 .plan_purified_alter_source_add_subsource(
803 ctx.session(),
804 params,
805 source_name,
806 options,
807 subsources,
808 )
809 .await
810 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
811 PurifiedStatement::PurifiedAlterSourceRefreshReferences {
812 source_name,
813 available_source_references,
814 } => self
815 .plan_purified_alter_source_refresh_references(
816 ctx.session(),
817 params,
818 source_name,
819 available_source_references,
820 )
821 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
822 o @ (PurifiedStatement::PurifiedAlterSource { .. }
823 | PurifiedStatement::PurifiedCreateSink(..)
824 | PurifiedStatement::PurifiedCreateTableFromSource { .. }) => {
825 let stmt = match o {
827 PurifiedStatement::PurifiedAlterSource { alter_source_stmt } => {
828 Statement::AlterSource(alter_source_stmt)
829 }
830 PurifiedStatement::PurifiedCreateTableFromSource { stmt } => {
831 Statement::CreateTableFromSource(stmt)
832 }
833 PurifiedStatement::PurifiedCreateSink(stmt) => Statement::CreateSink(stmt),
834 PurifiedStatement::PurifiedCreateSource { .. }
835 | PurifiedStatement::PurifiedAlterSourceAddSubsources { .. }
836 | PurifiedStatement::PurifiedAlterSourceRefreshReferences { .. } => {
837 unreachable!("not part of exterior match stmt")
838 }
839 };
840
841 let catalog = self.catalog().for_session(ctx.session());
844 let resolved_ids = mz_sql::names::visit_dependencies(&catalog, &stmt);
845 self.plan_statement(ctx.session(), stmt, ¶ms, &resolved_ids)
846 .map(|(plan, sql_impl_ids)| (plan, resolved_ids, sql_impl_ids))
847 }
848 };
849
850 match plan {
851 Ok((plan, resolved_ids, sql_impl_ids)) => {
852 self.sequence_plan(ctx, plan, resolved_ids, sql_impl_ids)
853 .await
854 }
855 Err(e) => ctx.retire(Err(e)),
856 }
857 }
858
859 #[mz_ore::instrument(level = "debug")]
860 async fn message_create_connection_validation_ready(
861 &mut self,
862 CreateConnectionValidationReady {
863 mut ctx,
864 result,
865 connection_id,
866 connection_gid,
867 mut plan_validity,
868 otel_ctx,
869 resolved_ids,
870 }: CreateConnectionValidationReady,
871 ) {
872 otel_ctx.attach_as_parent();
873
874 if let Err(e) = plan_validity.check(self.catalog()) {
880 if self.secrets_controller.delete(connection_id).await.is_ok() {
881 self.caching_secrets_reader.invalidate(connection_id);
882 }
883 return ctx.retire(Err(e));
884 }
885
886 let plan = match result {
887 Ok(ok) => ok,
888 Err(e) => {
889 if self.secrets_controller.delete(connection_id).await.is_ok() {
890 self.caching_secrets_reader.invalidate(connection_id);
891 }
892 return ctx.retire(Err(e));
893 }
894 };
895
896 let result = self
897 .sequence_create_connection_stage_finish(
898 &mut ctx,
899 connection_id,
900 connection_gid,
901 plan,
902 resolved_ids,
903 )
904 .await;
905 ctx.retire(result);
906 }
907
908 #[mz_ore::instrument(level = "debug")]
909 async fn message_alter_connection_validation_ready(
910 &mut self,
911 AlterConnectionValidationReady {
912 mut ctx,
913 result,
914 connection_id,
915 connection_gid: _,
916 mut plan_validity,
917 otel_ctx,
918 resolved_ids: _,
919 }: AlterConnectionValidationReady,
920 ) {
921 otel_ctx.attach_as_parent();
922
923 if let Err(e) = plan_validity.check(self.catalog()) {
929 return ctx.retire(Err(e));
930 }
931
932 let conn = match result {
933 Ok(ok) => ok,
934 Err(e) => {
935 return ctx.retire(Err(e));
936 }
937 };
938
939 let result = self
940 .sequence_alter_connection_stage_finish(ctx.session_mut(), connection_id, conn)
941 .await;
942 ctx.retire(result);
943 }
944
945 #[mz_ore::instrument(level = "debug")]
946 async fn message_cluster_event(&mut self, event: ClusterEvent) {
947 event!(Level::TRACE, event = format!("{:?}", event));
948
949 if let Some(segment_client) = &self.segment_client {
950 let env_id = &self.catalog().config().environment_id;
951 let mut properties = json!({
952 "cluster_id": event.cluster_id.to_string(),
953 "replica_id": event.replica_id.to_string(),
954 "process_id": event.process_id,
955 "status": event.status.as_kebab_case_str(),
956 });
957 match event.status {
958 ClusterStatus::Online => (),
959 ClusterStatus::Offline(reason) => {
960 let properties = match &mut properties {
961 serde_json::Value::Object(map) => map,
962 _ => unreachable!(),
963 };
964 properties.insert(
965 "reason".into(),
966 json!(reason.display_or("unknown").to_string()),
967 );
968 }
969 };
970 segment_client.environment_track(
971 env_id,
972 "Cluster Changed Status",
973 properties,
974 EventDetails {
975 timestamp: Some(event.time),
976 ..Default::default()
977 },
978 );
979 }
980
981 let Some(replica_statuses) = self
984 .cluster_replica_statuses
985 .try_get_cluster_replica_statuses(event.cluster_id, event.replica_id)
986 else {
987 return;
988 };
989
990 let old_process_status = &replica_statuses[&event.process_id];
991 let status_changed = event.status != old_process_status.status;
992 let restart_count_changed = event.restart_count != old_process_status.restart_count;
993
994 if !status_changed && !restart_count_changed {
1006 return;
1007 }
1008
1009 if status_changed && !self.controller.read_only() {
1010 let offline_reason = match event.status {
1011 ClusterStatus::Online => None,
1012 ClusterStatus::Offline(None) => None,
1013 ClusterStatus::Offline(Some(reason)) => Some(reason.to_string()),
1014 };
1015 let row = Row::pack_slice(&[
1016 Datum::String(&event.replica_id.to_string()),
1017 Datum::UInt64(event.process_id),
1018 Datum::String(event.status.as_kebab_case_str()),
1019 Datum::from(offline_reason.as_deref()),
1020 Datum::TimestampTz(event.time.try_into().expect("must fit")),
1021 ]);
1022 self.controller.storage.append_introspection_updates(
1023 IntrospectionType::ReplicaStatusHistory,
1024 vec![(row, Diff::ONE)],
1025 );
1026 }
1027
1028 let old_replica_status = status_changed
1031 .then(|| ClusterReplicaStatuses::cluster_replica_status(replica_statuses));
1032
1033 let new_process_status = ClusterReplicaProcessStatus {
1034 status: event.status,
1035 restart_count: event.restart_count,
1036 time: event.time,
1037 };
1038 self.cluster_replica_statuses.ensure_cluster_status(
1039 event.cluster_id,
1040 event.replica_id,
1041 event.process_id,
1042 new_process_status,
1043 );
1044
1045 if let Some(old_replica_status) = old_replica_status {
1046 let cluster = self.catalog().get_cluster(event.cluster_id);
1047 let replica = cluster.replica(event.replica_id).expect("Replica exists");
1048 let new_replica_status = self
1049 .cluster_replica_statuses
1050 .get_cluster_replica_status(event.cluster_id, event.replica_id);
1051
1052 if old_replica_status != new_replica_status {
1053 let notifier = self.broadcast_notice_tx();
1054 let notice = AdapterNotice::ClusterReplicaStatusChanged {
1055 cluster: cluster.name.clone(),
1056 replica: replica.name.clone(),
1057 status: new_replica_status,
1058 time: event.time,
1059 };
1060 notifier(notice);
1061 }
1062 }
1063 }
1064
1065 #[mz_ore::instrument(level = "debug")]
1066 async fn message_linearize_reads(&mut self) {
1071 let mut shortest_wait = Duration::MAX;
1072 let mut ready_txns = Vec::new();
1073
1074 let mut cached_oracle_ts = BTreeMap::new();
1079
1080 for (conn_id, mut read_txn) in std::mem::take(&mut self.pending_linearize_read_txns) {
1081 if let TimestampContext::TimelineTimestamp {
1082 timeline,
1083 chosen_ts,
1084 oracle_ts,
1085 } = read_txn.timestamp_context()
1086 {
1087 let oracle_ts = match oracle_ts {
1088 Some(oracle_ts) => oracle_ts,
1089 None => {
1090 ready_txns.push(read_txn);
1092 continue;
1093 }
1094 };
1095
1096 if chosen_ts <= oracle_ts {
1097 ready_txns.push(read_txn);
1100 continue;
1101 }
1102
1103 let current_oracle_ts = cached_oracle_ts.entry(timeline.clone());
1105 let current_oracle_ts = match current_oracle_ts {
1106 btree_map::Entry::Vacant(entry) => {
1107 let timestamp_oracle = self.get_timestamp_oracle(timeline);
1108 let read_ts = timestamp_oracle.read_ts().await;
1109 entry.insert(read_ts.clone());
1110 read_ts
1111 }
1112 btree_map::Entry::Occupied(entry) => entry.get().clone(),
1113 };
1114
1115 if *chosen_ts <= current_oracle_ts {
1116 ready_txns.push(read_txn);
1117 } else {
1118 let wait =
1119 Duration::from_millis(chosen_ts.saturating_sub(current_oracle_ts).into());
1120 if wait < shortest_wait {
1121 shortest_wait = wait;
1122 }
1123 read_txn.num_requeues += 1;
1124 self.pending_linearize_read_txns.insert(conn_id, read_txn);
1125 }
1126 } else {
1127 ready_txns.push(read_txn);
1128 }
1129 }
1130
1131 if !ready_txns.is_empty() {
1132 let otel_ctx = ready_txns.first().expect("known to exist").otel_ctx.clone();
1135 let span = tracing::debug_span!("message_linearize_reads");
1136 otel_ctx.attach_as_parent_to(&span);
1137
1138 let now = Instant::now();
1139 for ready_txn in ready_txns {
1140 let span = tracing::debug_span!("retire_read_results");
1141 ready_txn.otel_ctx.attach_as_parent_to(&span);
1142 let _entered = span.enter();
1143 self.metrics
1144 .linearize_message_seconds
1145 .with_label_values(&[
1146 ready_txn.txn.label(),
1147 if ready_txn.num_requeues == 0 {
1148 "true"
1149 } else {
1150 "false"
1151 },
1152 ])
1153 .observe((now - ready_txn.created).as_secs_f64());
1154 if let Some((ctx, result)) = ready_txn.txn.finish() {
1155 ctx.retire(result);
1156 }
1157 }
1158 }
1159
1160 if !self.pending_linearize_read_txns.is_empty() {
1161 let remaining_ms = std::cmp::min(shortest_wait, Duration::from_millis(1_000));
1164 let linearize_reads_notify = Arc::clone(&self.linearize_reads_notify);
1165 task::spawn(|| "deferred_read_txns", async move {
1166 tokio::time::sleep(remaining_ms).await;
1167 linearize_reads_notify.notify_one();
1168 });
1169 }
1170 }
1171}