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::RetireExecute {
189 otel_ctx,
190 data,
191 reason,
192 } => {
193 otel_ctx.attach_as_parent();
194 self.retire_execution(reason, data);
195 }
196 Message::ExecuteSingleStatementTransaction {
197 ctx,
198 otel_ctx,
199 stmt,
200 params,
201 } => {
202 otel_ctx.attach_as_parent();
203 self.sequence_execute_single_statement_transaction(ctx, stmt, params)
204 .boxed_local()
205 .await;
206 }
207 Message::PeekStageReady { ctx, span, stage } => {
208 self.sequence_staged(ctx, span, stage).boxed_local().await;
209 }
210 Message::CreateIndexStageReady { ctx, span, stage } => {
211 self.sequence_staged(ctx, span, stage).boxed_local().await;
212 }
213 Message::CreateMetricSinkStageReady { ctx, span, stage } => {
214 self.sequence_staged(ctx, span, stage).boxed_local().await;
215 }
216 Message::CreateViewStageReady { ctx, span, stage } => {
217 self.sequence_staged(ctx, span, stage).boxed_local().await;
218 }
219 Message::CreateMaterializedViewStageReady { ctx, span, stage } => {
220 self.sequence_staged(ctx, span, stage).boxed_local().await;
221 }
222 Message::SubscribeStageReady { ctx, span, stage } => {
223 self.sequence_staged(ctx, span, stage).boxed_local().await;
224 }
225 Message::IntrospectionSubscribeStageReady { span, stage } => {
226 self.sequence_staged((), span, stage).boxed_local().await;
227 }
228 Message::ExplainTimestampStageReady { ctx, span, stage } => {
229 self.sequence_staged(ctx, span, stage).boxed_local().await;
230 }
231 Message::SecretStageReady { ctx, span, stage } => {
232 self.sequence_staged(ctx, span, stage).boxed_local().await;
233 }
234 Message::ClusterStageReady { ctx, span, stage } => {
235 self.sequence_staged(ctx, span, stage).boxed_local().await;
236 }
237 Message::DrainStatementLog => {
238 self.drain_statement_log();
239 }
240 Message::PrivateLinkVpcEndpointEvents(events) => {
241 if !self.controller.read_only() {
242 self.controller.storage.append_introspection_updates(
243 IntrospectionType::PrivatelinkConnectionStatusHistory,
244 events
245 .into_iter()
246 .map(|e| (mz_repr::Row::from(e), Diff::ONE))
247 .collect(),
248 );
249 }
250 }
251 Message::ClusterControllerRequest(request) => {
252 self.handle_cluster_controller_request(request)
253 .boxed_local()
254 .await;
255 }
256 Message::DeferredStatementReady => {
257 self.handle_deferred_statement().boxed_local().await;
258 }
259 }
260 }
261
262 #[mz_ore::instrument(level = "debug")]
263 pub async fn storage_usage_fetch(&self) {
264 if self.controller.read_only() {
271 tracing::info!("skipping storage usage collection in read-only mode");
272 if let Err(e) = self.internal_cmd_tx.send(Message::StorageUsageSchedule) {
273 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
274 }
275 return;
276 }
277
278 let internal_cmd_tx = self.internal_cmd_tx.clone();
279 let client = self.storage_usage_client.clone();
280
281 let live_shards: BTreeSet<_> = self
283 .controller
284 .storage
285 .active_collection_metadatas()
286 .into_iter()
287 .map(|(_id, m)| m.data_shard)
288 .collect();
289
290 let collection_metric = self.metrics.storage_usage_collection_time_seconds.clone();
291
292 task::spawn(|| "storage_usage_fetch", async move {
295 let collection_metric_timer = collection_metric.start_timer();
296 let shard_sizes = client.shards_usage_referenced(live_shards).await;
297 collection_metric_timer.observe_duration();
298
299 if let Err(e) = internal_cmd_tx.send(Message::StorageUsageUpdate(shard_sizes)) {
302 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
303 }
304 });
305 }
306
307 #[mz_ore::instrument(level = "debug")]
308 async fn storage_usage_update(&mut self, shards_usage: ShardsUsageReferenced) {
309 let write_ts = self.get_catalog_write_ts().await;
317 let collection_timestamp: EpochMillis = write_ts.into();
318
319 let batch_id = match self.catalog().allocate_storage_usage_id(write_ts).await {
325 Ok(id) => id,
326 Err(err) => {
327 tracing::warn!("failed to allocate storage usage batch id: {:?}", err);
328 return;
329 }
330 };
331
332 let updates: Vec<_> = shards_usage
333 .by_shard
334 .into_iter()
335 .map(|(shard_id, shard_usage)| {
336 let event = VersionedStorageUsage::new(
337 batch_id,
338 Some(shard_id.to_string()),
339 shard_usage.size_bytes(),
340 collection_timestamp,
341 );
342 self.catalog().pack_storage_usage_update(event, Diff::ONE)
343 })
344 .collect();
345
346 let table_updates = self.builtin_table_update().execute(updates);
347
348 let internal_cmd_tx = self.internal_cmd_tx.clone();
349 let task_span = info_span!(parent: None, "coord::storage_usage_update::table_updates");
350 OpenTelemetryContext::obtain().attach_as_parent_to(&task_span);
351 task::spawn(|| "storage_usage_update_table_updates", async move {
352 table_updates.instrument(task_span).await;
353 if let Err(e) = internal_cmd_tx.send(Message::StorageUsageSchedule) {
355 warn!("internal_cmd_rx dropped before we could send: {e:?}");
356 }
357 });
358 }
359
360 #[mz_ore::instrument(level = "debug")]
361 async fn storage_usage_prune(&mut self, expired: Vec<BuiltinTableUpdate>) {
362 let fut = self.builtin_table_update().execute(expired);
363 task::spawn(|| "storage_usage_pruning_apply", async move {
364 fut.await;
365 });
366 }
367
368 pub async fn schedule_storage_usage_collection(&self) {
369 const SEED_LEN: usize = 32;
377 let mut seed = [0; SEED_LEN];
378 for (i, byte) in self
379 .catalog()
380 .state()
381 .config()
382 .environment_id
383 .organization_id()
384 .as_bytes()
385 .into_iter()
386 .take(SEED_LEN)
387 .enumerate()
388 {
389 seed[i] = *byte;
390 }
391 let storage_usage_collection_interval_ms: EpochMillis =
392 EpochMillis::try_from(self.storage_usage_collection_interval.as_millis())
393 .expect("storage usage collection interval must fit into u64");
394 let offset =
395 rngs::SmallRng::from_seed(seed).random_range(0..storage_usage_collection_interval_ms);
396 let now_ts: EpochMillis = self.peek_local_write_ts().await.into();
397
398 let previous_collection_ts =
400 (now_ts - (now_ts % storage_usage_collection_interval_ms)) + offset;
401 let next_collection_ts = if previous_collection_ts > now_ts {
402 previous_collection_ts
403 } else {
404 previous_collection_ts + storage_usage_collection_interval_ms
405 };
406 let next_collection_interval = Duration::from_millis(next_collection_ts - now_ts);
407
408 let internal_cmd_tx = self.internal_cmd_tx.clone();
410 task::spawn(|| "storage_usage_collection", async move {
411 tokio::time::sleep(next_collection_interval).await;
412 if internal_cmd_tx.send(Message::StorageUsageFetch).is_err() {
413 }
415 });
416 }
417
418 pub async fn schedule_arrangement_sizes_collection(&self) {
426 const MAX_SLEEP: Duration = Duration::from_secs(60);
427
428 let interval_duration =
429 mz_adapter_types::dyncfgs::ARRANGEMENT_SIZE_HISTORY_COLLECTION_INTERVAL
430 .get(self.catalog().system_config().dyncfgs());
431
432 if interval_duration.is_zero() {
435 let internal_cmd_tx = self.internal_cmd_tx.clone();
436 task::spawn(|| "arrangement_sizes_collection_disabled", async move {
437 tokio::time::sleep(MAX_SLEEP).await;
438 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
439 });
440 return;
441 }
442
443 const SEED_LEN: usize = 32;
444 let mut seed = [0; SEED_LEN];
445 for (i, byte) in self
446 .catalog()
447 .state()
448 .config()
449 .environment_id
450 .organization_id()
451 .as_bytes()
452 .into_iter()
453 .take(SEED_LEN)
454 .enumerate()
455 {
456 seed[i] = *byte;
457 }
458 let interval_ms: EpochMillis = EpochMillis::try_from(interval_duration.as_millis())
459 .expect("arrangement_size_history_collection_interval must fit into u64");
460 let interval_ms = interval_ms.max(1);
462 let offset = rngs::SmallRng::from_seed(seed).random_range(0..interval_ms);
463 let now_ts: EpochMillis = self.peek_local_write_ts().await.into();
464
465 let previous_collection_ts = (now_ts - (now_ts % interval_ms)) + offset;
466 let next_collection_ts = if previous_collection_ts > now_ts {
467 previous_collection_ts
468 } else {
469 previous_collection_ts + interval_ms
470 };
471 let sleep_for = Duration::from_millis(next_collection_ts - now_ts);
472
473 let (capped_sleep, fire_snapshot) = if sleep_for <= MAX_SLEEP {
477 (sleep_for, true)
478 } else {
479 (MAX_SLEEP, false)
480 };
481
482 let internal_cmd_tx = self.internal_cmd_tx.clone();
483 task::spawn(|| "arrangement_sizes_collection", async move {
484 tokio::time::sleep(capped_sleep).await;
485 let msg = if fire_snapshot {
486 Message::ArrangementSizesSnapshot
487 } else {
488 Message::ArrangementSizesSchedule
489 };
490 let _ = internal_cmd_tx.send(msg);
492 });
493 }
494
495 #[mz_ore::instrument(level = "debug")]
508 async fn arrangement_sizes_snapshot(&self) {
509 if self.controller.read_only() {
515 self.schedule_arrangement_sizes_collection().await;
516 return;
517 }
518
519 let fresh_size_replicas = self.fresh_introspection_replicas(
520 IntrospectionType::ComputeObjectArrangementSizes,
521 ARRANGEMENT_SIZES_FRESHNESS_MARGIN,
522 );
523 let fresh_hydration_replicas = self.fresh_introspection_replicas(
524 IntrospectionType::ComputeHydrationTimes,
525 ARRANGEMENT_SIZES_FRESHNESS_MARGIN,
526 );
527 if fresh_size_replicas.is_empty() {
528 self.schedule_arrangement_sizes_collection().await;
531 return;
532 }
533
534 let live_item_id = self.catalog().resolve_builtin_storage_collection(
535 &mz_catalog::builtin::MZ_OBJECT_ARRANGEMENT_SIZES_UNIFIED,
536 );
537 let live_global_id = self.catalog.get_entry(&live_item_id).latest_global_id();
538 let hydration_item_id = self
539 .catalog()
540 .resolve_builtin_storage_collection(&mz_catalog::builtin::MZ_COMPUTE_HYDRATION_TIMES);
541 let hydration_global_id = self
542 .catalog
543 .get_entry(&hydration_item_id)
544 .latest_global_id();
545
546 let oracle = self.get_local_timestamp_oracle();
547 let storage_collections = Arc::clone(&self.controller.storage_collections);
548 let collection_metric = self
549 .metrics
550 .arrangement_sizes_collection_time_seconds
551 .clone();
552 let internal_cmd_tx = self.internal_cmd_tx.clone();
553
554 task::spawn(|| "arrangement_sizes_snapshot", async move {
555 let collection_metric_timer = collection_metric.start_timer();
556
557 let read_ts = oracle.read_ts().await;
563 let live_snapshot = match storage_collections.snapshot(live_global_id, read_ts).await {
564 Ok(s) => s,
565 Err(e) => {
566 soft_panic_or_log!("arrangement sizes snapshot failed: {e:?}");
570 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
571 return;
572 }
573 };
574 let hydration_snapshot = match storage_collections
575 .snapshot(hydration_global_id, read_ts)
576 .await
577 {
578 Ok(s) => s,
579 Err(e) => {
580 soft_panic_or_log!("arrangement sizes hydration snapshot failed: {e:?}");
581 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
582 return;
583 }
584 };
585
586 let records = arrangement_sizes_records(
587 live_snapshot,
588 hydration_snapshot,
589 &fresh_size_replicas,
590 &fresh_hydration_replicas,
591 );
592 collection_metric_timer.observe_duration();
593
594 let msg = if records.is_empty() {
595 Message::ArrangementSizesSchedule
596 } else {
597 Message::ArrangementSizesWrite(records)
598 };
599 let _ = internal_cmd_tx.send(msg);
601 });
602 }
603
604 #[mz_ore::instrument(level = "debug")]
608 async fn arrangement_sizes_write(&mut self, records: Vec<ArrangementSizeRecord>) {
609 let fresh_size_replicas = self.fresh_introspection_replicas(
613 IntrospectionType::ComputeObjectArrangementSizes,
614 ARRANGEMENT_SIZES_FRESHNESS_MARGIN,
615 );
616 let records: Vec<_> = records
617 .into_iter()
618 .filter(|record| fresh_size_replicas.contains(&record.replica_id))
619 .collect();
620 if records.is_empty() {
621 self.schedule_arrangement_sizes_collection().await;
622 return;
623 }
624
625 let collection_ts: EpochMillis = self.get_local_write_ts().await.timestamp.into();
630 let collection_datum = Datum::TimestampTz(
631 mz_ore::now::to_datetime(collection_ts)
632 .try_into()
633 .expect("collection_timestamp must fit into TimestampTz"),
634 );
635
636 let history_item_id = self
637 .catalog()
638 .resolve_builtin_table(&mz_catalog::builtin::MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY);
639
640 let updates: Vec<_> = records
641 .into_iter()
642 .map(|record| {
643 let row = Row::pack_slice(&[
644 Datum::String(&record.replica_id),
645 Datum::String(&record.object_id),
646 Datum::Int64(record.size),
647 collection_datum,
648 Datum::from(record.hydration_complete),
649 ]);
650 BuiltinTableUpdate::row(history_item_id, row, Diff::ONE)
651 })
652 .collect();
653
654 let row_count = updates.len();
655 self.metrics
656 .arrangement_sizes_rows_written
657 .inc_by(u64::cast_from(row_count));
658
659 let fut = self.builtin_table_update().execute(updates);
664 let internal_cmd_tx = self.internal_cmd_tx.clone();
665 let task_span = info_span!(parent: None, "coord::arrangement_sizes_write::table_updates");
666 OpenTelemetryContext::obtain().attach_as_parent_to(&task_span);
667 task::spawn(|| "arrangement_sizes_write_table_updates", async move {
668 fut.instrument(task_span).await;
669 if let Err(e) = internal_cmd_tx.send(Message::ArrangementSizesSchedule) {
670 warn!("internal_cmd_rx dropped before we could send: {e:?}");
671 }
672 });
673
674 tracing::debug!(
675 "appended {row_count} rows to mz_object_arrangement_size_history at ts {collection_ts}"
676 );
677 }
678
679 #[mz_ore::instrument(level = "debug")]
680 async fn arrangement_sizes_prune(&mut self, expired: Vec<BuiltinTableUpdate>) {
681 let fut = self.builtin_table_update().execute(expired);
682 task::spawn(|| "arrangement_sizes_pruning_apply", async move {
683 fut.await;
684 });
685 }
686
687 #[mz_ore::instrument(level = "debug")]
688 async fn message_command(&mut self, cmd: Command) {
689 self.handle_command(cmd).await;
690 }
691
692 #[mz_ore::instrument(level = "debug")]
693 async fn message_controller(&mut self, message: ControllerResponse) {
694 event!(Level::TRACE, message = format!("{:?}", message));
695 match message {
696 ControllerResponse::PeekNotification(uuid, response, otel_ctx) => {
697 self.handle_peek_notification(uuid, response, otel_ctx);
698 }
699 ControllerResponse::SubscribeResponse(sink_id, response) => {
700 if let Some(ActiveComputeSink::Subscribe(active_subscribe)) =
701 self.active_compute_sinks.get_mut(&sink_id)
702 {
703 let finished = active_subscribe.process_response(response);
704 let buffered_bytes = active_subscribe
715 .backlog_accounting
716 .lock()
717 .expect("subscribe backlog accounting poisoned")
718 .backlog_size();
719 let max_buffered_bytes = active_subscribe.max_buffered_bytes;
720
721 let reason = if finished {
722 Some(ActiveComputeSinkRetireReason::Finished)
723 } else if buffered_bytes > max_buffered_bytes {
724 Some(ActiveComputeSinkRetireReason::BufferExceeded {
725 buffered_bytes,
726 max_buffered_bytes,
727 })
728 } else {
729 None
730 };
731 if let Some(reason) = reason {
732 let retire_notify = self
733 .retire_compute_sinks(btreemap! {
734 sink_id => reason,
735 })
736 .await;
737 drop(retire_notify);
740 }
741
742 soft_assert_or_log!(
743 !self.introspection_subscribes.contains_key(&sink_id),
744 "`sink_id` {sink_id} unexpectedly found in both `active_subscribes` \
745 and `introspection_subscribes`",
746 );
747 } else if self.introspection_subscribes.contains_key(&sink_id) {
748 self.handle_introspection_subscribe_batch(sink_id, response)
749 .await;
750 } else {
751 }
754 }
755 ControllerResponse::CopyToResponse(sink_id, response) => {
756 match self.drop_compute_sink(sink_id).await {
757 Some((ActiveComputeSink::CopyTo(active_copy_to), _write_notify)) => {
758 active_copy_to.retire_with_response(response);
759 }
760 _ => {
761 }
764 }
765 }
766 ControllerResponse::WatchSetFinished(ws_ids) => {
767 let now = self.now();
768 for ws_id in ws_ids {
769 let Some((conn_id, rsp)) = self.installed_watch_sets.remove(&ws_id) else {
770 continue;
771 };
772 self.connection_watch_sets
773 .get_mut(&conn_id)
774 .expect("corrupted coordinator state: unknown connection id")
775 .remove(&ws_id);
776 if self.connection_watch_sets[&conn_id].is_empty() {
777 self.connection_watch_sets.remove(&conn_id);
778 }
779
780 match rsp {
781 WatchSetResponse::StatementDependenciesReady(id, ev) => {
782 self.record_statement_lifecycle_event(&id, &ev, now);
783 }
784 WatchSetResponse::AlterSinkReady(ctx) => {
785 self.sequence_alter_sink_finish(ctx).await;
786 }
787 WatchSetResponse::AlterMaterializedViewReady(ctx) => {
788 self.sequence_alter_materialized_view_apply_replacement_finish(ctx)
789 .await;
790 }
791 }
792 }
793 }
794 }
795 }
796
797 #[mz_ore::instrument(level = "debug")]
798 async fn message_purified_statement_ready(
799 &mut self,
800 PurifiedStatementReady {
801 ctx,
802 result,
803 params,
804 mut plan_validity,
805 original_stmt,
806 otel_ctx,
807 }: PurifiedStatementReady,
808 ) {
809 otel_ctx.attach_as_parent();
810
811 if plan_validity.check(self.catalog()).is_err() {
822 self.handle_execute_inner(original_stmt, params, ctx).await;
823 return;
824 }
825
826 let purified_statement = match result {
827 Ok(ok) => ok,
828 Err(e) => return ctx.retire(Err(e)),
829 };
830
831 let plan = match purified_statement {
832 PurifiedStatement::PurifiedCreateSource {
833 create_progress_subsource_stmt,
834 create_source_stmt,
835 subsources,
836 available_source_references,
837 } => self
838 .plan_purified_create_source(
839 &ctx,
840 params,
841 create_progress_subsource_stmt,
842 create_source_stmt,
843 subsources,
844 available_source_references,
845 )
846 .await
847 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
848 PurifiedStatement::PurifiedAlterSourceAddSubsources {
849 source_name,
850 options,
851 subsources,
852 } => self
853 .plan_purified_alter_source_add_subsource(
854 ctx.session(),
855 params,
856 source_name,
857 options,
858 subsources,
859 )
860 .await
861 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
862 PurifiedStatement::PurifiedAlterSourceRefreshReferences {
863 source_name,
864 available_source_references,
865 } => self
866 .plan_purified_alter_source_refresh_references(
867 ctx.session(),
868 params,
869 source_name,
870 available_source_references,
871 )
872 .map(|(plan, resolved_ids)| (plan, resolved_ids, ResolvedIds::empty())),
873 o @ (PurifiedStatement::PurifiedAlterSource { .. }
874 | PurifiedStatement::PurifiedCreateSink(..)
875 | PurifiedStatement::PurifiedCreateTableFromSource { .. }) => {
876 let stmt = match o {
878 PurifiedStatement::PurifiedAlterSource { alter_source_stmt } => {
879 Statement::AlterSource(alter_source_stmt)
880 }
881 PurifiedStatement::PurifiedCreateTableFromSource { stmt } => {
882 Statement::CreateTableFromSource(stmt)
883 }
884 PurifiedStatement::PurifiedCreateSink(stmt) => Statement::CreateSink(stmt),
885 PurifiedStatement::PurifiedCreateSource { .. }
886 | PurifiedStatement::PurifiedAlterSourceAddSubsources { .. }
887 | PurifiedStatement::PurifiedAlterSourceRefreshReferences { .. } => {
888 unreachable!("not part of exterior match stmt")
889 }
890 };
891
892 let catalog = self.catalog().for_session(ctx.session());
895 let resolved_ids = mz_sql::names::visit_dependencies(&catalog, &stmt);
896 self.plan_statement(ctx.session(), stmt, ¶ms, &resolved_ids)
897 .map(|(plan, sql_impl_ids)| (plan, resolved_ids, sql_impl_ids))
898 }
899 };
900
901 match plan {
902 Ok((plan, resolved_ids, sql_impl_ids)) => {
903 self.sequence_plan(ctx, plan, resolved_ids, sql_impl_ids)
904 .await
905 }
906 Err(e) => ctx.retire(Err(e)),
907 }
908 }
909
910 #[mz_ore::instrument(level = "debug")]
911 async fn message_create_connection_validation_ready(
912 &mut self,
913 CreateConnectionValidationReady {
914 mut ctx,
915 result,
916 connection_id,
917 connection_gid,
918 mut plan_validity,
919 otel_ctx,
920 resolved_ids,
921 }: CreateConnectionValidationReady,
922 ) {
923 otel_ctx.attach_as_parent();
924
925 if let Err(e) = plan_validity.check(self.catalog()) {
931 if self.secrets_controller.delete(connection_id).await.is_ok() {
932 self.caching_secrets_reader.invalidate(connection_id);
933 }
934 return ctx.retire(Err(e));
935 }
936
937 let plan = match result {
938 Ok(ok) => ok,
939 Err(e) => {
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
947 let result = self
948 .sequence_create_connection_stage_finish(
949 &mut ctx,
950 connection_id,
951 connection_gid,
952 plan,
953 resolved_ids,
954 )
955 .await;
956 ctx.retire(result);
957 }
958
959 #[mz_ore::instrument(level = "debug")]
960 async fn message_alter_connection_validation_ready(
961 &mut self,
962 AlterConnectionValidationReady {
963 mut ctx,
964 result,
965 connection_id,
966 connection_gid: _,
967 mut plan_validity,
968 otel_ctx,
969 resolved_ids: _,
970 }: AlterConnectionValidationReady,
971 ) {
972 otel_ctx.attach_as_parent();
973
974 if let Err(e) = plan_validity.check(self.catalog()) {
980 return ctx.retire(Err(e));
981 }
982
983 let conn = match result {
984 Ok(ok) => ok,
985 Err(e) => {
986 return ctx.retire(Err(e));
987 }
988 };
989
990 let result = self
991 .sequence_alter_connection_stage_finish(ctx.session_mut(), connection_id, conn)
992 .await;
993 ctx.retire(result);
994 }
995
996 #[mz_ore::instrument(level = "debug")]
997 async fn message_cluster_event(&mut self, event: ClusterEvent) {
998 event!(Level::TRACE, event = format!("{:?}", event));
999
1000 if let Some(segment_client) = &self.segment_client {
1001 let env_id = &self.catalog().config().environment_id;
1002 let mut properties = json!({
1003 "cluster_id": event.cluster_id.to_string(),
1004 "replica_id": event.replica_id.to_string(),
1005 "process_id": event.process_id,
1006 "status": event.status.as_kebab_case_str(),
1007 });
1008 match event.status {
1009 ClusterStatus::Online => (),
1010 ClusterStatus::Offline(reason) => {
1011 let properties = match &mut properties {
1012 serde_json::Value::Object(map) => map,
1013 _ => unreachable!(),
1014 };
1015 properties.insert(
1016 "reason".into(),
1017 json!(reason.display_or("unknown").to_string()),
1018 );
1019 }
1020 };
1021 segment_client.environment_track(
1022 env_id,
1023 "Cluster Changed Status",
1024 properties,
1025 EventDetails {
1026 timestamp: Some(event.time),
1027 ..Default::default()
1028 },
1029 );
1030 }
1031
1032 let Some(replica_statuses) = self
1035 .cluster_replica_statuses
1036 .try_get_cluster_replica_statuses(event.cluster_id, event.replica_id)
1037 else {
1038 return;
1039 };
1040
1041 let old_process_status = &replica_statuses[&event.process_id];
1042 let status_changed = event.status != old_process_status.status;
1043 let restart_count_changed = event.restart_count != old_process_status.restart_count;
1044
1045 if !status_changed && !restart_count_changed {
1057 return;
1058 }
1059
1060 if status_changed && !self.controller.read_only() {
1061 let offline_reason = match event.status {
1062 ClusterStatus::Online => None,
1063 ClusterStatus::Offline(None) => None,
1064 ClusterStatus::Offline(Some(reason)) => Some(reason.to_string()),
1065 };
1066 let row = Row::pack_slice(&[
1067 Datum::String(&event.replica_id.to_string()),
1068 Datum::UInt64(event.process_id),
1069 Datum::String(event.status.as_kebab_case_str()),
1070 Datum::from(offline_reason.as_deref()),
1071 Datum::TimestampTz(event.time.try_into().expect("must fit")),
1072 ]);
1073 self.controller.storage.append_introspection_updates(
1074 IntrospectionType::ReplicaStatusHistory,
1075 vec![(row, Diff::ONE)],
1076 );
1077 }
1078
1079 let old_replica_status = status_changed
1082 .then(|| ClusterReplicaStatuses::cluster_replica_status(replica_statuses));
1083
1084 let new_process_status = ClusterReplicaProcessStatus {
1085 status: event.status,
1086 restart_count: event.restart_count,
1087 time: event.time,
1088 };
1089 self.cluster_replica_statuses.ensure_cluster_status(
1090 event.cluster_id,
1091 event.replica_id,
1092 event.process_id,
1093 new_process_status,
1094 );
1095
1096 if !matches!(event.status, ClusterStatus::Online) || restart_count_changed {
1101 self.invalidate_introspection_freshness(event.replica_id);
1102 }
1103
1104 if let Some(old_replica_status) = old_replica_status {
1105 let cluster = self.catalog().get_cluster(event.cluster_id);
1106 let replica = cluster.replica(event.replica_id).expect("Replica exists");
1107 let new_replica_status = self
1108 .cluster_replica_statuses
1109 .get_cluster_replica_status(event.cluster_id, event.replica_id);
1110
1111 if old_replica_status != new_replica_status {
1112 let notifier = self.broadcast_notice_tx();
1113 let notice = AdapterNotice::ClusterReplicaStatusChanged {
1114 cluster: cluster.name.clone(),
1115 replica: replica.name.clone(),
1116 status: new_replica_status,
1117 time: event.time,
1118 };
1119 notifier(notice);
1120 }
1121 }
1122 }
1123
1124 #[mz_ore::instrument(level = "debug")]
1125 async fn message_linearize_reads(&mut self) {
1130 let mut shortest_wait = Duration::MAX;
1131 let mut ready_txns = Vec::new();
1132
1133 let mut cached_oracle_ts = BTreeMap::new();
1138
1139 for (conn_id, mut read_txn) in std::mem::take(&mut self.pending_linearize_read_txns) {
1140 if let TimestampContext::TimelineTimestamp {
1141 timeline,
1142 chosen_ts,
1143 oracle_ts,
1144 } = read_txn.timestamp_context()
1145 {
1146 let oracle_ts = match oracle_ts {
1147 Some(oracle_ts) => oracle_ts,
1148 None => {
1149 ready_txns.push(read_txn);
1151 continue;
1152 }
1153 };
1154
1155 if chosen_ts <= oracle_ts {
1156 ready_txns.push(read_txn);
1159 continue;
1160 }
1161
1162 let current_oracle_ts = cached_oracle_ts.entry(timeline.clone());
1164 let current_oracle_ts = match current_oracle_ts {
1165 btree_map::Entry::Vacant(entry) => {
1166 let timestamp_oracle = self.get_timestamp_oracle(timeline);
1167 let read_ts = timestamp_oracle.read_ts().await;
1168 entry.insert(read_ts.clone());
1169 read_ts
1170 }
1171 btree_map::Entry::Occupied(entry) => entry.get().clone(),
1172 };
1173
1174 if *chosen_ts <= current_oracle_ts {
1175 ready_txns.push(read_txn);
1176 } else {
1177 let wait =
1178 Duration::from_millis(chosen_ts.saturating_sub(current_oracle_ts).into());
1179 if wait < shortest_wait {
1180 shortest_wait = wait;
1181 }
1182 read_txn.num_requeues += 1;
1183 self.pending_linearize_read_txns.insert(conn_id, read_txn);
1184 }
1185 } else {
1186 ready_txns.push(read_txn);
1187 }
1188 }
1189
1190 if !ready_txns.is_empty() {
1191 let otel_ctx = ready_txns.first().expect("known to exist").otel_ctx.clone();
1194 let span = tracing::debug_span!("message_linearize_reads");
1195 otel_ctx.attach_as_parent_to(&span);
1196
1197 let now = Instant::now();
1198 for ready_txn in ready_txns {
1199 let span = tracing::debug_span!("retire_read_results");
1200 ready_txn.otel_ctx.attach_as_parent_to(&span);
1201 let _entered = span.enter();
1202 self.metrics
1203 .linearize_message_seconds
1204 .with_label_values(&[
1205 ready_txn.txn.label(),
1206 if ready_txn.num_requeues == 0 {
1207 "true"
1208 } else {
1209 "false"
1210 },
1211 ])
1212 .observe((now - ready_txn.created).as_secs_f64());
1213 if let Some((ctx, result)) = ready_txn.txn.finish() {
1214 ctx.retire(result);
1215 }
1216 }
1217 }
1218
1219 if !self.pending_linearize_read_txns.is_empty() {
1220 let remaining_ms = std::cmp::min(shortest_wait, Duration::from_millis(1_000));
1223 let linearize_reads_notify = Arc::clone(&self.linearize_reads_notify);
1224 task::spawn(|| "deferred_read_txns", async move {
1225 tokio::time::sleep(remaining_ms).await;
1226 linearize_reads_notify.notify_one();
1227 });
1228 }
1229 }
1230}
1231
1232fn arrangement_sizes_records(
1248 mut live_snapshot: Vec<(Row, StorageDiff)>,
1249 mut hydration_snapshot: Vec<(Row, StorageDiff)>,
1250 fresh_size_replicas: &BTreeSet<String>,
1251 fresh_hydration_replicas: &BTreeSet<String>,
1252) -> Vec<ArrangementSizeRecord> {
1253 differential_dataflow::consolidation::consolidate(&mut live_snapshot);
1254 differential_dataflow::consolidation::consolidate(&mut hydration_snapshot);
1255
1256 let mut datum_vec = mz_repr::DatumVec::new();
1257
1258 const HYDRATION_COL_REPLICA_ID: usize = 0;
1260 const HYDRATION_COL_OBJECT_ID: usize = 1;
1261 const HYDRATION_COL_TIME_NS: usize = 2;
1262 const HYDRATION_COL_COUNT: usize = 3;
1263
1264 let mut hydrated: BTreeSet<(String, String)> = BTreeSet::new();
1265 for (row, diff) in &hydration_snapshot {
1266 if *diff != 1 {
1267 continue;
1268 }
1269 let datums = datum_vec.borrow_with(row);
1270 if datums.len() < HYDRATION_COL_COUNT {
1271 continue;
1272 }
1273 if datums[HYDRATION_COL_TIME_NS].is_null() {
1274 continue;
1275 }
1276 let replica_id = datums[HYDRATION_COL_REPLICA_ID].unwrap_str();
1277 if !fresh_hydration_replicas.contains(replica_id) {
1278 continue;
1279 }
1280 hydrated.insert((
1281 replica_id.to_string(),
1282 datums[HYDRATION_COL_OBJECT_ID].unwrap_str().to_string(),
1283 ));
1284 }
1285
1286 const LIVE_COL_REPLICA_ID: usize = 0;
1288 const LIVE_COL_OBJECT_ID: usize = 1;
1289 const LIVE_COL_SIZE: usize = 2;
1290 const LIVE_COL_COUNT: usize = 3;
1291
1292 let mut skipped_malformed: u64 = 0;
1293 let mut skipped_null_size: u64 = 0;
1294 let mut skipped_zero_size: u64 = 0;
1295 let mut skipped_stale_replica: u64 = 0;
1296 let mut records = Vec::with_capacity(live_snapshot.len());
1297 for (row, diff) in &live_snapshot {
1298 if *diff != 1 {
1299 continue;
1300 }
1301 let datums = datum_vec.borrow_with(row);
1302 if datums.len() != LIVE_COL_COUNT {
1305 skipped_malformed += 1;
1306 continue;
1307 }
1308 let replica_id = datums[LIVE_COL_REPLICA_ID].unwrap_str();
1309 if !fresh_size_replicas.contains(replica_id) {
1310 skipped_stale_replica += 1;
1311 continue;
1312 }
1313 let object_id = datums[LIVE_COL_OBJECT_ID].unwrap_str();
1314 let size_datum = datums[LIVE_COL_SIZE];
1315 if size_datum.is_null() {
1318 skipped_null_size += 1;
1319 continue;
1320 }
1321 if size_datum.unwrap_int64() == 0 {
1325 skipped_zero_size += 1;
1326 continue;
1327 }
1328 let hydration_complete =
1329 hydrated.contains(&(replica_id.to_string(), object_id.to_string()));
1330 records.push(ArrangementSizeRecord {
1331 replica_id: replica_id.to_string(),
1332 object_id: object_id.to_string(),
1333 size: size_datum.unwrap_int64(),
1334 hydration_complete,
1335 });
1336 }
1337 if skipped_malformed > 0 {
1338 warn!(
1339 "mz_object_arrangement_sizes schema drift: skipped {skipped_malformed} rows \
1340 with unexpected arity"
1341 );
1342 }
1343 if skipped_null_size > 0 {
1344 tracing::debug!("skipped {skipped_null_size} live rows with null size");
1345 }
1346 if skipped_zero_size > 0 {
1347 tracing::debug!("skipped {skipped_zero_size} live rows with zero size");
1348 }
1349 if skipped_stale_replica > 0 {
1350 tracing::debug!(
1351 "skipped {skipped_stale_replica} live rows from replicas without fresh \
1352 introspection data"
1353 );
1354 }
1355 records
1356}
1357
1358#[cfg(test)]
1359mod arrangement_sizes_records_tests {
1360 use std::collections::BTreeSet;
1361
1362 use mz_repr::{Datum, Row};
1363
1364 use super::arrangement_sizes_records;
1365
1366 fn live_row(replica_id: &str, object_id: &str, size: Option<i64>) -> Row {
1367 Row::pack_slice(&[
1368 Datum::String(replica_id),
1369 Datum::String(object_id),
1370 size.map_or(Datum::Null, Datum::Int64),
1371 ])
1372 }
1373
1374 fn hydration_row(replica_id: &str, object_id: &str, hydrated: bool) -> Row {
1375 Row::pack_slice(&[
1376 Datum::String(replica_id),
1377 Datum::String(object_id),
1378 if hydrated {
1379 Datum::UInt64(1)
1380 } else {
1381 Datum::Null
1382 },
1383 ])
1384 }
1385
1386 fn replicas(ids: &[&str]) -> BTreeSet<String> {
1387 ids.iter().map(|id| id.to_string()).collect()
1388 }
1389
1390 #[mz_ore::test]
1391 fn hydration_flag_per_pair() {
1392 let live = vec![
1393 (live_row("u1", "u100", Some(10)), 1),
1394 (live_row("u1", "u200", Some(20)), 1),
1395 ];
1396 let hydration = vec![
1397 (hydration_row("u1", "u100", true), 1),
1398 (hydration_row("u1", "u200", false), 1),
1399 ];
1400 let fresh = replicas(&["u1"]);
1401 let records = arrangement_sizes_records(live, hydration, &fresh, &fresh);
1402 assert_eq!(records.len(), 2);
1403 assert!(
1404 records
1405 .iter()
1406 .any(|r| r.object_id == "u100" && r.hydration_complete)
1407 );
1408 assert!(
1409 records
1410 .iter()
1411 .any(|r| r.object_id == "u200" && !r.hydration_complete)
1412 );
1413 }
1414
1415 #[mz_ore::test]
1416 fn skips_malformed_null_and_retracted() {
1417 let live = vec![
1418 (Row::pack_slice(&[Datum::String("u1")]), 1),
1420 (live_row("u1", "u100", None), 1),
1422 (live_row("u1", "u200", Some(20)), 1),
1424 (live_row("u1", "u200", Some(20)), -1),
1425 (live_row("u1", "u300", Some(30)), 1),
1426 ];
1427 let fresh = replicas(&["u1"]);
1428 let records = arrangement_sizes_records(live, Vec::new(), &fresh, &fresh);
1429 assert_eq!(records.len(), 1);
1430 assert_eq!(records[0].object_id, "u300");
1431 assert_eq!(records[0].size, 30);
1432 assert!(!records[0].hydration_complete);
1433 }
1434
1435 #[mz_ore::test]
1436 fn skips_zero_size_rows() {
1437 let live = vec![
1440 (live_row("u1", "u100", Some(0)), 1),
1441 (live_row("u1", "u200", Some(10485760)), 1),
1442 ];
1443 let fresh = replicas(&["u1"]);
1444 let records = arrangement_sizes_records(live, Vec::new(), &fresh, &fresh);
1445 assert_eq!(records.len(), 1);
1446 assert_eq!(records[0].object_id, "u200");
1447 }
1448
1449 #[mz_ore::test]
1450 fn skips_rows_from_stale_replicas() {
1451 let live = vec![
1453 (live_row("u1", "u100", Some(10)), 1),
1454 (live_row("u2", "u100", Some(99)), 1),
1455 ];
1456 let hydration = vec![
1457 (hydration_row("u1", "u100", true), 1),
1458 (hydration_row("u2", "u100", true), 1),
1459 ];
1460 let fresh = replicas(&["u1"]);
1461 let records = arrangement_sizes_records(live, hydration, &fresh, &fresh);
1462 assert_eq!(records.len(), 1);
1463 assert_eq!(records[0].replica_id, "u1");
1464 assert!(records[0].hydration_complete);
1465 }
1466
1467 #[mz_ore::test]
1468 fn stale_hydration_data_is_not_trusted() {
1469 let live = vec![(live_row("u1", "u100", Some(10)), 1)];
1472 let hydration = vec![(hydration_row("u1", "u100", true), 1)];
1473 let records =
1474 arrangement_sizes_records(live, hydration, &replicas(&["u1"]), &replicas(&[]));
1475 assert_eq!(records.len(), 1);
1476 assert!(!records[0].hydration_complete);
1477 }
1478}