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::{Rng, 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::TryDeferred {
106 conn_id,
107 acquired_lock,
108 } => self.try_deferred(conn_id, acquired_lock).await,
109 Message::GroupCommitInitiate(span, permit) => {
110 tracing::Span::current().add_link(span.context().span().span_context().clone());
112 span.in_scope(|| self.stage_group_commit(permit));
113 }
114 Message::GroupCommitApplied {
115 responses,
116 statement_logging_ids,
117 internal_results,
118 write_ts,
119 } => {
120 for id in statement_logging_ids {
123 self.set_statement_execution_timestamp(id, write_ts);
124 }
125 for response in responses {
126 let (mut ctx, result) = response.finalize();
127 ctx.session_mut().apply_write(write_ts);
128 ctx.retire(result);
129 }
130 self.downgrade_local_read_holds(write_ts);
133 self.advance_custom_timelines().boxed_local().await;
134 for result in internal_results {
135 result.send(crate::coord::appends::WriteResult::Success {
136 timestamp: write_ts,
137 });
138 }
139 }
140 Message::AdvanceTimelines => {
141 let read_ts = self.get_local_read_ts().await;
145 self.downgrade_local_read_holds(read_ts);
146 self.advance_custom_timelines().boxed_local().await;
147 }
148 Message::ClusterEvent(event) => self.message_cluster_event(event).boxed_local().await,
149 Message::CancelPendingPeeks { conn_id } => {
150 self.cancel_pending_peeks(&conn_id);
151 }
152 Message::LinearizeReads => {
153 self.message_linearize_reads().boxed_local().await;
154 }
155 Message::StagedBatches {
156 conn_id,
157 table_id,
158 batches,
159 } => {
160 self.commit_staged_batches(conn_id, table_id, batches);
161 }
162 Message::StorageUsageSchedule => {
163 self.schedule_storage_usage_collection().boxed_local().await;
164 }
165 Message::StorageUsageFetch => {
166 self.storage_usage_fetch().boxed_local().await;
167 }
168 Message::StorageUsageUpdate(sizes) => {
169 self.storage_usage_update(sizes).boxed_local().await;
170 }
171 Message::StorageUsagePrune(expired) => {
172 self.storage_usage_prune(expired).boxed_local().await;
173 }
174 Message::ArrangementSizesSchedule => {
175 self.schedule_arrangement_sizes_collection()
176 .boxed_local()
177 .await;
178 }
179 Message::ArrangementSizesSnapshot => {
180 self.arrangement_sizes_snapshot().boxed_local().await;
181 }
182 Message::ArrangementSizesWrite(records) => {
183 self.arrangement_sizes_write(records).boxed_local().await;
184 }
185 Message::ArrangementSizesPrune(expired) => {
186 self.arrangement_sizes_prune(expired).boxed_local().await;
187 }
188 Message::HydrationHistorySchedule => {
189 self.schedule_hydration_history_collection();
190 }
191 Message::HydrationHistoryRun => {
192 self.run_hydration_history_collection();
193 }
194 Message::RetireExecute {
195 otel_ctx,
196 data,
197 reason,
198 } => {
199 otel_ctx.attach_as_parent();
200 self.retire_execution(reason, data);
201 }
202 Message::ExecuteSingleStatementTransaction {
203 ctx,
204 otel_ctx,
205 stmt,
206 params,
207 } => {
208 otel_ctx.attach_as_parent();
209 self.sequence_execute_single_statement_transaction(ctx, stmt, params)
210 .boxed_local()
211 .await;
212 }
213 Message::PeekStageReady { ctx, span, stage } => {
214 self.sequence_staged(ctx, span, stage).boxed_local().await;
215 }
216 Message::CreateIndexStageReady { ctx, span, stage } => {
217 self.sequence_staged(ctx, span, stage).boxed_local().await;
218 }
219 Message::CreateMetricSinkStageReady { ctx, span, stage } => {
220 self.sequence_staged(ctx, span, stage).boxed_local().await;
221 }
222 Message::CreateViewStageReady { ctx, span, stage } => {
223 self.sequence_staged(ctx, span, stage).boxed_local().await;
224 }
225 Message::CreateMaterializedViewStageReady { ctx, span, stage } => {
226 self.sequence_staged(ctx, span, stage).boxed_local().await;
227 }
228 Message::SubscribeStageReady { ctx, span, stage } => {
229 self.sequence_staged(ctx, span, stage).boxed_local().await;
230 }
231 Message::IntrospectionSubscribeStageReady { span, stage } => {
232 self.sequence_staged((), span, stage).boxed_local().await;
233 }
234 Message::MetricSinkStageReady { span, stage } => {
235 self.sequence_staged((), span, stage).boxed_local().await;
236 }
237 Message::ExplainTimestampStageReady { ctx, span, stage } => {
238 self.sequence_staged(ctx, span, stage).boxed_local().await;
239 }
240 Message::SecretStageReady { ctx, span, stage } => {
241 self.sequence_staged(ctx, span, stage).boxed_local().await;
242 }
243 Message::ClusterStageReady { ctx, span, stage } => {
244 self.sequence_staged(ctx, span, stage).boxed_local().await;
245 }
246 Message::DrainStatementLog => {
247 self.drain_statement_log();
248 }
249 Message::PrivateLinkVpcEndpointEvents(events) => {
250 if !self.controller.read_only() {
251 self.controller.storage.append_introspection_updates(
252 IntrospectionType::PrivatelinkConnectionStatusHistory,
253 events
254 .into_iter()
255 .map(|e| (mz_repr::Row::from(e), Diff::ONE))
256 .collect(),
257 );
258 }
259 }
260 Message::ClusterControllerRequest(request) => {
261 self.handle_cluster_controller_request(request)
262 .boxed_local()
263 .await;
264 }
265 Message::DeferredStatementReady => {
266 self.handle_deferred_statement().boxed_local().await;
267 }
268 }
269 }
270
271 #[mz_ore::instrument(level = "debug")]
272 pub async fn storage_usage_fetch(&self) {
273 if self.controller.read_only() {
280 tracing::info!("skipping storage usage collection in read-only mode");
281 if let Err(e) = self.internal_cmd_tx.send(Message::StorageUsageSchedule) {
282 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
283 }
284 return;
285 }
286
287 let internal_cmd_tx = self.internal_cmd_tx.clone();
288 let client = self.storage_usage_client.clone();
289
290 let live_shards: BTreeSet<_> = self
292 .controller
293 .storage
294 .active_collection_metadatas()
295 .into_iter()
296 .map(|(_id, m)| m.data_shard)
297 .collect();
298
299 let collection_metric = self.metrics.storage_usage_collection_time_seconds.clone();
300
301 task::spawn(|| "storage_usage_fetch", async move {
304 let collection_metric_timer = collection_metric.start_timer();
305 let shard_sizes = client.shards_usage_referenced(live_shards).await;
306 collection_metric_timer.observe_duration();
307
308 if let Err(e) = internal_cmd_tx.send(Message::StorageUsageUpdate(shard_sizes)) {
311 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
312 }
313 });
314 }
315
316 #[mz_ore::instrument(level = "debug")]
317 async fn storage_usage_update(&mut self, shards_usage: ShardsUsageReferenced) {
318 let write_ts = self.get_catalog_write_ts().await;
326 let collection_timestamp: EpochMillis = write_ts.into();
327
328 let batch_id = match self.catalog().allocate_storage_usage_id(write_ts).await {
334 Ok(id) => id,
335 Err(err) => {
336 tracing::warn!("failed to allocate storage usage batch id: {:?}", err);
337 return;
338 }
339 };
340
341 let updates: Vec<_> = shards_usage
342 .by_shard
343 .into_iter()
344 .map(|(shard_id, shard_usage)| {
345 let event = VersionedStorageUsage::new(
346 batch_id,
347 Some(shard_id.to_string()),
348 shard_usage.size_bytes(),
349 collection_timestamp,
350 );
351 self.catalog().pack_storage_usage_update(event, Diff::ONE)
352 })
353 .collect();
354
355 let table_updates = self.builtin_table_update().execute(updates);
356
357 let internal_cmd_tx = self.internal_cmd_tx.clone();
358 let task_span = info_span!(parent: None, "coord::storage_usage_update::table_updates");
359 OpenTelemetryContext::obtain().attach_as_parent_to(&task_span);
360 task::spawn(|| "storage_usage_update_table_updates", async move {
361 table_updates.instrument(task_span).await;
362 if let Err(e) = internal_cmd_tx.send(Message::StorageUsageSchedule) {
364 warn!("internal_cmd_rx dropped before we could send: {e:?}");
365 }
366 });
367 }
368
369 #[mz_ore::instrument(level = "debug")]
370 async fn storage_usage_prune(&mut self, expired: Vec<BuiltinTableUpdate>) {
371 let fut = self.builtin_table_update().execute(expired);
372 task::spawn(|| "storage_usage_pruning_apply", async move {
373 fut.await;
374 });
375 }
376
377 pub async fn schedule_storage_usage_collection(&self) {
378 const SEED_LEN: usize = 32;
386 let mut seed = [0; SEED_LEN];
387 for (i, byte) in self
388 .catalog()
389 .state()
390 .config()
391 .environment_id
392 .organization_id()
393 .as_bytes()
394 .into_iter()
395 .take(SEED_LEN)
396 .enumerate()
397 {
398 seed[i] = *byte;
399 }
400 let storage_usage_collection_interval_ms: EpochMillis =
401 EpochMillis::try_from(self.storage_usage_collection_interval.as_millis())
402 .expect("storage usage collection interval must fit into u64");
403 let offset =
404 rngs::SmallRng::from_seed(seed).random_range(0..storage_usage_collection_interval_ms);
405 let now_ts: EpochMillis = self.peek_local_write_ts().await.into();
406
407 let previous_collection_ts =
409 (now_ts - (now_ts % storage_usage_collection_interval_ms)) + offset;
410 let next_collection_ts = if previous_collection_ts > now_ts {
411 previous_collection_ts
412 } else {
413 previous_collection_ts + storage_usage_collection_interval_ms
414 };
415 let next_collection_interval = Duration::from_millis(next_collection_ts - now_ts);
416
417 let internal_cmd_tx = self.internal_cmd_tx.clone();
419 task::spawn(|| "storage_usage_collection", async move {
420 tokio::time::sleep(next_collection_interval).await;
421 if internal_cmd_tx.send(Message::StorageUsageFetch).is_err() {
422 }
424 });
425 }
426
427 pub async fn schedule_arrangement_sizes_collection(&self) {
435 const MAX_SLEEP: Duration = Duration::from_secs(60);
436
437 let interval_duration =
438 mz_adapter_types::dyncfgs::ARRANGEMENT_SIZE_HISTORY_COLLECTION_INTERVAL
439 .get(self.catalog().system_config().dyncfgs());
440
441 if interval_duration.is_zero() {
444 let internal_cmd_tx = self.internal_cmd_tx.clone();
445 task::spawn(|| "arrangement_sizes_collection_disabled", async move {
446 tokio::time::sleep(MAX_SLEEP).await;
447 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
448 });
449 return;
450 }
451
452 const SEED_LEN: usize = 32;
453 let mut seed = [0; SEED_LEN];
454 for (i, byte) in self
455 .catalog()
456 .state()
457 .config()
458 .environment_id
459 .organization_id()
460 .as_bytes()
461 .into_iter()
462 .take(SEED_LEN)
463 .enumerate()
464 {
465 seed[i] = *byte;
466 }
467 let interval_ms: EpochMillis = EpochMillis::try_from(interval_duration.as_millis())
468 .expect("arrangement_size_history_collection_interval must fit into u64");
469 let interval_ms = interval_ms.max(1);
471 let offset = rngs::SmallRng::from_seed(seed).random_range(0..interval_ms);
472 let now_ts: EpochMillis = self.peek_local_write_ts().await.into();
473
474 let previous_collection_ts = (now_ts - (now_ts % interval_ms)) + offset;
475 let next_collection_ts = if previous_collection_ts > now_ts {
476 previous_collection_ts
477 } else {
478 previous_collection_ts + interval_ms
479 };
480 let sleep_for = Duration::from_millis(next_collection_ts - now_ts);
481
482 let (capped_sleep, fire_snapshot) = if sleep_for <= MAX_SLEEP {
486 (sleep_for, true)
487 } else {
488 (MAX_SLEEP, false)
489 };
490
491 let internal_cmd_tx = self.internal_cmd_tx.clone();
492 task::spawn(|| "arrangement_sizes_collection", async move {
493 tokio::time::sleep(capped_sleep).await;
494 let msg = if fire_snapshot {
495 Message::ArrangementSizesSnapshot
496 } else {
497 Message::ArrangementSizesSchedule
498 };
499 let _ = internal_cmd_tx.send(msg);
501 });
502 }
503
504 #[mz_ore::instrument(level = "debug")]
517 async fn arrangement_sizes_snapshot(&self) {
518 if self.controller.read_only() {
524 self.schedule_arrangement_sizes_collection().await;
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().await;
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(records)
607 };
608 let _ = internal_cmd_tx.send(msg);
610 });
611 }
612
613 #[mz_ore::instrument(level = "debug")]
617 async fn arrangement_sizes_write(&mut self, records: Vec<ArrangementSizeRecord>) {
618 let fresh_size_replicas = self.fresh_introspection_replicas(
622 IntrospectionType::ComputeObjectArrangementSizes,
623 ARRANGEMENT_SIZES_FRESHNESS_MARGIN,
624 );
625 let records: Vec<_> = records
626 .into_iter()
627 .filter(|record| fresh_size_replicas.contains(&record.replica_id))
628 .collect();
629 if records.is_empty() {
630 self.schedule_arrangement_sizes_collection().await;
631 return;
632 }
633
634 let collection_ts: EpochMillis = self.get_local_write_ts().await.timestamp.into();
639 let collection_datum = Datum::TimestampTz(
640 mz_ore::now::to_datetime(collection_ts)
641 .try_into()
642 .expect("collection_timestamp must fit into TimestampTz"),
643 );
644
645 let history_item_id = self
646 .catalog()
647 .resolve_builtin_table(&mz_catalog::builtin::MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY);
648
649 let updates: Vec<_> = records
650 .into_iter()
651 .map(|record| {
652 let row = Row::pack_slice(&[
653 Datum::String(&record.replica_id),
654 Datum::String(&record.object_id),
655 Datum::Int64(record.size),
656 collection_datum,
657 Datum::from(record.hydration_complete),
658 ]);
659 BuiltinTableUpdate::row(history_item_id, row, Diff::ONE)
660 })
661 .collect();
662
663 let row_count = updates.len();
664 self.metrics
665 .arrangement_sizes_rows_written
666 .inc_by(u64::cast_from(row_count));
667
668 let fut = self.builtin_table_update().execute(updates);
673 let internal_cmd_tx = self.internal_cmd_tx.clone();
674 let task_span = info_span!(parent: None, "coord::arrangement_sizes_write::table_updates");
675 OpenTelemetryContext::obtain().attach_as_parent_to(&task_span);
676 task::spawn(|| "arrangement_sizes_write_table_updates", async move {
677 fut.instrument(task_span).await;
678 if let Err(e) = internal_cmd_tx.send(Message::ArrangementSizesSchedule) {
679 warn!("internal_cmd_rx dropped before we could send: {e:?}");
680 }
681 });
682
683 tracing::debug!(
684 "appended {row_count} rows to mz_object_arrangement_size_history at ts {collection_ts}"
685 );
686 }
687
688 #[mz_ore::instrument(level = "debug")]
689 async fn arrangement_sizes_prune(&mut self, expired: Vec<BuiltinTableUpdate>) {
690 let fut = self.builtin_table_update().execute(expired);
691 task::spawn(|| "arrangement_sizes_pruning_apply", async move {
692 fut.await;
693 });
694 }
695
696 #[mz_ore::instrument(level = "debug")]
697 async fn message_command(&mut self, cmd: Command) {
698 self.handle_command(cmd).await;
699 }
700
701 #[mz_ore::instrument(level = "debug")]
702 async fn message_controller(&mut self, message: ControllerResponse) {
703 event!(Level::TRACE, message = format!("{:?}", message));
704 match message {
705 ControllerResponse::PeekNotification(uuid, response, otel_ctx) => {
706 self.handle_peek_notification(uuid, response, otel_ctx);
707 }
708 ControllerResponse::SubscribeResponse(sink_id, response) => {
709 if let Some(ActiveComputeSink::Subscribe(active_subscribe)) =
710 self.active_compute_sinks.get_mut(&sink_id)
711 {
712 let finished = active_subscribe.process_response(response);
713 let buffered_bytes = active_subscribe
724 .backlog_accounting
725 .lock()
726 .expect("subscribe backlog accounting poisoned")
727 .backlog_size();
728 let max_buffered_bytes = active_subscribe.max_buffered_bytes;
729
730 let reason = if finished {
731 Some(ActiveComputeSinkRetireReason::Finished)
732 } else if buffered_bytes > max_buffered_bytes {
733 Some(ActiveComputeSinkRetireReason::BufferExceeded {
734 buffered_bytes,
735 max_buffered_bytes,
736 })
737 } else {
738 None
739 };
740 if let Some(reason) = reason {
741 let retire_notify = self
742 .retire_compute_sinks(btreemap! {
743 sink_id => reason,
744 })
745 .await;
746 drop(retire_notify);
749 }
750
751 soft_assert_or_log!(
752 !self.introspection_subscribes.contains_key(&sink_id),
753 "`sink_id` {sink_id} unexpectedly found in both `active_subscribes` \
754 and `introspection_subscribes`",
755 );
756 } else if self.introspection_subscribes.contains_key(&sink_id) {
757 self.handle_introspection_subscribe_batch(sink_id, response)
758 .await;
759 } else {
760 }
763 }
764 ControllerResponse::CopyToResponse(sink_id, response) => {
765 match self.drop_compute_sink(sink_id).await {
766 Some((ActiveComputeSink::CopyTo(active_copy_to), _write_notify)) => {
767 active_copy_to.retire_with_response(response);
768 }
769 _ => {
770 }
773 }
774 }
775 ControllerResponse::WatchSetFinished(ws_ids) => {
776 let now = self.now();
777 for ws_id in ws_ids {
778 let Some((conn_id, rsp)) = self.installed_watch_sets.remove(&ws_id) else {
779 continue;
780 };
781 self.connection_watch_sets
782 .get_mut(&conn_id)
783 .expect("corrupted coordinator state: unknown connection id")
784 .remove(&ws_id);
785 if self.connection_watch_sets[&conn_id].is_empty() {
786 self.connection_watch_sets.remove(&conn_id);
787 }
788
789 match rsp {
790 WatchSetResponse::StatementDependenciesReady(id, ev) => {
791 self.record_statement_lifecycle_event(&id, &ev, now);
792 }
793 WatchSetResponse::AlterSinkReady(ctx) => {
794 self.sequence_alter_sink_finish(ctx).await;
795 }
796 WatchSetResponse::AlterMaterializedViewReady(ctx) => {
797 self.sequence_alter_materialized_view_apply_replacement_finish(ctx)
798 .await;
799 }
800 }
801 }
802 }
803 }
804 }
805
806 #[mz_ore::instrument(level = "debug")]
807 async fn message_purified_statement_ready(
808 &mut self,
809 PurifiedStatementReady {
810 ctx,
811 result,
812 params,
813 mut plan_validity,
814 original_stmt,
815 otel_ctx,
816 }: PurifiedStatementReady,
817 ) {
818 otel_ctx.attach_as_parent();
819
820 if plan_validity.check(self.catalog()).is_err() {
831 self.handle_execute_inner(original_stmt, params, ctx).await;
832 return;
833 }
834
835 let purified_statement = match result {
836 Ok(ok) => ok,
837 Err(e) => return ctx.retire(Err(e)),
838 };
839
840 let plan = match purified_statement {
841 PurifiedStatement::PurifiedCreateSource {
842 create_progress_subsource_stmt,
843 create_source_stmt,
844 subsources,
845 available_source_references,
846 } => self
847 .plan_purified_create_source(
848 &ctx,
849 params,
850 create_progress_subsource_stmt,
851 create_source_stmt,
852 subsources,
853 available_source_references,
854 )
855 .await
856 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
857 PurifiedStatement::PurifiedAlterSourceAddSubsources {
858 source_name,
859 options,
860 subsources,
861 } => self
862 .plan_purified_alter_source_add_subsource(
863 ctx.session(),
864 params,
865 source_name,
866 options,
867 subsources,
868 )
869 .await
870 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
871 PurifiedStatement::PurifiedAlterSourceRefreshReferences {
872 source_name,
873 available_source_references,
874 } => self
875 .plan_purified_alter_source_refresh_references(
876 ctx.session(),
877 params,
878 source_name,
879 available_source_references,
880 )
881 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
882 o @ (PurifiedStatement::PurifiedAlterSource { .. }
883 | PurifiedStatement::PurifiedCreateSink(..)
884 | PurifiedStatement::PurifiedCreateTableFromSource { .. }) => {
885 let stmt = match o {
887 PurifiedStatement::PurifiedAlterSource { alter_source_stmt } => {
888 Statement::AlterSource(alter_source_stmt)
889 }
890 PurifiedStatement::PurifiedCreateTableFromSource { stmt } => {
891 Statement::CreateTableFromSource(stmt)
892 }
893 PurifiedStatement::PurifiedCreateSink(stmt) => Statement::CreateSink(stmt),
894 PurifiedStatement::PurifiedCreateSource { .. }
895 | PurifiedStatement::PurifiedAlterSourceAddSubsources { .. }
896 | PurifiedStatement::PurifiedAlterSourceRefreshReferences { .. } => {
897 unreachable!("not part of exterior match stmt")
898 }
899 };
900
901 let catalog = self.catalog().for_session(ctx.session());
904 let resolved_ids = mz_sql::names::visit_dependencies(&catalog, &stmt);
905 self.plan_statement(ctx.session(), stmt, ¶ms, &resolved_ids)
906 .map(|(plan, sql_impl_ids)| (plan, resolved_ids, sql_impl_ids))
907 }
908 };
909
910 match plan {
911 Ok((plan, resolved_ids, sql_impl_ids)) => {
912 self.sequence_plan(ctx, plan, resolved_ids, sql_impl_ids)
913 .await
914 }
915 Err(e) => ctx.retire(Err(e)),
916 }
917 }
918
919 #[mz_ore::instrument(level = "debug")]
920 async fn message_create_connection_validation_ready(
921 &mut self,
922 CreateConnectionValidationReady {
923 mut ctx,
924 result,
925 connection_id,
926 connection_gid,
927 mut plan_validity,
928 otel_ctx,
929 resolved_ids,
930 }: CreateConnectionValidationReady,
931 ) {
932 otel_ctx.attach_as_parent();
933
934 if let Err(e) = plan_validity.check(self.catalog()) {
940 if self.secrets_controller.delete(connection_id).await.is_ok() {
941 self.caching_secrets_reader.invalidate(connection_id);
942 }
943 return ctx.retire(Err(e));
944 }
945
946 let plan = match result {
947 Ok(ok) => ok,
948 Err(e) => {
949 if self.secrets_controller.delete(connection_id).await.is_ok() {
950 self.caching_secrets_reader.invalidate(connection_id);
951 }
952 return ctx.retire(Err(e));
953 }
954 };
955
956 let result = self
957 .sequence_create_connection_stage_finish(
958 &mut ctx,
959 connection_id,
960 connection_gid,
961 plan,
962 resolved_ids,
963 )
964 .await;
965 ctx.retire(result);
966 }
967
968 #[mz_ore::instrument(level = "debug")]
969 async fn message_alter_connection_validation_ready(
970 &mut self,
971 AlterConnectionValidationReady {
972 mut ctx,
973 result,
974 connection_id,
975 connection_gid: _,
976 mut plan_validity,
977 otel_ctx,
978 resolved_ids: _,
979 }: AlterConnectionValidationReady,
980 ) {
981 otel_ctx.attach_as_parent();
982
983 if let Err(e) = plan_validity.check(self.catalog()) {
989 return ctx.retire(Err(e));
990 }
991
992 let conn = match result {
993 Ok(ok) => ok,
994 Err(e) => {
995 return ctx.retire(Err(e));
996 }
997 };
998
999 let result = self
1000 .sequence_alter_connection_stage_finish(ctx.session_mut(), connection_id, conn)
1001 .await;
1002 ctx.retire(result);
1003 }
1004
1005 #[mz_ore::instrument(level = "debug")]
1006 async fn message_cluster_event(&mut self, event: ClusterEvent) {
1007 event!(Level::TRACE, event = format!("{:?}", event));
1008
1009 if let Some(segment_client) = &self.segment_client {
1010 let env_id = &self.catalog().config().environment_id;
1011 let mut properties = json!({
1012 "cluster_id": event.cluster_id.to_string(),
1013 "replica_id": event.replica_id.to_string(),
1014 "process_id": event.process_id,
1015 "status": event.status.as_kebab_case_str(),
1016 });
1017 match event.status {
1018 ClusterStatus::Online => (),
1019 ClusterStatus::Offline(reason) => {
1020 let properties = match &mut properties {
1021 serde_json::Value::Object(map) => map,
1022 _ => unreachable!(),
1023 };
1024 properties.insert(
1025 "reason".into(),
1026 json!(reason.display_or("unknown").to_string()),
1027 );
1028 }
1029 };
1030 segment_client.environment_track(
1031 env_id,
1032 "Cluster Changed Status",
1033 properties,
1034 EventDetails {
1035 timestamp: Some(event.time),
1036 ..Default::default()
1037 },
1038 );
1039 }
1040
1041 let Some(replica_statuses) = self
1044 .cluster_replica_statuses
1045 .try_get_cluster_replica_statuses(event.cluster_id, event.replica_id)
1046 else {
1047 return;
1048 };
1049
1050 let old_process_status = &replica_statuses[&event.process_id];
1051 let status_changed = event.status != old_process_status.status;
1052 let restart_count_changed = event.restart_count != old_process_status.restart_count;
1053
1054 if !status_changed && !restart_count_changed {
1066 return;
1067 }
1068
1069 if status_changed && !self.controller.read_only() {
1070 let offline_reason = match event.status {
1071 ClusterStatus::Online => None,
1072 ClusterStatus::Offline(None) => None,
1073 ClusterStatus::Offline(Some(reason)) => Some(reason.to_string()),
1074 };
1075 let row = Row::pack_slice(&[
1076 Datum::String(&event.replica_id.to_string()),
1077 Datum::UInt64(event.process_id),
1078 Datum::String(event.status.as_kebab_case_str()),
1079 Datum::from(offline_reason.as_deref()),
1080 Datum::TimestampTz(event.time.try_into().expect("must fit")),
1081 ]);
1082 self.controller.storage.append_introspection_updates(
1083 IntrospectionType::ReplicaStatusHistory,
1084 vec![(row, Diff::ONE)],
1085 );
1086 }
1087
1088 let old_replica_status = status_changed
1091 .then(|| ClusterReplicaStatuses::cluster_replica_status(replica_statuses));
1092
1093 let new_process_status = ClusterReplicaProcessStatus {
1094 status: event.status,
1095 restart_count: event.restart_count,
1096 time: event.time,
1097 };
1098 self.cluster_replica_statuses.ensure_cluster_status(
1099 event.cluster_id,
1100 event.replica_id,
1101 event.process_id,
1102 new_process_status,
1103 );
1104
1105 if !matches!(event.status, ClusterStatus::Online) || restart_count_changed {
1110 self.invalidate_introspection_freshness(event.replica_id);
1111 }
1112
1113 if let Some(old_replica_status) = old_replica_status {
1114 let cluster = self.catalog().get_cluster(event.cluster_id);
1115 let replica = cluster.replica(event.replica_id).expect("Replica exists");
1116 let new_replica_status = self
1117 .cluster_replica_statuses
1118 .get_cluster_replica_status(event.cluster_id, event.replica_id);
1119
1120 if old_replica_status != new_replica_status {
1121 let notifier = self.broadcast_notice_tx();
1122 let notice = AdapterNotice::ClusterReplicaStatusChanged {
1123 cluster: cluster.name.clone(),
1124 replica: replica.name.clone(),
1125 status: new_replica_status,
1126 time: event.time,
1127 };
1128 notifier(notice);
1129 }
1130 }
1131 }
1132
1133 #[mz_ore::instrument(level = "debug")]
1134 async fn message_linearize_reads(&mut self) {
1139 let mut shortest_wait = Duration::MAX;
1140 let mut ready_txns = Vec::new();
1141
1142 let mut cached_oracle_ts = BTreeMap::new();
1147
1148 for (conn_id, mut read_txn) in std::mem::take(&mut self.pending_linearize_read_txns) {
1149 if let TimestampContext::TimelineTimestamp {
1150 timeline,
1151 chosen_ts,
1152 oracle_ts,
1153 } = read_txn.timestamp_context()
1154 {
1155 let oracle_ts = match oracle_ts {
1156 Some(oracle_ts) => oracle_ts,
1157 None => {
1158 ready_txns.push(read_txn);
1160 continue;
1161 }
1162 };
1163
1164 if chosen_ts <= oracle_ts {
1165 ready_txns.push(read_txn);
1168 continue;
1169 }
1170
1171 let current_oracle_ts = cached_oracle_ts.entry(timeline.clone());
1173 let current_oracle_ts = match current_oracle_ts {
1174 btree_map::Entry::Vacant(entry) => {
1175 let timestamp_oracle = self.get_timestamp_oracle(timeline);
1176 let read_ts = timestamp_oracle.read_ts().await;
1177 entry.insert(read_ts.clone());
1178 read_ts
1179 }
1180 btree_map::Entry::Occupied(entry) => entry.get().clone(),
1181 };
1182
1183 if *chosen_ts <= current_oracle_ts {
1184 ready_txns.push(read_txn);
1185 } else {
1186 let wait =
1187 Duration::from_millis(chosen_ts.saturating_sub(current_oracle_ts).into());
1188 if wait < shortest_wait {
1189 shortest_wait = wait;
1190 }
1191 read_txn.num_requeues += 1;
1192 self.pending_linearize_read_txns.insert(conn_id, read_txn);
1193 }
1194 } else {
1195 ready_txns.push(read_txn);
1196 }
1197 }
1198
1199 if !ready_txns.is_empty() {
1200 let otel_ctx = ready_txns.first().expect("known to exist").otel_ctx.clone();
1203 let span = tracing::debug_span!("message_linearize_reads");
1204 otel_ctx.attach_as_parent_to(&span);
1205
1206 let now = Instant::now();
1207 for ready_txn in ready_txns {
1208 let span = tracing::debug_span!("retire_read_results");
1209 ready_txn.otel_ctx.attach_as_parent_to(&span);
1210 let _entered = span.enter();
1211 self.metrics
1212 .linearize_message_seconds
1213 .with_label_values(&[
1214 ready_txn.txn.label(),
1215 if ready_txn.num_requeues == 0 {
1216 "true"
1217 } else {
1218 "false"
1219 },
1220 ])
1221 .observe((now - ready_txn.created).as_secs_f64());
1222 if let Some((ctx, result)) = ready_txn.txn.finish() {
1223 ctx.retire(result);
1224 }
1225 }
1226 }
1227
1228 if !self.pending_linearize_read_txns.is_empty() {
1229 let remaining_ms = std::cmp::min(shortest_wait, Duration::from_millis(1_000));
1232 let linearize_reads_notify = Arc::clone(&self.linearize_reads_notify);
1233 task::spawn(|| "deferred_read_txns", async move {
1234 tokio::time::sleep(remaining_ms).await;
1235 linearize_reads_notify.notify_one();
1236 });
1237 }
1238 }
1239}
1240
1241fn arrangement_sizes_records(
1257 mut live_snapshot: Vec<(Row, StorageDiff)>,
1258 mut hydration_snapshot: Vec<(Row, StorageDiff)>,
1259 fresh_size_replicas: &BTreeSet<String>,
1260 fresh_hydration_replicas: &BTreeSet<String>,
1261) -> Vec<ArrangementSizeRecord> {
1262 differential_dataflow::consolidation::consolidate(&mut live_snapshot);
1263 differential_dataflow::consolidation::consolidate(&mut hydration_snapshot);
1264
1265 let mut datum_vec = mz_repr::DatumVec::new();
1266
1267 const HYDRATION_COL_REPLICA_ID: usize = 0;
1269 const HYDRATION_COL_OBJECT_ID: usize = 1;
1270 const HYDRATION_COL_TIME_NS: usize = 2;
1271 const HYDRATION_COL_COUNT: usize = 3;
1272
1273 let mut hydrated: BTreeSet<(String, String)> = BTreeSet::new();
1274 for (row, diff) in &hydration_snapshot {
1275 if *diff != 1 {
1276 continue;
1277 }
1278 let datums = datum_vec.borrow_with(row);
1279 if datums.len() < HYDRATION_COL_COUNT {
1280 continue;
1281 }
1282 if datums[HYDRATION_COL_TIME_NS].is_null() {
1283 continue;
1284 }
1285 let replica_id = datums[HYDRATION_COL_REPLICA_ID].unwrap_str();
1286 if !fresh_hydration_replicas.contains(replica_id) {
1287 continue;
1288 }
1289 hydrated.insert((
1290 replica_id.to_string(),
1291 datums[HYDRATION_COL_OBJECT_ID].unwrap_str().to_string(),
1292 ));
1293 }
1294
1295 const LIVE_COL_REPLICA_ID: usize = 0;
1297 const LIVE_COL_OBJECT_ID: usize = 1;
1298 const LIVE_COL_SIZE: usize = 2;
1299 const LIVE_COL_COUNT: usize = 3;
1300
1301 let mut skipped_malformed: u64 = 0;
1302 let mut skipped_null_size: u64 = 0;
1303 let mut skipped_zero_size: u64 = 0;
1304 let mut skipped_stale_replica: u64 = 0;
1305 let mut records = Vec::with_capacity(live_snapshot.len());
1306 for (row, diff) in &live_snapshot {
1307 if *diff != 1 {
1308 continue;
1309 }
1310 let datums = datum_vec.borrow_with(row);
1311 if datums.len() != LIVE_COL_COUNT {
1314 skipped_malformed += 1;
1315 continue;
1316 }
1317 let replica_id = datums[LIVE_COL_REPLICA_ID].unwrap_str();
1318 if !fresh_size_replicas.contains(replica_id) {
1319 skipped_stale_replica += 1;
1320 continue;
1321 }
1322 let object_id = datums[LIVE_COL_OBJECT_ID].unwrap_str();
1323 let size_datum = datums[LIVE_COL_SIZE];
1324 if size_datum.is_null() {
1327 skipped_null_size += 1;
1328 continue;
1329 }
1330 if size_datum.unwrap_int64() == 0 {
1334 skipped_zero_size += 1;
1335 continue;
1336 }
1337 let hydration_complete =
1338 hydrated.contains(&(replica_id.to_string(), object_id.to_string()));
1339 records.push(ArrangementSizeRecord {
1340 replica_id: replica_id.to_string(),
1341 object_id: object_id.to_string(),
1342 size: size_datum.unwrap_int64(),
1343 hydration_complete,
1344 });
1345 }
1346 if skipped_malformed > 0 {
1347 warn!(
1348 "mz_object_arrangement_sizes schema drift: skipped {skipped_malformed} rows \
1349 with unexpected arity"
1350 );
1351 }
1352 if skipped_null_size > 0 {
1353 tracing::debug!("skipped {skipped_null_size} live rows with null size");
1354 }
1355 if skipped_zero_size > 0 {
1356 tracing::debug!("skipped {skipped_zero_size} live rows with zero size");
1357 }
1358 if skipped_stale_replica > 0 {
1359 tracing::debug!(
1360 "skipped {skipped_stale_replica} live rows from replicas without fresh \
1361 introspection data"
1362 );
1363 }
1364 records
1365}
1366
1367#[cfg(test)]
1368mod arrangement_sizes_records_tests {
1369 use std::collections::BTreeSet;
1370
1371 use mz_repr::{Datum, Row};
1372
1373 use super::arrangement_sizes_records;
1374
1375 fn live_row(replica_id: &str, object_id: &str, size: Option<i64>) -> Row {
1376 Row::pack_slice(&[
1377 Datum::String(replica_id),
1378 Datum::String(object_id),
1379 size.map_or(Datum::Null, Datum::Int64),
1380 ])
1381 }
1382
1383 fn hydration_row(replica_id: &str, object_id: &str, hydrated: bool) -> Row {
1384 Row::pack_slice(&[
1385 Datum::String(replica_id),
1386 Datum::String(object_id),
1387 if hydrated {
1388 Datum::UInt64(1)
1389 } else {
1390 Datum::Null
1391 },
1392 ])
1393 }
1394
1395 fn replicas(ids: &[&str]) -> BTreeSet<String> {
1396 ids.iter().map(|id| id.to_string()).collect()
1397 }
1398
1399 #[mz_ore::test]
1400 fn hydration_flag_per_pair() {
1401 let live = vec![
1402 (live_row("u1", "u100", Some(10)), 1),
1403 (live_row("u1", "u200", Some(20)), 1),
1404 ];
1405 let hydration = vec![
1406 (hydration_row("u1", "u100", true), 1),
1407 (hydration_row("u1", "u200", false), 1),
1408 ];
1409 let fresh = replicas(&["u1"]);
1410 let records = arrangement_sizes_records(live, hydration, &fresh, &fresh);
1411 assert_eq!(records.len(), 2);
1412 assert!(
1413 records
1414 .iter()
1415 .any(|r| r.object_id == "u100" && r.hydration_complete)
1416 );
1417 assert!(
1418 records
1419 .iter()
1420 .any(|r| r.object_id == "u200" && !r.hydration_complete)
1421 );
1422 }
1423
1424 #[mz_ore::test]
1425 fn skips_malformed_null_and_retracted() {
1426 let live = vec![
1427 (Row::pack_slice(&[Datum::String("u1")]), 1),
1429 (live_row("u1", "u100", None), 1),
1431 (live_row("u1", "u200", Some(20)), 1),
1433 (live_row("u1", "u200", Some(20)), -1),
1434 (live_row("u1", "u300", Some(30)), 1),
1435 ];
1436 let fresh = replicas(&["u1"]);
1437 let records = arrangement_sizes_records(live, Vec::new(), &fresh, &fresh);
1438 assert_eq!(records.len(), 1);
1439 assert_eq!(records[0].object_id, "u300");
1440 assert_eq!(records[0].size, 30);
1441 assert!(!records[0].hydration_complete);
1442 }
1443
1444 #[mz_ore::test]
1445 fn skips_zero_size_rows() {
1446 let live = vec![
1449 (live_row("u1", "u100", Some(0)), 1),
1450 (live_row("u1", "u200", Some(10485760)), 1),
1451 ];
1452 let fresh = replicas(&["u1"]);
1453 let records = arrangement_sizes_records(live, Vec::new(), &fresh, &fresh);
1454 assert_eq!(records.len(), 1);
1455 assert_eq!(records[0].object_id, "u200");
1456 }
1457
1458 #[mz_ore::test]
1459 fn skips_rows_from_stale_replicas() {
1460 let live = vec![
1462 (live_row("u1", "u100", Some(10)), 1),
1463 (live_row("u2", "u100", Some(99)), 1),
1464 ];
1465 let hydration = vec![
1466 (hydration_row("u1", "u100", true), 1),
1467 (hydration_row("u2", "u100", true), 1),
1468 ];
1469 let fresh = replicas(&["u1"]);
1470 let records = arrangement_sizes_records(live, hydration, &fresh, &fresh);
1471 assert_eq!(records.len(), 1);
1472 assert_eq!(records[0].replica_id, "u1");
1473 assert!(records[0].hydration_complete);
1474 }
1475
1476 #[mz_ore::test]
1477 fn stale_hydration_data_is_not_trusted() {
1478 let live = vec![(live_row("u1", "u100", Some(10)), 1)];
1481 let hydration = vec![(hydration_row("u1", "u100", true), 1)];
1482 let records =
1483 arrangement_sizes_records(live, hydration, &replicas(&["u1"]), &replicas(&[]));
1484 assert_eq!(records.len(), 1);
1485 assert!(!records[0].hydration_complete);
1486 }
1487}