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, soft_panic_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 mz_storage_types::StorageDiff;
36use opentelemetry::trace::TraceContextExt;
37use rand::{RngExt, SeedableRng, rngs};
38use serde_json::json;
39use tracing::{Instrument, Level, event, info_span, warn};
40use tracing_opentelemetry::OpenTelemetrySpanExt;
41
42use crate::active_compute_sink::{ActiveComputeSink, ActiveComputeSinkRetireReason};
43use crate::catalog::BuiltinTableUpdate;
44use crate::command::Command;
45use crate::coord::{
46 AlterConnectionValidationReady, ArrangementSizeRecord, ClusterReplicaStatuses, Coordinator,
47 CreateConnectionValidationReady, Message, PurifiedStatementReady, WatchSetResponse,
48};
49use crate::telemetry::{EventDetails, SegmentClientExt};
50use crate::{AdapterNotice, TimestampContext};
51
52const ARRANGEMENT_SIZES_FRESHNESS_MARGIN: Duration = Duration::from_secs(10);
59
60impl Coordinator {
61 #[instrument]
66 pub(crate) async fn handle_message(&mut self, msg: Message) -> () {
67 match msg {
68 Message::Command(otel_ctx, cmd) => {
69 let span = tracing::info_span!("message_command").or_current();
73 span.in_scope(|| otel_ctx.attach_as_parent());
74 self.message_command(cmd).instrument(span).await
75 }
76 Message::ControllerReady { controller: _ } => {
77 let Coordinator {
78 controller,
79 catalog,
80 ..
81 } = self;
82 let storage_metadata = catalog.state().storage_metadata();
83 if let Some(m) = controller
84 .process(storage_metadata)
85 .expect("`process` never returns an error")
86 {
87 self.message_controller(m).boxed_local().await
88 }
89 }
90 Message::PurifiedStatementReady(ready) => {
91 self.message_purified_statement_ready(ready)
92 .boxed_local()
93 .await
94 }
95 Message::CreateConnectionValidationReady(ready) => {
96 self.message_create_connection_validation_ready(ready)
97 .boxed_local()
98 .await
99 }
100 Message::AlterConnectionValidationReady(ready) => {
101 self.message_alter_connection_validation_ready(ready)
102 .boxed_local()
103 .await
104 }
105 Message::DeferredPlanReady { conn_id } => self.sequence_deferred_plan(conn_id).await,
106 Message::GroupCommitInitiate(span, permit) => {
107 tracing::Span::current().add_link(span.context().span().span_context().clone());
109 span.in_scope(|| self.stage_group_commit(permit));
110 }
111 Message::GroupCommitApplied {
112 responses,
113 statement_logging_ids,
114 internal_results,
115 write_ts,
116 } => {
117 for id in statement_logging_ids {
120 self.set_statement_execution_timestamp(id, write_ts);
121 }
122 for response in responses {
123 let (mut ctx, result) = response.finalize();
124 ctx.session_mut().apply_write(write_ts);
125 ctx.retire(result);
126 }
127 self.downgrade_local_read_holds(write_ts);
130 self.advance_custom_timelines().boxed_local().await;
131 for result in internal_results {
132 result.send(crate::coord::appends::WriteResult::Success {
133 timestamp: write_ts,
134 });
135 }
136 }
137 Message::AdvanceTimelines => {
138 let read_ts = self.get_local_read_ts().await;
142 self.downgrade_local_read_holds(read_ts);
143 self.advance_custom_timelines().boxed_local().await;
144 }
145 Message::ClusterEvent(event) => self.message_cluster_event(event).boxed_local().await,
146 Message::LinearizeReads => {
147 self.message_linearize_reads().boxed_local().await;
148 }
149 Message::StagedBatches {
150 conn_id,
151 table_id,
152 batches,
153 } => {
154 self.commit_staged_batches(conn_id, table_id, batches);
155 }
156 Message::StorageUsageSchedule => {
157 self.schedule_storage_usage_collection().boxed_local().await;
158 }
159 Message::StorageUsageFetch => {
160 self.storage_usage_fetch().boxed_local().await;
161 }
162 Message::StorageUsageUpdate(sizes) => {
163 self.storage_usage_update(sizes).boxed_local().await;
164 }
165 Message::StorageUsagePrune(expired) => {
166 self.storage_usage_prune(expired).boxed_local().await;
167 }
168 Message::ArrangementSizesSchedule => {
169 self.schedule_arrangement_sizes_collection();
170 }
171 Message::ArrangementSizesSnapshot => {
172 self.arrangement_sizes_snapshot();
173 }
174 Message::ArrangementSizesWrite {
175 records,
176 collection_ts,
177 } => {
178 self.arrangement_sizes_write(records, collection_ts);
179 }
180 Message::ArrangementSizesPrune(expired) => {
181 self.arrangement_sizes_prune(expired);
182 }
183 Message::HydrationHistorySchedule => {
184 self.schedule_hydration_history_collection();
185 }
186 Message::HydrationHistoryRun => {
187 self.run_hydration_history_collection();
188 }
189 Message::CaughtUpCheck(request) => {
190 self.handle_caught_up_check_request(request);
191 }
192 Message::RetireExecute {
193 otel_ctx,
194 data,
195 reason,
196 } => {
197 otel_ctx.attach_as_parent();
198 self.retire_execution(reason, data);
199 }
200 Message::ExecuteSingleStatementTransaction {
201 ctx,
202 otel_ctx,
203 stmt,
204 params,
205 } => {
206 otel_ctx.attach_as_parent();
207 self.sequence_execute_single_statement_transaction(ctx, stmt, params)
208 .boxed_local()
209 .await;
210 }
211 Message::CreateIndexStageReady { ctx, span, stage } => {
212 self.sequence_staged(ctx, span, stage).boxed_local().await;
213 }
214 Message::CreateMetricSinkStageReady { ctx, span, stage } => {
215 self.sequence_staged(ctx, span, stage).boxed_local().await;
216 }
217 Message::CreateViewStageReady { ctx, span, stage } => {
218 self.sequence_staged(ctx, span, stage).boxed_local().await;
219 }
220 Message::CreateMaterializedViewStageReady { ctx, span, stage } => {
221 self.sequence_staged(ctx, span, stage).boxed_local().await;
222 }
223 Message::SubscribeStageReady { ctx, span, stage } => {
224 self.sequence_staged(ctx, span, stage).boxed_local().await;
225 }
226 Message::IntrospectionSubscribeStageReady { span, stage } => {
227 self.sequence_staged((), span, stage).boxed_local().await;
228 }
229 Message::MetricSinkStageReady { span, stage } => {
230 self.sequence_staged((), span, stage).boxed_local().await;
231 }
232 Message::ExplainTimestampStageReady { ctx, span, stage } => {
233 self.sequence_staged(ctx, span, stage).boxed_local().await;
234 }
235 Message::SecretStageReady { ctx, span, stage } => {
236 self.sequence_staged(ctx, span, stage).boxed_local().await;
237 }
238 Message::ClusterStageReady { ctx, span, stage } => {
239 self.sequence_staged(ctx, span, stage).boxed_local().await;
240 }
241 Message::DrainStatementLog => {
242 self.drain_statement_log();
243 }
244 Message::PrivateLinkVpcEndpointEvents(events) => {
245 if !self.controller.read_only() {
246 self.controller.storage.append_introspection_updates(
247 IntrospectionType::PrivatelinkConnectionStatusHistory,
248 events
249 .into_iter()
250 .map(|e| (mz_repr::Row::from(e), Diff::ONE))
251 .collect(),
252 );
253 }
254 }
255 Message::ClusterControllerRequest(request) => {
256 self.handle_cluster_controller_request(request)
257 .boxed_local()
258 .await;
259 }
260 Message::DeferredStatementReady => {
261 self.handle_deferred_statement().boxed_local().await;
262 }
263 }
264 }
265
266 #[mz_ore::instrument(level = "debug")]
267 pub async fn storage_usage_fetch(&self) {
268 if self.controller.read_only() {
275 tracing::info!("skipping storage usage collection in read-only mode");
276 if let Err(e) = self.internal_cmd_tx.send(Message::StorageUsageSchedule) {
277 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
278 }
279 return;
280 }
281
282 let internal_cmd_tx = self.internal_cmd_tx.clone();
283 let client = self.storage_usage_client.clone();
284
285 let live_shards: BTreeSet<_> = self
287 .controller
288 .storage
289 .active_collection_metadatas()
290 .into_iter()
291 .map(|(_id, m)| m.data_shard)
292 .collect();
293
294 let collection_metric = self.metrics.storage_usage_collection_time_seconds.clone();
295
296 task::spawn(|| "storage_usage_fetch", async move {
299 let collection_metric_timer = collection_metric.start_timer();
300 let shard_sizes = client.shards_usage_referenced(live_shards).await;
301 collection_metric_timer.observe_duration();
302
303 if let Err(e) = internal_cmd_tx.send(Message::StorageUsageUpdate(shard_sizes)) {
306 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
307 }
308 });
309 }
310
311 #[mz_ore::instrument(level = "debug")]
312 async fn storage_usage_update(&mut self, shards_usage: ShardsUsageReferenced) {
313 let write_ts = self.get_catalog_write_ts().await;
321 let collection_timestamp: EpochMillis = write_ts.into();
322
323 let batch_id = match self.catalog().allocate_storage_usage_id(write_ts).await {
329 Ok(id) => id,
330 Err(err) => {
331 tracing::warn!("failed to allocate storage usage batch id: {:?}", err);
332 return;
333 }
334 };
335
336 let updates: Vec<_> = shards_usage
337 .by_shard
338 .into_iter()
339 .map(|(shard_id, shard_usage)| {
340 let event = VersionedStorageUsage::new(
341 batch_id,
342 Some(shard_id.to_string()),
343 shard_usage.size_bytes(),
344 collection_timestamp,
345 );
346 self.catalog().pack_storage_usage_update(event, Diff::ONE)
347 })
348 .collect();
349
350 let table_updates = self.builtin_table_update().execute(updates);
351
352 let internal_cmd_tx = self.internal_cmd_tx.clone();
353 let task_span = info_span!(parent: None, "coord::storage_usage_update::table_updates");
354 OpenTelemetryContext::obtain().attach_as_parent_to(&task_span);
355 task::spawn(|| "storage_usage_update_table_updates", async move {
356 table_updates.instrument(task_span).await;
357 if let Err(e) = internal_cmd_tx.send(Message::StorageUsageSchedule) {
359 warn!("internal_cmd_rx dropped before we could send: {e:?}");
360 }
361 });
362 }
363
364 #[mz_ore::instrument(level = "debug")]
365 async fn storage_usage_prune(&mut self, expired: Vec<BuiltinTableUpdate>) {
366 let fut = self.builtin_table_update().execute(expired);
367 task::spawn(|| "storage_usage_pruning_apply", async move {
368 fut.await;
369 });
370 }
371
372 pub async fn schedule_storage_usage_collection(&self) {
373 const SEED_LEN: usize = 32;
381 let mut seed = [0; SEED_LEN];
382 for (i, byte) in self
383 .catalog()
384 .state()
385 .config()
386 .environment_id
387 .organization_id()
388 .as_bytes()
389 .into_iter()
390 .take(SEED_LEN)
391 .enumerate()
392 {
393 seed[i] = *byte;
394 }
395 let storage_usage_collection_interval_ms: EpochMillis =
396 EpochMillis::try_from(self.storage_usage_collection_interval.as_millis())
397 .expect("storage usage collection interval must fit into u64");
398 let offset =
399 rngs::SmallRng::from_seed(seed).random_range(0..storage_usage_collection_interval_ms);
400 let now_ts: EpochMillis = self.peek_local_write_ts().await.into();
401
402 let previous_collection_ts =
404 (now_ts - (now_ts % storage_usage_collection_interval_ms)) + offset;
405 let next_collection_ts = if previous_collection_ts > now_ts {
406 previous_collection_ts
407 } else {
408 previous_collection_ts + storage_usage_collection_interval_ms
409 };
410 let next_collection_interval = Duration::from_millis(next_collection_ts - now_ts);
411
412 let internal_cmd_tx = self.internal_cmd_tx.clone();
414 task::spawn(|| "storage_usage_collection", async move {
415 tokio::time::sleep(next_collection_interval).await;
416 if internal_cmd_tx.send(Message::StorageUsageFetch).is_err() {
417 }
419 });
420 }
421
422 pub fn schedule_arrangement_sizes_collection(&self) {
430 const MAX_SLEEP: Duration = Duration::from_secs(60);
431
432 let interval_duration =
433 mz_adapter_types::dyncfgs::ARRANGEMENT_SIZE_HISTORY_COLLECTION_INTERVAL
434 .get(self.catalog().system_config().dyncfgs());
435
436 if interval_duration.is_zero() {
439 let internal_cmd_tx = self.internal_cmd_tx.clone();
440 task::spawn(|| "arrangement_sizes_collection_disabled", async move {
441 tokio::time::sleep(MAX_SLEEP).await;
442 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
443 });
444 return;
445 }
446
447 const SEED_LEN: usize = 32;
448 let mut seed = [0; SEED_LEN];
449 for (i, byte) in self
450 .catalog()
451 .state()
452 .config()
453 .environment_id
454 .organization_id()
455 .as_bytes()
456 .into_iter()
457 .take(SEED_LEN)
458 .enumerate()
459 {
460 seed[i] = *byte;
461 }
462 let interval_ms: EpochMillis = EpochMillis::try_from(interval_duration.as_millis())
463 .expect("arrangement_size_history_collection_interval must fit into u64");
464 let interval_ms = interval_ms.max(1);
466 let offset = rngs::SmallRng::from_seed(seed).random_range(0..interval_ms);
467
468 let oracle = self.get_local_timestamp_oracle();
471 let internal_cmd_tx = self.internal_cmd_tx.clone();
472 task::spawn(|| "arrangement_sizes_collection", async move {
473 let now_ts: EpochMillis = oracle.peek_write_ts().await.into();
474
475 let previous_collection_ts = (now_ts - (now_ts % interval_ms)) + offset;
476 let next_collection_ts = if previous_collection_ts > now_ts {
477 previous_collection_ts
478 } else {
479 previous_collection_ts + interval_ms
480 };
481 let sleep_for = Duration::from_millis(next_collection_ts - now_ts);
482
483 let (capped_sleep, fire_snapshot) = if sleep_for <= MAX_SLEEP {
487 (sleep_for, true)
488 } else {
489 (MAX_SLEEP, false)
490 };
491
492 tokio::time::sleep(capped_sleep).await;
493 let msg = if fire_snapshot {
494 Message::ArrangementSizesSnapshot
495 } else {
496 Message::ArrangementSizesSchedule
497 };
498 let _ = internal_cmd_tx.send(msg);
500 });
501 }
502
503 #[mz_ore::instrument(level = "debug")]
517 fn arrangement_sizes_snapshot(&self) {
518 if self.controller.read_only() {
524 self.schedule_arrangement_sizes_collection();
525 return;
526 }
527
528 let fresh_size_replicas = self.fresh_introspection_replicas(
529 IntrospectionType::ComputeObjectArrangementSizes,
530 ARRANGEMENT_SIZES_FRESHNESS_MARGIN,
531 );
532 let fresh_hydration_replicas = self.fresh_introspection_replicas(
533 IntrospectionType::ComputeHydrationTimes,
534 ARRANGEMENT_SIZES_FRESHNESS_MARGIN,
535 );
536 if fresh_size_replicas.is_empty() {
537 self.schedule_arrangement_sizes_collection();
540 return;
541 }
542
543 let live_item_id = self.catalog().resolve_builtin_storage_collection(
544 &mz_catalog::builtin::MZ_OBJECT_ARRANGEMENT_SIZES_UNIFIED,
545 );
546 let live_global_id = self.catalog.get_entry(&live_item_id).latest_global_id();
547 let hydration_item_id = self
548 .catalog()
549 .resolve_builtin_storage_collection(&mz_catalog::builtin::MZ_COMPUTE_HYDRATION_TIMES);
550 let hydration_global_id = self
551 .catalog
552 .get_entry(&hydration_item_id)
553 .latest_global_id();
554
555 let oracle = self.get_local_timestamp_oracle();
556 let storage_collections = Arc::clone(&self.controller.storage_collections);
557 let collection_metric = self
558 .metrics
559 .arrangement_sizes_collection_time_seconds
560 .clone();
561 let internal_cmd_tx = self.internal_cmd_tx.clone();
562
563 task::spawn(|| "arrangement_sizes_snapshot", async move {
564 let collection_metric_timer = collection_metric.start_timer();
565
566 let read_ts = oracle.read_ts().await;
572 let live_snapshot = match storage_collections.snapshot(live_global_id, read_ts).await {
573 Ok(s) => s,
574 Err(e) => {
575 soft_panic_or_log!("arrangement sizes snapshot failed: {e:?}");
579 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
580 return;
581 }
582 };
583 let hydration_snapshot = match storage_collections
584 .snapshot(hydration_global_id, read_ts)
585 .await
586 {
587 Ok(s) => s,
588 Err(e) => {
589 soft_panic_or_log!("arrangement sizes hydration snapshot failed: {e:?}");
590 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
591 return;
592 }
593 };
594
595 let records = arrangement_sizes_records(
596 live_snapshot,
597 hydration_snapshot,
598 &fresh_size_replicas,
599 &fresh_hydration_replicas,
600 );
601 collection_metric_timer.observe_duration();
602
603 let msg = if records.is_empty() {
604 Message::ArrangementSizesSchedule
605 } else {
606 Message::ArrangementSizesWrite {
612 records,
613 collection_ts: read_ts.into(),
614 }
615 };
616 let _ = internal_cmd_tx.send(msg);
618 });
619 }
620
621 #[mz_ore::instrument(level = "debug")]
626 fn arrangement_sizes_write(
627 &mut self,
628 records: Vec<ArrangementSizeRecord>,
629 collection_ts: EpochMillis,
630 ) {
631 let fresh_size_replicas = self.fresh_introspection_replicas(
635 IntrospectionType::ComputeObjectArrangementSizes,
636 ARRANGEMENT_SIZES_FRESHNESS_MARGIN,
637 );
638 let records: Vec<_> = records
639 .into_iter()
640 .filter(|record| fresh_size_replicas.contains(&record.replica_id))
641 .collect();
642 if records.is_empty() {
643 self.schedule_arrangement_sizes_collection();
644 return;
645 }
646
647 let collection_datum = Datum::TimestampTz(
648 mz_ore::now::to_datetime(collection_ts)
649 .try_into()
650 .expect("collection_timestamp must fit into TimestampTz"),
651 );
652
653 let history_item_id = self
654 .catalog()
655 .resolve_builtin_table(&mz_catalog::builtin::MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY);
656
657 let updates: Vec<_> = records
658 .into_iter()
659 .map(|record| {
660 let row = Row::pack_slice(&[
661 Datum::String(&record.replica_id),
662 Datum::String(&record.object_id),
663 Datum::Int64(record.size),
664 collection_datum,
665 Datum::from(record.hydration_complete),
666 ]);
667 BuiltinTableUpdate::row(history_item_id, row, Diff::ONE)
668 })
669 .collect();
670
671 let row_count = updates.len();
672 self.metrics
673 .arrangement_sizes_rows_written
674 .inc_by(u64::cast_from(row_count));
675
676 let fut = self.builtin_table_update().execute(updates);
681 let internal_cmd_tx = self.internal_cmd_tx.clone();
682 let task_span = info_span!(parent: None, "coord::arrangement_sizes_write::table_updates");
683 OpenTelemetryContext::obtain().attach_as_parent_to(&task_span);
684 task::spawn(|| "arrangement_sizes_write_table_updates", async move {
685 fut.instrument(task_span).await;
686 if let Err(e) = internal_cmd_tx.send(Message::ArrangementSizesSchedule) {
687 warn!("internal_cmd_rx dropped before we could send: {e:?}");
688 }
689 });
690
691 tracing::debug!(
692 "appended {row_count} rows to mz_object_arrangement_size_history at ts {collection_ts}"
693 );
694 }
695
696 #[mz_ore::instrument(level = "debug")]
697 fn arrangement_sizes_prune(&mut self, expired: Vec<BuiltinTableUpdate>) {
698 let fut = self.builtin_table_update().execute(expired);
699 task::spawn(|| "arrangement_sizes_pruning_apply", async move {
700 fut.await;
701 });
702 }
703
704 #[mz_ore::instrument(level = "debug")]
705 async fn message_command(&mut self, cmd: Command) {
706 self.handle_command(cmd).await;
707 }
708
709 #[mz_ore::instrument(level = "debug")]
710 async fn message_controller(&mut self, message: ControllerResponse) {
711 event!(Level::TRACE, message = format!("{:?}", message));
712 match message {
713 ControllerResponse::PeekNotification(uuid, response, otel_ctx) => {
714 self.handle_peek_notification(uuid, response, otel_ctx);
715 }
716 ControllerResponse::SubscribeResponse(sink_id, response) => {
717 if let Some(ActiveComputeSink::Subscribe(active_subscribe)) =
718 self.active_compute_sinks.get_mut(&sink_id)
719 {
720 let finished = active_subscribe.process_response(response);
721 let buffered_bytes = active_subscribe
732 .backlog_accounting
733 .lock()
734 .expect("subscribe backlog accounting poisoned")
735 .backlog_size();
736 let max_buffered_bytes = active_subscribe.max_buffered_bytes;
737
738 let reason = if finished {
739 Some(ActiveComputeSinkRetireReason::Finished)
740 } else if buffered_bytes > max_buffered_bytes {
741 Some(ActiveComputeSinkRetireReason::BufferExceeded {
742 buffered_bytes,
743 max_buffered_bytes,
744 })
745 } else {
746 None
747 };
748 if let Some(reason) = reason {
749 let retire_notify = self
750 .retire_compute_sinks(btreemap! {
751 sink_id => reason,
752 })
753 .await;
754 drop(retire_notify);
757 }
758
759 soft_assert_or_log!(
760 !self.introspection_subscribes.contains_key(&sink_id),
761 "`sink_id` {sink_id} unexpectedly found in both `active_subscribes` \
762 and `introspection_subscribes`",
763 );
764 } else if self.introspection_subscribes.contains_key(&sink_id) {
765 self.handle_introspection_subscribe_batch(sink_id, response)
766 .await;
767 } else {
768 }
771 }
772 ControllerResponse::CopyToResponse(sink_id, response) => {
773 match self.drop_compute_sink(sink_id).await {
774 Some((ActiveComputeSink::CopyTo(active_copy_to), _write_notify)) => {
775 active_copy_to.retire_with_response(response);
776 }
777 _ => {
778 }
781 }
782 }
783 ControllerResponse::WatchSetFinished(ws_ids) => {
784 let now = self.now();
785 for ws_id in ws_ids {
786 let Some((conn_id, rsp)) = self.installed_watch_sets.remove(&ws_id) else {
787 continue;
788 };
789 self.connection_watch_sets
790 .get_mut(&conn_id)
791 .expect("corrupted coordinator state: unknown connection id")
792 .remove(&ws_id);
793 if self.connection_watch_sets[&conn_id].is_empty() {
794 self.connection_watch_sets.remove(&conn_id);
795 }
796
797 match rsp {
798 WatchSetResponse::StatementDependenciesReady(id, ev) => {
799 self.record_statement_lifecycle_event(&id, &ev, now);
800 }
801 WatchSetResponse::AlterSinkReady(ctx) => {
802 self.sequence_alter_sink_finish(ctx).await;
803 }
804 WatchSetResponse::AlterMaterializedViewReady(ctx) => {
805 self.sequence_alter_materialized_view_apply_replacement_finish(ctx)
806 .await;
807 }
808 }
809 }
810 }
811 }
812 }
813
814 #[mz_ore::instrument(level = "debug")]
815 async fn message_purified_statement_ready(
816 &mut self,
817 PurifiedStatementReady {
818 ctx,
819 result,
820 params,
821 mut plan_validity,
822 original_stmt,
823 otel_ctx,
824 }: PurifiedStatementReady,
825 ) {
826 otel_ctx.attach_as_parent();
827
828 if plan_validity.check(self.catalog()).is_err() {
839 self.handle_execute_inner(original_stmt, params, ctx).await;
840 return;
841 }
842
843 let purified_statement = match result {
844 Ok(ok) => ok,
845 Err(e) => return ctx.retire(Err(e)),
846 };
847
848 let plan = match purified_statement {
849 PurifiedStatement::PurifiedCreateSource {
850 create_progress_subsource_stmt,
851 create_source_stmt,
852 subsources,
853 available_source_references,
854 } => self
855 .plan_purified_create_source(
856 &ctx,
857 params,
858 create_progress_subsource_stmt,
859 create_source_stmt,
860 subsources,
861 available_source_references,
862 )
863 .await
864 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
865 PurifiedStatement::PurifiedAlterSourceAddSubsources {
866 source_name,
867 options,
868 subsources,
869 } => self
870 .plan_purified_alter_source_add_subsource(
871 ctx.session(),
872 params,
873 source_name,
874 options,
875 subsources,
876 )
877 .await
878 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
879 PurifiedStatement::PurifiedAlterSourceRefreshReferences {
880 source_name,
881 available_source_references,
882 } => self
883 .plan_purified_alter_source_refresh_references(
884 ctx.session(),
885 params,
886 source_name,
887 available_source_references,
888 )
889 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
890 o @ (PurifiedStatement::PurifiedAlterSource { .. }
891 | PurifiedStatement::PurifiedCreateSink(..)
892 | PurifiedStatement::PurifiedCreateTableFromSource { .. }) => {
893 let stmt = match o {
895 PurifiedStatement::PurifiedAlterSource { alter_source_stmt } => {
896 Statement::AlterSource(alter_source_stmt)
897 }
898 PurifiedStatement::PurifiedCreateTableFromSource { stmt } => {
899 Statement::CreateTableFromSource(stmt)
900 }
901 PurifiedStatement::PurifiedCreateSink(stmt) => Statement::CreateSink(stmt),
902 PurifiedStatement::PurifiedCreateSource { .. }
903 | PurifiedStatement::PurifiedAlterSourceAddSubsources { .. }
904 | PurifiedStatement::PurifiedAlterSourceRefreshReferences { .. } => {
905 unreachable!("not part of exterior match stmt")
906 }
907 };
908
909 let catalog = self.catalog().for_session(ctx.session());
912 let resolved_ids = mz_sql::names::visit_dependencies(&catalog, &stmt);
913 self.plan_statement(ctx.session(), stmt, ¶ms, &resolved_ids)
914 .map(|(plan, sql_impl_ids)| (plan, resolved_ids, sql_impl_ids))
915 }
916 };
917
918 match plan {
919 Ok((plan, resolved_ids, sql_impl_ids)) => {
920 self.sequence_plan(ctx, plan, resolved_ids, sql_impl_ids)
921 .await
922 }
923 Err(e) => ctx.retire(Err(e)),
924 }
925 }
926
927 #[mz_ore::instrument(level = "debug")]
928 async fn message_create_connection_validation_ready(
929 &mut self,
930 CreateConnectionValidationReady {
931 mut ctx,
932 result,
933 connection_id,
934 connection_gid,
935 mut plan_validity,
936 otel_ctx,
937 resolved_ids,
938 }: CreateConnectionValidationReady,
939 ) {
940 otel_ctx.attach_as_parent();
941
942 if let Err(e) = plan_validity.check(self.catalog()) {
948 if self.secrets_controller.delete(connection_id).await.is_ok() {
949 self.caching_secrets_reader.invalidate(connection_id);
950 }
951 return ctx.retire(Err(e));
952 }
953
954 let plan = match result {
955 Ok(ok) => ok,
956 Err(e) => {
957 if self.secrets_controller.delete(connection_id).await.is_ok() {
958 self.caching_secrets_reader.invalidate(connection_id);
959 }
960 return ctx.retire(Err(e));
961 }
962 };
963
964 let result = self
965 .sequence_create_connection_stage_finish(
966 &mut ctx,
967 connection_id,
968 connection_gid,
969 plan,
970 resolved_ids,
971 )
972 .await;
973 ctx.retire(result);
974 }
975
976 #[mz_ore::instrument(level = "debug")]
977 async fn message_alter_connection_validation_ready(
978 &mut self,
979 AlterConnectionValidationReady {
980 mut ctx,
981 result,
982 connection_id,
983 connection_gid: _,
984 mut plan_validity,
985 otel_ctx,
986 resolved_ids: _,
987 }: AlterConnectionValidationReady,
988 ) {
989 otel_ctx.attach_as_parent();
990
991 if let Err(e) = plan_validity.check(self.catalog()) {
997 return ctx.retire(Err(e));
998 }
999
1000 let conn = match result {
1001 Ok(ok) => ok,
1002 Err(e) => {
1003 return ctx.retire(Err(e));
1004 }
1005 };
1006
1007 let result = self
1008 .sequence_alter_connection_stage_finish(ctx.session_mut(), connection_id, conn)
1009 .await;
1010 ctx.retire(result);
1011 }
1012
1013 #[mz_ore::instrument(level = "debug")]
1014 async fn message_cluster_event(&mut self, event: ClusterEvent) {
1015 event!(Level::TRACE, event = format!("{:?}", event));
1016
1017 if let Some(segment_client) = &self.segment_client {
1018 let env_id = &self.catalog().config().environment_id;
1019 let mut properties = json!({
1020 "cluster_id": event.cluster_id.to_string(),
1021 "replica_id": event.replica_id.to_string(),
1022 "process_id": event.process_id,
1023 "status": event.status.as_kebab_case_str(),
1024 });
1025 match event.status {
1026 ClusterStatus::Online => (),
1027 ClusterStatus::Offline(reason) => {
1028 let properties = match &mut properties {
1029 serde_json::Value::Object(map) => map,
1030 _ => unreachable!(),
1031 };
1032 properties.insert(
1033 "reason".into(),
1034 json!(reason.display_or("unknown").to_string()),
1035 );
1036 }
1037 };
1038 segment_client.environment_track(
1039 env_id,
1040 "Cluster Changed Status",
1041 properties,
1042 EventDetails {
1043 timestamp: Some(event.time),
1044 ..Default::default()
1045 },
1046 );
1047 }
1048
1049 let Some(replica_statuses) = self
1052 .cluster_replica_statuses
1053 .try_get_cluster_replica_statuses(event.cluster_id, event.replica_id)
1054 else {
1055 return;
1056 };
1057
1058 let old_process_status = &replica_statuses[&event.process_id];
1059 let status_changed = event.status != old_process_status.status;
1060 let restart_count_changed = event.restart_count != old_process_status.restart_count;
1061
1062 if !status_changed && !restart_count_changed {
1074 return;
1075 }
1076
1077 if status_changed && !self.controller.read_only() {
1078 let offline_reason = match event.status {
1079 ClusterStatus::Online => None,
1080 ClusterStatus::Offline(None) => None,
1081 ClusterStatus::Offline(Some(reason)) => Some(reason.to_string()),
1082 };
1083 let row = Row::pack_slice(&[
1084 Datum::String(&event.replica_id.to_string()),
1085 Datum::UInt64(event.process_id),
1086 Datum::String(event.status.as_kebab_case_str()),
1087 Datum::from(offline_reason.as_deref()),
1088 Datum::TimestampTz(event.time.try_into().expect("must fit")),
1089 ]);
1090 self.controller.storage.append_introspection_updates(
1091 IntrospectionType::ReplicaStatusHistory,
1092 vec![(row, Diff::ONE)],
1093 );
1094 }
1095
1096 let old_replica_status = status_changed
1099 .then(|| ClusterReplicaStatuses::cluster_replica_status(replica_statuses));
1100
1101 let new_process_status = ClusterReplicaProcessStatus {
1102 status: event.status,
1103 restart_count: event.restart_count,
1104 time: event.time,
1105 };
1106 self.cluster_replica_statuses.ensure_cluster_status(
1107 event.cluster_id,
1108 event.replica_id,
1109 event.process_id,
1110 new_process_status,
1111 );
1112
1113 if !matches!(event.status, ClusterStatus::Online) || restart_count_changed {
1118 self.invalidate_introspection_freshness(event.replica_id);
1119 }
1120
1121 if let Some(old_replica_status) = old_replica_status {
1122 let cluster = self.catalog().get_cluster(event.cluster_id);
1123 let replica = cluster.replica(event.replica_id).expect("Replica exists");
1124 let new_replica_status = self
1125 .cluster_replica_statuses
1126 .get_cluster_replica_status(event.cluster_id, event.replica_id);
1127
1128 if old_replica_status != new_replica_status {
1129 let notifier = self.broadcast_notice_tx();
1130 let notice = AdapterNotice::ClusterReplicaStatusChanged {
1131 cluster: cluster.name.clone(),
1132 replica: replica.name.clone(),
1133 status: new_replica_status,
1134 time: event.time,
1135 };
1136 notifier(notice);
1137 }
1138 }
1139 }
1140
1141 #[mz_ore::instrument(level = "debug")]
1142 async fn message_linearize_reads(&mut self) {
1147 let mut shortest_wait = Duration::MAX;
1148 let mut ready_txns = Vec::new();
1149
1150 let mut cached_oracle_ts = BTreeMap::new();
1155
1156 for (conn_id, mut read_txn) in std::mem::take(&mut self.pending_linearize_read_txns) {
1157 if let TimestampContext::TimelineTimestamp {
1158 timeline,
1159 chosen_ts,
1160 oracle_ts,
1161 } = read_txn.timestamp_context()
1162 {
1163 let oracle_ts = match oracle_ts {
1164 Some(oracle_ts) => oracle_ts,
1165 None => {
1166 ready_txns.push(read_txn);
1168 continue;
1169 }
1170 };
1171
1172 if chosen_ts <= oracle_ts {
1173 ready_txns.push(read_txn);
1176 continue;
1177 }
1178
1179 let current_oracle_ts = cached_oracle_ts.entry(timeline.clone());
1181 let current_oracle_ts = match current_oracle_ts {
1182 btree_map::Entry::Vacant(entry) => {
1183 let timestamp_oracle = self.get_timestamp_oracle(timeline);
1184 let read_ts = timestamp_oracle.read_ts().await;
1185 entry.insert(read_ts.clone());
1186 read_ts
1187 }
1188 btree_map::Entry::Occupied(entry) => entry.get().clone(),
1189 };
1190
1191 if *chosen_ts <= current_oracle_ts {
1192 ready_txns.push(read_txn);
1193 } else {
1194 let wait =
1195 Duration::from_millis(chosen_ts.saturating_sub(current_oracle_ts).into());
1196 if wait < shortest_wait {
1197 shortest_wait = wait;
1198 }
1199 read_txn.num_requeues += 1;
1200 self.pending_linearize_read_txns.insert(conn_id, read_txn);
1201 }
1202 } else {
1203 ready_txns.push(read_txn);
1204 }
1205 }
1206
1207 if !ready_txns.is_empty() {
1208 let otel_ctx = ready_txns.first().expect("known to exist").otel_ctx.clone();
1211 let span = tracing::debug_span!("message_linearize_reads");
1212 otel_ctx.attach_as_parent_to(&span);
1213
1214 let now = Instant::now();
1215 for ready_txn in ready_txns {
1216 let span = tracing::debug_span!("retire_read_results");
1217 ready_txn.otel_ctx.attach_as_parent_to(&span);
1218 let _entered = span.enter();
1219 self.metrics
1220 .linearize_message_seconds
1221 .with_label_values(&[
1222 "read",
1223 if ready_txn.num_requeues == 0 {
1224 "true"
1225 } else {
1226 "false"
1227 },
1228 ])
1229 .observe((now - ready_txn.created).as_secs_f64());
1230 ready_txn.finish();
1231 }
1232 }
1233
1234 if !self.pending_linearize_read_txns.is_empty() {
1235 let remaining_ms = std::cmp::min(shortest_wait, Duration::from_millis(1_000));
1238 let linearize_reads_notify = Arc::clone(&self.linearize_reads_notify);
1239 task::spawn(|| "deferred_read_txns", async move {
1240 tokio::time::sleep(remaining_ms).await;
1241 linearize_reads_notify.notify_one();
1242 });
1243 }
1244 }
1245}
1246
1247fn arrangement_sizes_records(
1263 mut live_snapshot: Vec<(Row, StorageDiff)>,
1264 mut hydration_snapshot: Vec<(Row, StorageDiff)>,
1265 fresh_size_replicas: &BTreeSet<String>,
1266 fresh_hydration_replicas: &BTreeSet<String>,
1267) -> Vec<ArrangementSizeRecord> {
1268 differential_dataflow::consolidation::consolidate(&mut live_snapshot);
1269 differential_dataflow::consolidation::consolidate(&mut hydration_snapshot);
1270
1271 let mut datum_vec = mz_repr::DatumVec::new();
1272
1273 const HYDRATION_COL_REPLICA_ID: usize = 0;
1275 const HYDRATION_COL_OBJECT_ID: usize = 1;
1276 const HYDRATION_COL_TIME_NS: usize = 2;
1277 const HYDRATION_COL_COUNT: usize = 3;
1278
1279 let mut hydrated: BTreeSet<(String, String)> = BTreeSet::new();
1280 for (row, diff) in &hydration_snapshot {
1281 if *diff != 1 {
1282 continue;
1283 }
1284 let datums = datum_vec.borrow_with(row);
1285 if datums.len() < HYDRATION_COL_COUNT {
1286 continue;
1287 }
1288 if datums[HYDRATION_COL_TIME_NS].is_null() {
1289 continue;
1290 }
1291 let replica_id = datums[HYDRATION_COL_REPLICA_ID].unwrap_str();
1292 if !fresh_hydration_replicas.contains(replica_id) {
1293 continue;
1294 }
1295 hydrated.insert((
1296 replica_id.to_string(),
1297 datums[HYDRATION_COL_OBJECT_ID].unwrap_str().to_string(),
1298 ));
1299 }
1300
1301 const LIVE_COL_REPLICA_ID: usize = 0;
1303 const LIVE_COL_OBJECT_ID: usize = 1;
1304 const LIVE_COL_SIZE: usize = 2;
1305 const LIVE_COL_COUNT: usize = 3;
1306
1307 let mut skipped_malformed: u64 = 0;
1308 let mut skipped_null_size: u64 = 0;
1309 let mut skipped_zero_size: u64 = 0;
1310 let mut skipped_stale_replica: u64 = 0;
1311 let mut records = Vec::with_capacity(live_snapshot.len());
1312 for (row, diff) in &live_snapshot {
1313 if *diff != 1 {
1314 continue;
1315 }
1316 let datums = datum_vec.borrow_with(row);
1317 if datums.len() != LIVE_COL_COUNT {
1320 skipped_malformed += 1;
1321 continue;
1322 }
1323 let replica_id = datums[LIVE_COL_REPLICA_ID].unwrap_str();
1324 if !fresh_size_replicas.contains(replica_id) {
1325 skipped_stale_replica += 1;
1326 continue;
1327 }
1328 let object_id = datums[LIVE_COL_OBJECT_ID].unwrap_str();
1329 let size_datum = datums[LIVE_COL_SIZE];
1330 if size_datum.is_null() {
1333 skipped_null_size += 1;
1334 continue;
1335 }
1336 if size_datum.unwrap_int64() == 0 {
1340 skipped_zero_size += 1;
1341 continue;
1342 }
1343 let hydration_complete =
1344 hydrated.contains(&(replica_id.to_string(), object_id.to_string()));
1345 records.push(ArrangementSizeRecord {
1346 replica_id: replica_id.to_string(),
1347 object_id: object_id.to_string(),
1348 size: size_datum.unwrap_int64(),
1349 hydration_complete,
1350 });
1351 }
1352 if skipped_malformed > 0 {
1353 warn!(
1354 "mz_object_arrangement_sizes schema drift: skipped {skipped_malformed} rows \
1355 with unexpected arity"
1356 );
1357 }
1358 if skipped_null_size > 0 {
1359 tracing::debug!("skipped {skipped_null_size} live rows with null size");
1360 }
1361 if skipped_zero_size > 0 {
1362 tracing::debug!("skipped {skipped_zero_size} live rows with zero size");
1363 }
1364 if skipped_stale_replica > 0 {
1365 tracing::debug!(
1366 "skipped {skipped_stale_replica} live rows from replicas without fresh \
1367 introspection data"
1368 );
1369 }
1370 records
1371}
1372
1373#[cfg(test)]
1374mod arrangement_sizes_records_tests {
1375 use std::collections::BTreeSet;
1376
1377 use mz_repr::{Datum, Row};
1378
1379 use super::arrangement_sizes_records;
1380
1381 fn live_row(replica_id: &str, object_id: &str, size: Option<i64>) -> Row {
1382 Row::pack_slice(&[
1383 Datum::String(replica_id),
1384 Datum::String(object_id),
1385 size.map_or(Datum::Null, Datum::Int64),
1386 ])
1387 }
1388
1389 fn hydration_row(replica_id: &str, object_id: &str, hydrated: bool) -> Row {
1390 Row::pack_slice(&[
1391 Datum::String(replica_id),
1392 Datum::String(object_id),
1393 if hydrated {
1394 Datum::UInt64(1)
1395 } else {
1396 Datum::Null
1397 },
1398 ])
1399 }
1400
1401 fn replicas(ids: &[&str]) -> BTreeSet<String> {
1402 ids.iter().map(|id| id.to_string()).collect()
1403 }
1404
1405 #[mz_ore::test]
1406 fn hydration_flag_per_pair() {
1407 let live = vec![
1408 (live_row("u1", "u100", Some(10)), 1),
1409 (live_row("u1", "u200", Some(20)), 1),
1410 ];
1411 let hydration = vec![
1412 (hydration_row("u1", "u100", true), 1),
1413 (hydration_row("u1", "u200", false), 1),
1414 ];
1415 let fresh = replicas(&["u1"]);
1416 let records = arrangement_sizes_records(live, hydration, &fresh, &fresh);
1417 assert_eq!(records.len(), 2);
1418 assert!(
1419 records
1420 .iter()
1421 .any(|r| r.object_id == "u100" && r.hydration_complete)
1422 );
1423 assert!(
1424 records
1425 .iter()
1426 .any(|r| r.object_id == "u200" && !r.hydration_complete)
1427 );
1428 }
1429
1430 #[mz_ore::test]
1431 fn skips_malformed_null_and_retracted() {
1432 let live = vec![
1433 (Row::pack_slice(&[Datum::String("u1")]), 1),
1435 (live_row("u1", "u100", None), 1),
1437 (live_row("u1", "u200", Some(20)), 1),
1439 (live_row("u1", "u200", Some(20)), -1),
1440 (live_row("u1", "u300", Some(30)), 1),
1441 ];
1442 let fresh = replicas(&["u1"]);
1443 let records = arrangement_sizes_records(live, Vec::new(), &fresh, &fresh);
1444 assert_eq!(records.len(), 1);
1445 assert_eq!(records[0].object_id, "u300");
1446 assert_eq!(records[0].size, 30);
1447 assert!(!records[0].hydration_complete);
1448 }
1449
1450 #[mz_ore::test]
1451 fn skips_zero_size_rows() {
1452 let live = vec![
1455 (live_row("u1", "u100", Some(0)), 1),
1456 (live_row("u1", "u200", Some(10485760)), 1),
1457 ];
1458 let fresh = replicas(&["u1"]);
1459 let records = arrangement_sizes_records(live, Vec::new(), &fresh, &fresh);
1460 assert_eq!(records.len(), 1);
1461 assert_eq!(records[0].object_id, "u200");
1462 }
1463
1464 #[mz_ore::test]
1465 fn skips_rows_from_stale_replicas() {
1466 let live = vec![
1468 (live_row("u1", "u100", Some(10)), 1),
1469 (live_row("u2", "u100", Some(99)), 1),
1470 ];
1471 let hydration = vec![
1472 (hydration_row("u1", "u100", true), 1),
1473 (hydration_row("u2", "u100", true), 1),
1474 ];
1475 let fresh = replicas(&["u1"]);
1476 let records = arrangement_sizes_records(live, hydration, &fresh, &fresh);
1477 assert_eq!(records.len(), 1);
1478 assert_eq!(records[0].replica_id, "u1");
1479 assert!(records[0].hydration_complete);
1480 }
1481
1482 #[mz_ore::test]
1483 fn stale_hydration_data_is_not_trusted() {
1484 let live = vec![(live_row("u1", "u100", Some(10)), 1)];
1487 let hydration = vec![(hydration_row("u1", "u100", true), 1)];
1488 let records =
1489 arrangement_sizes_records(live, hydration, &replicas(&["u1"]), &replicas(&[]));
1490 assert_eq!(records.len(), 1);
1491 assert!(!records[0].hydration_complete);
1492 }
1493}