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, Timestamp};
31use mz_sql::ast::Statement;
32use mz_sql::pure::PurifiedStatement;
33use mz_storage_client::controller::IntrospectionType;
34use mz_storage_types::StorageDiff;
35use mz_storage_types::sources::Timeline;
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::{AdapterError, AdapterNotice};
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::SourceStatementReady {
91 pending,
92 response,
93 otel_ctx,
94 } => {
95 otel_ctx.attach_as_parent();
96 self.sequence_source_statement_ready(pending, Some(response))
97 .boxed_local()
98 .await;
99 }
100 Message::DdlCommandsReady { pending, otel_ctx } => {
101 otel_ctx.attach_as_parent();
102 self.sequence_ddl_commit(pending).boxed_local().await;
103 }
104 Message::PurifiedStatementReady(ready) => {
105 self.message_purified_statement_ready(ready)
106 .boxed_local()
107 .await
108 }
109 Message::CreateConnectionValidationReady(ready) => {
110 self.message_create_connection_validation_ready(ready)
111 .boxed_local()
112 .await
113 }
114 Message::AlterConnectionValidationReady(ready) => {
115 self.message_alter_connection_validation_ready(ready)
116 .boxed_local()
117 .await
118 }
119 Message::GroupCommitInitiate(span, permit) => {
120 tracing::Span::current().add_link(span.context().span().span_context().clone());
122 span.in_scope(|| self.stage_group_commit(permit));
123 }
124 Message::GroupCommitApplied {
125 responses,
126 statement_logging_ids,
127 internal_results,
128 write_ts,
129 } => {
130 for id in statement_logging_ids {
133 self.set_statement_execution_timestamp(id, write_ts);
134 }
135 for response in responses {
136 let (mut ctx, result) = response.finalize();
137 ctx.session_mut().apply_write(write_ts);
138 ctx.retire(result);
139 }
140 self.downgrade_local_read_holds(write_ts);
143 self.retire_local_reads_linearized_by(write_ts);
144 self.advance_custom_timelines().boxed_local().await;
145 for result in internal_results {
146 result.send(crate::coord::appends::WriteResult::Success {
147 timestamp: write_ts,
148 });
149 }
150 }
151 Message::AdvanceTimelines => {
152 let read_ts = self.get_local_read_ts().await;
156 self.downgrade_local_read_holds(read_ts);
157 self.advance_custom_timelines().boxed_local().await;
158 }
159 Message::ClusterEvent(event) => self.message_cluster_event(event).boxed_local().await,
160 Message::LinearizeReads => {
161 self.message_linearize_reads().boxed_local().await;
162 }
163 Message::StagedBatches {
164 conn_id,
165 table_id,
166 batches,
167 } => {
168 self.commit_staged_batches(conn_id, table_id, batches);
169 }
170 Message::StorageUsageSchedule => {
171 self.schedule_storage_usage_collection().boxed_local().await;
172 }
173 Message::StorageUsageFetch => {
174 self.storage_usage_fetch().boxed_local().await;
175 }
176 Message::StorageUsageUpdate(sizes) => {
177 self.storage_usage_update(sizes).boxed_local().await;
178 }
179 Message::StorageUsagePrune(expired) => {
180 self.storage_usage_prune(expired).boxed_local().await;
181 }
182 Message::ArrangementSizesSchedule => {
183 self.schedule_arrangement_sizes_collection();
184 }
185 Message::ArrangementSizesSnapshot => {
186 self.arrangement_sizes_snapshot();
187 }
188 Message::ArrangementSizesWrite {
189 records,
190 collection_ts,
191 } => {
192 self.arrangement_sizes_write(records, collection_ts);
193 }
194 Message::ArrangementSizesPrune(expired) => {
195 self.arrangement_sizes_prune(expired);
196 }
197 Message::HydrationHistorySchedule => {
198 self.schedule_hydration_history_collection();
199 }
200 Message::HydrationHistoryRun => {
201 self.run_hydration_history_collection();
202 }
203 Message::CaughtUpCheck(request) => {
204 self.handle_caught_up_check_request(request);
205 }
206 Message::RetireExecute {
207 otel_ctx,
208 data,
209 reason,
210 } => {
211 otel_ctx.attach_as_parent();
212 self.retire_execution(reason, data);
213 }
214 Message::ExecuteSingleStatementTransaction {
215 ctx,
216 otel_ctx,
217 stmt,
218 params,
219 } => {
220 otel_ctx.attach_as_parent();
221 self.sequence_execute_single_statement_transaction(ctx, stmt, params)
222 .boxed_local()
223 .await;
224 }
225 Message::CreateIndexStageReady { ctx, span, stage } => {
226 self.sequence_staged(ctx, span, stage).boxed_local().await;
227 }
228 Message::CreateMetricSinkStageReady { ctx, span, stage } => {
229 self.sequence_staged(ctx, span, stage).boxed_local().await;
230 }
231 Message::CreateViewStageReady { ctx, span, stage } => {
232 self.sequence_staged(ctx, span, stage).boxed_local().await;
233 }
234 Message::CreateMaterializedViewStageReady { ctx, span, stage } => {
235 self.sequence_staged(ctx, span, stage).boxed_local().await;
236 }
237 Message::IntrospectionSubscribeStageReady { span, stage } => {
238 self.sequence_staged((), span, stage).boxed_local().await;
239 }
240 Message::MetricSinkStageReady { span, stage } => {
241 self.sequence_staged((), span, stage).boxed_local().await;
242 }
243 Message::SecretStageReady { ctx, span, stage } => {
244 self.sequence_staged(ctx, span, stage).boxed_local().await;
245 }
246 Message::ClusterStageReady { ctx, span, stage } => {
247 self.sequence_staged(ctx, span, stage).boxed_local().await;
248 }
249 Message::DrainStatementLog => {
250 self.drain_statement_log();
251 }
252 Message::PrivateLinkVpcEndpointEvents(events) => {
253 if !self.controller.read_only() {
254 self.controller.storage.append_introspection_updates(
255 IntrospectionType::PrivatelinkConnectionStatusHistory,
256 events
257 .into_iter()
258 .map(|e| (mz_repr::Row::from(e), Diff::ONE))
259 .collect(),
260 );
261 }
262 }
263 Message::ClusterControllerRequest(request) => {
264 self.handle_cluster_controller_request(request)
265 .boxed_local()
266 .await;
267 }
268 Message::DeferredStatementReady => {
269 self.handle_deferred_statement().boxed_local().await;
270 }
271 }
272 }
273
274 #[mz_ore::instrument(level = "debug")]
275 pub async fn storage_usage_fetch(&self) {
276 if self.controller.read_only() {
283 tracing::info!("skipping storage usage collection in read-only mode");
284 if let Err(e) = self.internal_cmd_tx.send(Message::StorageUsageSchedule) {
285 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
286 }
287 return;
288 }
289
290 let internal_cmd_tx = self.internal_cmd_tx.clone();
291 let client = self.storage_usage_client.clone();
292
293 let live_shards: BTreeSet<_> = self
295 .controller
296 .storage
297 .active_collection_metadatas()
298 .into_iter()
299 .map(|(_id, m)| m.data_shard)
300 .collect();
301
302 let collection_metric = self.metrics.storage_usage_collection_time_seconds.clone();
303
304 task::spawn(|| "storage_usage_fetch", async move {
307 let collection_metric_timer = collection_metric.start_timer();
308 let shard_sizes = client.shards_usage_referenced(live_shards).await;
309 collection_metric_timer.observe_duration();
310
311 if let Err(e) = internal_cmd_tx.send(Message::StorageUsageUpdate(shard_sizes)) {
314 warn!("internal_cmd_rx dropped before we could send: {:?}", e);
315 }
316 });
317 }
318
319 #[mz_ore::instrument(level = "debug")]
320 async fn storage_usage_update(&mut self, shards_usage: ShardsUsageReferenced) {
321 let write_ts = self.get_catalog_write_ts().await;
329 let collection_timestamp: EpochMillis = write_ts.into();
330
331 let batch_id = match self.catalog().allocate_storage_usage_id(write_ts).await {
337 Ok(id) => id,
338 Err(err) => {
339 tracing::warn!("failed to allocate storage usage batch id: {:?}", err);
340 return;
341 }
342 };
343
344 let updates: Vec<_> = shards_usage
345 .by_shard
346 .into_iter()
347 .map(|(shard_id, shard_usage)| {
348 let event = VersionedStorageUsage::new(
349 batch_id,
350 Some(shard_id.to_string()),
351 shard_usage.size_bytes(),
352 collection_timestamp,
353 );
354 self.catalog().pack_storage_usage_update(event, Diff::ONE)
355 })
356 .collect();
357
358 let table_updates = self.builtin_table_update().execute(updates);
359
360 let internal_cmd_tx = self.internal_cmd_tx.clone();
361 let task_span = info_span!(parent: None, "coord::storage_usage_update::table_updates");
362 OpenTelemetryContext::obtain().attach_as_parent_to(&task_span);
363 task::spawn(|| "storage_usage_update_table_updates", async move {
364 table_updates.instrument(task_span).await;
365 if let Err(e) = internal_cmd_tx.send(Message::StorageUsageSchedule) {
367 warn!("internal_cmd_rx dropped before we could send: {e:?}");
368 }
369 });
370 }
371
372 #[mz_ore::instrument(level = "debug")]
373 async fn storage_usage_prune(&mut self, expired: Vec<BuiltinTableUpdate>) {
374 let fut = self.builtin_table_update().execute(expired);
375 task::spawn(|| "storage_usage_pruning_apply", async move {
376 fut.await;
377 });
378 }
379
380 pub async fn schedule_storage_usage_collection(&self) {
381 const SEED_LEN: usize = 32;
389 let mut seed = [0; SEED_LEN];
390 for (i, byte) in self
391 .catalog()
392 .state()
393 .config()
394 .environment_id
395 .organization_id()
396 .as_bytes()
397 .into_iter()
398 .take(SEED_LEN)
399 .enumerate()
400 {
401 seed[i] = *byte;
402 }
403 let storage_usage_collection_interval_ms: EpochMillis =
404 EpochMillis::try_from(self.storage_usage_collection_interval.as_millis())
405 .expect("storage usage collection interval must fit into u64");
406 let offset =
407 rngs::SmallRng::from_seed(seed).random_range(0..storage_usage_collection_interval_ms);
408 let now_ts: EpochMillis = self.peek_local_write_ts().await.into();
409
410 let previous_collection_ts =
412 (now_ts - (now_ts % storage_usage_collection_interval_ms)) + offset;
413 let next_collection_ts = if previous_collection_ts > now_ts {
414 previous_collection_ts
415 } else {
416 previous_collection_ts + storage_usage_collection_interval_ms
417 };
418 let next_collection_interval = Duration::from_millis(next_collection_ts - now_ts);
419
420 let internal_cmd_tx = self.internal_cmd_tx.clone();
422 task::spawn(|| "storage_usage_collection", async move {
423 tokio::time::sleep(next_collection_interval).await;
424 if internal_cmd_tx.send(Message::StorageUsageFetch).is_err() {
425 }
427 });
428 }
429
430 pub fn schedule_arrangement_sizes_collection(&self) {
438 const MAX_SLEEP: Duration = Duration::from_secs(60);
439
440 let interval_duration =
441 mz_adapter_types::dyncfgs::ARRANGEMENT_SIZE_HISTORY_COLLECTION_INTERVAL
442 .get(self.catalog().system_config().dyncfgs());
443
444 if interval_duration.is_zero() {
447 let internal_cmd_tx = self.internal_cmd_tx.clone();
448 task::spawn(|| "arrangement_sizes_collection_disabled", async move {
449 tokio::time::sleep(MAX_SLEEP).await;
450 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
451 });
452 return;
453 }
454
455 const SEED_LEN: usize = 32;
456 let mut seed = [0; SEED_LEN];
457 for (i, byte) in self
458 .catalog()
459 .state()
460 .config()
461 .environment_id
462 .organization_id()
463 .as_bytes()
464 .into_iter()
465 .take(SEED_LEN)
466 .enumerate()
467 {
468 seed[i] = *byte;
469 }
470 let interval_ms: EpochMillis = EpochMillis::try_from(interval_duration.as_millis())
471 .expect("arrangement_size_history_collection_interval must fit into u64");
472 let interval_ms = interval_ms.max(1);
474 let offset = rngs::SmallRng::from_seed(seed).random_range(0..interval_ms);
475
476 let oracle = self.get_local_timestamp_oracle();
479 let internal_cmd_tx = self.internal_cmd_tx.clone();
480 task::spawn(|| "arrangement_sizes_collection", async move {
481 let now_ts: EpochMillis = oracle.peek_write_ts().await.into();
482
483 let previous_collection_ts = (now_ts - (now_ts % interval_ms)) + offset;
484 let next_collection_ts = if previous_collection_ts > now_ts {
485 previous_collection_ts
486 } else {
487 previous_collection_ts + interval_ms
488 };
489 let sleep_for = Duration::from_millis(next_collection_ts - now_ts);
490
491 let (capped_sleep, fire_snapshot) = if sleep_for <= MAX_SLEEP {
495 (sleep_for, true)
496 } else {
497 (MAX_SLEEP, false)
498 };
499
500 tokio::time::sleep(capped_sleep).await;
501 let msg = if fire_snapshot {
502 Message::ArrangementSizesSnapshot
503 } else {
504 Message::ArrangementSizesSchedule
505 };
506 let _ = internal_cmd_tx.send(msg);
508 });
509 }
510
511 #[mz_ore::instrument(level = "debug")]
525 fn arrangement_sizes_snapshot(&self) {
526 if self.controller.read_only() {
532 self.schedule_arrangement_sizes_collection();
533 return;
534 }
535
536 let fresh_size_replicas = self.fresh_introspection_replicas(
537 IntrospectionType::ComputeObjectArrangementSizes,
538 ARRANGEMENT_SIZES_FRESHNESS_MARGIN,
539 );
540 let fresh_hydration_replicas = self.fresh_introspection_replicas(
541 IntrospectionType::ComputeHydrationTimes,
542 ARRANGEMENT_SIZES_FRESHNESS_MARGIN,
543 );
544 if fresh_size_replicas.is_empty() {
545 self.schedule_arrangement_sizes_collection();
548 return;
549 }
550
551 let live_item_id = self.catalog().resolve_builtin_storage_collection(
552 &mz_catalog::builtin::MZ_OBJECT_ARRANGEMENT_SIZES_UNIFIED,
553 );
554 let live_global_id = self.catalog.get_entry(&live_item_id).latest_global_id();
555 let hydration_item_id = self
556 .catalog()
557 .resolve_builtin_storage_collection(&mz_catalog::builtin::MZ_COMPUTE_HYDRATION_TIMES);
558 let hydration_global_id = self
559 .catalog
560 .get_entry(&hydration_item_id)
561 .latest_global_id();
562
563 let oracle = self.get_local_timestamp_oracle();
564 let storage_collections = Arc::clone(&self.controller.storage_collections);
565 let collection_metric = self
566 .metrics
567 .arrangement_sizes_collection_time_seconds
568 .clone();
569 let internal_cmd_tx = self.internal_cmd_tx.clone();
570
571 task::spawn(|| "arrangement_sizes_snapshot", async move {
572 let collection_metric_timer = collection_metric.start_timer();
573
574 let read_ts = oracle.read_ts().await;
580 let live_snapshot = match storage_collections.snapshot(live_global_id, read_ts).await {
581 Ok(s) => s,
582 Err(e) => {
583 soft_panic_or_log!("arrangement sizes snapshot failed: {e:?}");
587 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
588 return;
589 }
590 };
591 let hydration_snapshot = match storage_collections
592 .snapshot(hydration_global_id, read_ts)
593 .await
594 {
595 Ok(s) => s,
596 Err(e) => {
597 soft_panic_or_log!("arrangement sizes hydration snapshot failed: {e:?}");
598 let _ = internal_cmd_tx.send(Message::ArrangementSizesSchedule);
599 return;
600 }
601 };
602
603 let records = arrangement_sizes_records(
604 live_snapshot,
605 hydration_snapshot,
606 &fresh_size_replicas,
607 &fresh_hydration_replicas,
608 );
609 collection_metric_timer.observe_duration();
610
611 let msg = if records.is_empty() {
612 Message::ArrangementSizesSchedule
613 } else {
614 Message::ArrangementSizesWrite {
620 records,
621 collection_ts: read_ts.into(),
622 }
623 };
624 let _ = internal_cmd_tx.send(msg);
626 });
627 }
628
629 #[mz_ore::instrument(level = "debug")]
634 fn arrangement_sizes_write(
635 &mut self,
636 records: Vec<ArrangementSizeRecord>,
637 collection_ts: EpochMillis,
638 ) {
639 let fresh_size_replicas = self.fresh_introspection_replicas(
643 IntrospectionType::ComputeObjectArrangementSizes,
644 ARRANGEMENT_SIZES_FRESHNESS_MARGIN,
645 );
646 let records: Vec<_> = records
647 .into_iter()
648 .filter(|record| fresh_size_replicas.contains(&record.replica_id))
649 .collect();
650 if records.is_empty() {
651 self.schedule_arrangement_sizes_collection();
652 return;
653 }
654
655 let collection_datum = Datum::TimestampTz(
656 mz_ore::now::to_datetime(collection_ts)
657 .try_into()
658 .expect("collection_timestamp must fit into TimestampTz"),
659 );
660
661 let history_item_id = self
662 .catalog()
663 .resolve_builtin_table(&mz_catalog::builtin::MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY);
664
665 let updates: Vec<_> = records
666 .into_iter()
667 .map(|record| {
668 let row = Row::pack_slice(&[
669 Datum::String(&record.replica_id),
670 Datum::String(&record.object_id),
671 Datum::Int64(record.size),
672 collection_datum,
673 Datum::from(record.hydration_complete),
674 ]);
675 BuiltinTableUpdate::row(history_item_id, row, Diff::ONE)
676 })
677 .collect();
678
679 let row_count = updates.len();
680 self.metrics
681 .arrangement_sizes_rows_written
682 .inc_by(u64::cast_from(row_count));
683
684 let fut = self.builtin_table_update().execute(updates);
689 let internal_cmd_tx = self.internal_cmd_tx.clone();
690 let task_span = info_span!(parent: None, "coord::arrangement_sizes_write::table_updates");
691 OpenTelemetryContext::obtain().attach_as_parent_to(&task_span);
692 task::spawn(|| "arrangement_sizes_write_table_updates", async move {
693 fut.instrument(task_span).await;
694 if let Err(e) = internal_cmd_tx.send(Message::ArrangementSizesSchedule) {
695 warn!("internal_cmd_rx dropped before we could send: {e:?}");
696 }
697 });
698
699 tracing::debug!(
700 "appended {row_count} rows to mz_object_arrangement_size_history at ts {collection_ts}"
701 );
702 }
703
704 #[mz_ore::instrument(level = "debug")]
705 fn arrangement_sizes_prune(&mut self, expired: Vec<BuiltinTableUpdate>) {
706 let fut = self.builtin_table_update().execute(expired);
707 task::spawn(|| "arrangement_sizes_pruning_apply", async move {
708 fut.await;
709 });
710 }
711
712 #[mz_ore::instrument(level = "debug")]
713 async fn message_command(&mut self, cmd: Command) {
714 self.handle_command(cmd).await;
715 }
716
717 #[mz_ore::instrument(level = "debug")]
718 async fn message_controller(&mut self, message: ControllerResponse) {
719 event!(Level::TRACE, message = format!("{:?}", message));
720 match message {
721 ControllerResponse::PeekNotification(uuid, response, otel_ctx) => {
722 self.handle_peek_notification(uuid, response, otel_ctx);
723 }
724 ControllerResponse::SubscribeResponse(sink_id, response) => {
725 if let Some(ActiveComputeSink::Subscribe(active_subscribe)) =
726 self.active_compute_sinks.get_mut(&sink_id)
727 {
728 let finished = active_subscribe.process_response(response);
729 let buffered_bytes = active_subscribe
740 .backlog_accounting
741 .lock()
742 .expect("subscribe backlog accounting poisoned")
743 .backlog_size();
744 let max_buffered_bytes = active_subscribe.max_buffered_bytes;
745
746 let reason = if finished {
747 Some(ActiveComputeSinkRetireReason::Finished)
748 } else if buffered_bytes > max_buffered_bytes {
749 Some(ActiveComputeSinkRetireReason::BufferExceeded {
750 buffered_bytes,
751 max_buffered_bytes,
752 })
753 } else {
754 None
755 };
756 if let Some(reason) = reason {
757 let retire_notify = self
758 .retire_compute_sinks(btreemap! {
759 sink_id => reason,
760 })
761 .await;
762 drop(retire_notify);
765 }
766
767 soft_assert_or_log!(
768 !self.introspection_subscribes.contains_key(&sink_id),
769 "`sink_id` {sink_id} unexpectedly found in both `active_subscribes` \
770 and `introspection_subscribes`",
771 );
772 } else if self.introspection_subscribes.contains_key(&sink_id) {
773 self.handle_introspection_subscribe_batch(sink_id, response)
774 .await;
775 } else {
776 }
779 }
780 ControllerResponse::CopyToResponse(sink_id, response) => {
781 match self.drop_compute_sink(sink_id).await {
782 Some((ActiveComputeSink::CopyTo(active_copy_to), _write_notify)) => {
783 active_copy_to.retire_with_response(response);
784 }
785 _ => {
786 }
789 }
790 }
791 ControllerResponse::WatchSetFinished(ws_ids) => {
792 let now = self.now();
793 for ws_id in ws_ids {
794 let Some((conn_id, rsp)) = self.installed_watch_sets.remove(&ws_id) else {
795 continue;
796 };
797 self.connection_watch_sets
798 .get_mut(&conn_id)
799 .expect("corrupted coordinator state: unknown connection id")
800 .remove(&ws_id);
801 if self.connection_watch_sets[&conn_id].is_empty() {
802 self.connection_watch_sets.remove(&conn_id);
803 }
804
805 match rsp {
806 WatchSetResponse::StatementDependenciesReady(id, ev) => {
807 self.record_statement_lifecycle_event(&id, &ev, now);
808 }
809 WatchSetResponse::AlterSinkReady(ctx) => {
810 self.sequence_alter_sink_finish(ctx).await;
811 }
812 WatchSetResponse::AlterMaterializedViewReady(ctx) => {
813 self.sequence_alter_materialized_view_apply_replacement_finish(ctx)
814 .await;
815 }
816 }
817 }
818 }
819 }
820 }
821
822 #[mz_ore::instrument(level = "debug")]
823 async fn message_purified_statement_ready(
824 &mut self,
825 PurifiedStatementReady {
826 ctx,
827 result,
828 params,
829 mut plan_validity,
830 original_stmt,
831 otel_ctx,
832 }: PurifiedStatementReady,
833 ) {
834 otel_ctx.attach_as_parent();
835
836 if plan_validity.check(self.catalog()).is_err() {
847 self.handle_execute_inner(original_stmt, params, ctx).await;
848 return;
849 }
850
851 let purified_statement = match result {
852 Ok(ok) => ok,
853 Err(e) => return ctx.retire(Err(e)),
854 };
855
856 let plan = match purified_statement {
857 PurifiedStatement::PurifiedCreateSource { .. }
858 | PurifiedStatement::PurifiedAlterSourceAddSubsources { .. }
859 | PurifiedStatement::PurifiedAlterSourceRefreshReferences { .. }
860 | PurifiedStatement::PurifiedCreateTableFromSource { .. } => {
861 let msg = "source preparation must execute as a catalog command";
862 soft_panic_or_log!("{msg}");
863 Err(AdapterError::Internal(msg.into()))
864 }
865 o @ (PurifiedStatement::PurifiedAlterSource { .. }
866 | PurifiedStatement::PurifiedCreateSink(..)) => {
867 let stmt = match o {
869 PurifiedStatement::PurifiedAlterSource { alter_source_stmt } => {
870 Statement::AlterSource(alter_source_stmt)
871 }
872 PurifiedStatement::PurifiedCreateSink(stmt) => Statement::CreateSink(stmt),
873 PurifiedStatement::PurifiedCreateSource { .. }
874 | PurifiedStatement::PurifiedCreateTableFromSource { .. }
875 | PurifiedStatement::PurifiedAlterSourceAddSubsources { .. }
876 | PurifiedStatement::PurifiedAlterSourceRefreshReferences { .. } => {
877 unreachable!("not part of exterior match stmt")
878 }
879 };
880
881 let catalog = self.catalog().for_session(ctx.session());
884 let resolved_ids = mz_sql::names::visit_dependencies(&catalog, &stmt);
885 self.plan_statement(ctx.session(), stmt, ¶ms, &resolved_ids)
886 .map(|(plan, sql_impl_ids)| (plan, resolved_ids, sql_impl_ids))
887 }
888 };
889
890 match plan {
891 Ok((plan, resolved_ids, sql_impl_ids)) => {
892 self.sequence_plan(ctx, plan, resolved_ids, sql_impl_ids)
893 .await
894 }
895 Err(e) => ctx.retire(Err(e)),
896 }
897 }
898
899 #[mz_ore::instrument(level = "debug")]
900 async fn message_create_connection_validation_ready(
901 &mut self,
902 CreateConnectionValidationReady {
903 mut ctx,
904 result,
905 connection_id,
906 connection_gid,
907 mut plan_validity,
908 otel_ctx,
909 resolved_ids,
910 }: CreateConnectionValidationReady,
911 ) {
912 otel_ctx.attach_as_parent();
913
914 if let Err(e) = plan_validity.check(self.catalog()) {
920 if self.secrets_controller.delete(connection_id).await.is_ok() {
921 self.caching_secrets_reader.invalidate(connection_id);
922 }
923 return ctx.retire(Err(e));
924 }
925
926 let plan = match result {
927 Ok(ok) => ok,
928 Err(e) => {
929 if self.secrets_controller.delete(connection_id).await.is_ok() {
930 self.caching_secrets_reader.invalidate(connection_id);
931 }
932 return ctx.retire(Err(e));
933 }
934 };
935
936 let result = self
937 .sequence_create_connection_stage_finish(
938 &mut ctx,
939 connection_id,
940 connection_gid,
941 plan,
942 resolved_ids,
943 )
944 .await;
945 ctx.retire(result);
946 }
947
948 #[mz_ore::instrument(level = "debug")]
949 async fn message_alter_connection_validation_ready(
950 &mut self,
951 AlterConnectionValidationReady {
952 mut ctx,
953 result,
954 connection_id,
955 connection_gid: _,
956 mut plan_validity,
957 otel_ctx,
958 resolved_ids: _,
959 }: AlterConnectionValidationReady,
960 ) {
961 otel_ctx.attach_as_parent();
962
963 if let Err(e) = plan_validity.check(self.catalog()) {
969 return ctx.retire(Err(e));
970 }
971
972 let conn = match result {
973 Ok(ok) => ok,
974 Err(e) => {
975 return ctx.retire(Err(e));
976 }
977 };
978
979 let result = self
980 .sequence_alter_connection_stage_finish(ctx.session_mut(), connection_id, conn)
981 .await;
982 ctx.retire(result);
983 }
984
985 #[mz_ore::instrument(level = "debug")]
986 async fn message_cluster_event(&mut self, event: ClusterEvent) {
987 event!(Level::TRACE, event = format!("{:?}", event));
988
989 if let Some(segment_client) = &self.segment_client {
990 let env_id = &self.catalog().config().environment_id;
991 let mut properties = json!({
992 "cluster_id": event.cluster_id.to_string(),
993 "replica_id": event.replica_id.to_string(),
994 "process_id": event.process_id,
995 "status": event.status.as_kebab_case_str(),
996 });
997 match event.status {
998 ClusterStatus::Online => (),
999 ClusterStatus::Offline(reason) => {
1000 let properties = match &mut properties {
1001 serde_json::Value::Object(map) => map,
1002 _ => unreachable!(),
1003 };
1004 properties.insert(
1005 "reason".into(),
1006 json!(reason.display_or("unknown").to_string()),
1007 );
1008 }
1009 };
1010 segment_client.environment_track(
1011 env_id,
1012 "Cluster Changed Status",
1013 properties,
1014 EventDetails {
1015 timestamp: Some(event.time),
1016 ..Default::default()
1017 },
1018 );
1019 }
1020
1021 let Some(replica_statuses) = self
1024 .cluster_replica_statuses
1025 .try_get_cluster_replica_statuses(event.cluster_id, event.replica_id)
1026 else {
1027 return;
1028 };
1029
1030 let old_process_status = &replica_statuses[&event.process_id];
1031 let status_changed = event.status != old_process_status.status;
1032 let restart_count_changed = event.restart_count != old_process_status.restart_count;
1033
1034 if !status_changed && !restart_count_changed {
1046 return;
1047 }
1048
1049 if status_changed && !self.controller.read_only() {
1050 let offline_reason = match event.status {
1051 ClusterStatus::Online => None,
1052 ClusterStatus::Offline(None) => None,
1053 ClusterStatus::Offline(Some(reason)) => Some(reason.to_string()),
1054 };
1055 let row = Row::pack_slice(&[
1056 Datum::String(&event.replica_id.to_string()),
1057 Datum::UInt64(event.process_id),
1058 Datum::String(event.status.as_kebab_case_str()),
1059 Datum::from(offline_reason.as_deref()),
1060 Datum::TimestampTz(event.time.try_into().expect("must fit")),
1061 ]);
1062 self.controller.storage.append_introspection_updates(
1063 IntrospectionType::ReplicaStatusHistory,
1064 vec![(row, Diff::ONE)],
1065 );
1066 }
1067
1068 let old_replica_status = status_changed
1071 .then(|| ClusterReplicaStatuses::cluster_replica_status(replica_statuses));
1072
1073 let new_process_status = ClusterReplicaProcessStatus {
1074 status: event.status,
1075 restart_count: event.restart_count,
1076 time: event.time,
1077 };
1078 self.cluster_replica_statuses.ensure_cluster_status(
1079 event.cluster_id,
1080 event.replica_id,
1081 event.process_id,
1082 new_process_status,
1083 );
1084
1085 if !matches!(event.status, ClusterStatus::Online) || restart_count_changed {
1090 self.invalidate_introspection_freshness(event.replica_id);
1091 }
1092
1093 if let Some(old_replica_status) = old_replica_status {
1094 let cluster = self.catalog().get_cluster(event.cluster_id);
1095 let replica = cluster.replica(event.replica_id).expect("Replica exists");
1096 let new_replica_status = self
1097 .cluster_replica_statuses
1098 .get_cluster_replica_status(event.cluster_id, event.replica_id);
1099
1100 if old_replica_status != new_replica_status {
1101 let notifier = self.broadcast_notice_tx();
1102 let notice = AdapterNotice::ClusterReplicaStatusChanged {
1103 cluster: cluster.name.clone(),
1104 replica: replica.name.clone(),
1105 status: new_replica_status,
1106 time: event.time,
1107 };
1108 notifier(notice);
1109 }
1110 }
1111 }
1112
1113 fn retire_local_reads_linearized_by(&mut self, applied_ts: Timestamp) {
1115 if self.pending_linearize_read_txns.is_empty() {
1116 return;
1117 }
1118 let now = Instant::now();
1119 let (ready_txns, pending_txns) = std::mem::take(&mut self.pending_linearize_read_txns)
1120 .into_iter()
1121 .partition::<BTreeMap<_, _>, _>(|(_conn_id, read_txn)| {
1122 read_txn.linearized_by(&Timeline::EpochMilliseconds, &applied_ts)
1123 });
1124 self.pending_linearize_read_txns = pending_txns;
1125 for ready_txn in ready_txns.into_values() {
1126 ready_txn.finish_with_metrics(&self.metrics, now);
1127 }
1128 }
1129
1130 #[mz_ore::instrument(level = "debug")]
1131 async fn message_linearize_reads(&mut self) {
1136 let mut shortest_wait = Duration::MAX;
1137 let mut ready_txns = Vec::new();
1138
1139 let mut cached_oracle_ts = BTreeMap::new();
1144
1145 for (conn_id, mut read_txn) in std::mem::take(&mut self.pending_linearize_read_txns) {
1146 if let Some((timeline, chosen_ts)) = read_txn.timestamp_to_linearize() {
1147 let current_oracle_ts = cached_oracle_ts.entry(timeline.clone());
1149 let current_oracle_ts = match current_oracle_ts {
1150 btree_map::Entry::Vacant(entry) => {
1151 let timestamp_oracle = self.get_timestamp_oracle(timeline);
1152 let read_ts = timestamp_oracle.read_ts().await;
1153 entry.insert(read_ts.clone());
1154 read_ts
1155 }
1156 btree_map::Entry::Occupied(entry) => entry.get().clone(),
1157 };
1158
1159 if *chosen_ts <= current_oracle_ts {
1160 ready_txns.push(read_txn);
1161 } else {
1162 let wait =
1163 Duration::from_millis(chosen_ts.saturating_sub(current_oracle_ts).into());
1164 if wait < shortest_wait {
1165 shortest_wait = wait;
1166 }
1167 read_txn.num_requeues += 1;
1168 self.pending_linearize_read_txns.insert(conn_id, read_txn);
1169 }
1170 } else {
1171 ready_txns.push(read_txn);
1172 }
1173 }
1174
1175 if !ready_txns.is_empty() {
1176 let otel_ctx = ready_txns.first().expect("known to exist").otel_ctx.clone();
1179 let span = tracing::debug_span!("message_linearize_reads");
1180 otel_ctx.attach_as_parent_to(&span);
1181
1182 let now = Instant::now();
1183 for ready_txn in ready_txns {
1184 ready_txn.finish_with_metrics(&self.metrics, now);
1185 }
1186 }
1187
1188 if !self.pending_linearize_read_txns.is_empty() {
1189 let remaining_ms = std::cmp::min(shortest_wait, Duration::from_millis(1_000));
1192 let linearize_reads_notify = Arc::clone(&self.linearize_reads_notify);
1193 task::spawn(|| "deferred_read_txns", async move {
1194 tokio::time::sleep(remaining_ms).await;
1195 linearize_reads_notify.notify_one();
1196 });
1197 }
1198 }
1199}
1200
1201fn arrangement_sizes_records(
1217 mut live_snapshot: Vec<(Row, StorageDiff)>,
1218 mut hydration_snapshot: Vec<(Row, StorageDiff)>,
1219 fresh_size_replicas: &BTreeSet<String>,
1220 fresh_hydration_replicas: &BTreeSet<String>,
1221) -> Vec<ArrangementSizeRecord> {
1222 differential_dataflow::consolidation::consolidate(&mut live_snapshot);
1223 differential_dataflow::consolidation::consolidate(&mut hydration_snapshot);
1224
1225 let mut datum_vec = mz_repr::DatumVec::new();
1226
1227 const HYDRATION_COL_REPLICA_ID: usize = 0;
1229 const HYDRATION_COL_OBJECT_ID: usize = 1;
1230 const HYDRATION_COL_TIME_NS: usize = 2;
1231 const HYDRATION_COL_COUNT: usize = 3;
1232
1233 let mut hydrated: BTreeSet<(String, String)> = BTreeSet::new();
1234 for (row, diff) in &hydration_snapshot {
1235 if *diff != 1 {
1236 continue;
1237 }
1238 let datums = datum_vec.borrow_with(row);
1239 if datums.len() < HYDRATION_COL_COUNT {
1240 continue;
1241 }
1242 if datums[HYDRATION_COL_TIME_NS].is_null() {
1243 continue;
1244 }
1245 let replica_id = datums[HYDRATION_COL_REPLICA_ID].unwrap_str();
1246 if !fresh_hydration_replicas.contains(replica_id) {
1247 continue;
1248 }
1249 hydrated.insert((
1250 replica_id.to_string(),
1251 datums[HYDRATION_COL_OBJECT_ID].unwrap_str().to_string(),
1252 ));
1253 }
1254
1255 const LIVE_COL_REPLICA_ID: usize = 0;
1257 const LIVE_COL_OBJECT_ID: usize = 1;
1258 const LIVE_COL_SIZE: usize = 2;
1259 const LIVE_COL_COUNT: usize = 3;
1260
1261 let mut skipped_malformed: u64 = 0;
1262 let mut skipped_null_size: u64 = 0;
1263 let mut skipped_zero_size: u64 = 0;
1264 let mut skipped_stale_replica: u64 = 0;
1265 let mut records = Vec::with_capacity(live_snapshot.len());
1266 for (row, diff) in &live_snapshot {
1267 if *diff != 1 {
1268 continue;
1269 }
1270 let datums = datum_vec.borrow_with(row);
1271 if datums.len() != LIVE_COL_COUNT {
1274 skipped_malformed += 1;
1275 continue;
1276 }
1277 let replica_id = datums[LIVE_COL_REPLICA_ID].unwrap_str();
1278 if !fresh_size_replicas.contains(replica_id) {
1279 skipped_stale_replica += 1;
1280 continue;
1281 }
1282 let object_id = datums[LIVE_COL_OBJECT_ID].unwrap_str();
1283 let size_datum = datums[LIVE_COL_SIZE];
1284 if size_datum.is_null() {
1287 skipped_null_size += 1;
1288 continue;
1289 }
1290 if size_datum.unwrap_int64() == 0 {
1294 skipped_zero_size += 1;
1295 continue;
1296 }
1297 let hydration_complete =
1298 hydrated.contains(&(replica_id.to_string(), object_id.to_string()));
1299 records.push(ArrangementSizeRecord {
1300 replica_id: replica_id.to_string(),
1301 object_id: object_id.to_string(),
1302 size: size_datum.unwrap_int64(),
1303 hydration_complete,
1304 });
1305 }
1306 if skipped_malformed > 0 {
1307 warn!(
1308 "mz_object_arrangement_sizes schema drift: skipped {skipped_malformed} rows \
1309 with unexpected arity"
1310 );
1311 }
1312 if skipped_null_size > 0 {
1313 tracing::debug!("skipped {skipped_null_size} live rows with null size");
1314 }
1315 if skipped_zero_size > 0 {
1316 tracing::debug!("skipped {skipped_zero_size} live rows with zero size");
1317 }
1318 if skipped_stale_replica > 0 {
1319 tracing::debug!(
1320 "skipped {skipped_stale_replica} live rows from replicas without fresh \
1321 introspection data"
1322 );
1323 }
1324 records
1325}
1326
1327#[cfg(test)]
1328mod arrangement_sizes_records_tests {
1329 use std::collections::BTreeSet;
1330
1331 use mz_repr::{Datum, Row};
1332
1333 use super::arrangement_sizes_records;
1334
1335 fn live_row(replica_id: &str, object_id: &str, size: Option<i64>) -> Row {
1336 Row::pack_slice(&[
1337 Datum::String(replica_id),
1338 Datum::String(object_id),
1339 size.map_or(Datum::Null, Datum::Int64),
1340 ])
1341 }
1342
1343 fn hydration_row(replica_id: &str, object_id: &str, hydrated: bool) -> Row {
1344 Row::pack_slice(&[
1345 Datum::String(replica_id),
1346 Datum::String(object_id),
1347 if hydrated {
1348 Datum::UInt64(1)
1349 } else {
1350 Datum::Null
1351 },
1352 ])
1353 }
1354
1355 fn replicas(ids: &[&str]) -> BTreeSet<String> {
1356 ids.iter().map(|id| id.to_string()).collect()
1357 }
1358
1359 #[mz_ore::test]
1360 fn hydration_flag_per_pair() {
1361 let live = vec![
1362 (live_row("u1", "u100", Some(10)), 1),
1363 (live_row("u1", "u200", Some(20)), 1),
1364 ];
1365 let hydration = vec![
1366 (hydration_row("u1", "u100", true), 1),
1367 (hydration_row("u1", "u200", false), 1),
1368 ];
1369 let fresh = replicas(&["u1"]);
1370 let records = arrangement_sizes_records(live, hydration, &fresh, &fresh);
1371 assert_eq!(records.len(), 2);
1372 assert!(
1373 records
1374 .iter()
1375 .any(|r| r.object_id == "u100" && r.hydration_complete)
1376 );
1377 assert!(
1378 records
1379 .iter()
1380 .any(|r| r.object_id == "u200" && !r.hydration_complete)
1381 );
1382 }
1383
1384 #[mz_ore::test]
1385 fn skips_malformed_null_and_retracted() {
1386 let live = vec![
1387 (Row::pack_slice(&[Datum::String("u1")]), 1),
1389 (live_row("u1", "u100", None), 1),
1391 (live_row("u1", "u200", Some(20)), 1),
1393 (live_row("u1", "u200", Some(20)), -1),
1394 (live_row("u1", "u300", Some(30)), 1),
1395 ];
1396 let fresh = replicas(&["u1"]);
1397 let records = arrangement_sizes_records(live, Vec::new(), &fresh, &fresh);
1398 assert_eq!(records.len(), 1);
1399 assert_eq!(records[0].object_id, "u300");
1400 assert_eq!(records[0].size, 30);
1401 assert!(!records[0].hydration_complete);
1402 }
1403
1404 #[mz_ore::test]
1405 fn skips_zero_size_rows() {
1406 let live = vec![
1409 (live_row("u1", "u100", Some(0)), 1),
1410 (live_row("u1", "u200", Some(10485760)), 1),
1411 ];
1412 let fresh = replicas(&["u1"]);
1413 let records = arrangement_sizes_records(live, Vec::new(), &fresh, &fresh);
1414 assert_eq!(records.len(), 1);
1415 assert_eq!(records[0].object_id, "u200");
1416 }
1417
1418 #[mz_ore::test]
1419 fn skips_rows_from_stale_replicas() {
1420 let live = vec![
1422 (live_row("u1", "u100", Some(10)), 1),
1423 (live_row("u2", "u100", Some(99)), 1),
1424 ];
1425 let hydration = vec![
1426 (hydration_row("u1", "u100", true), 1),
1427 (hydration_row("u2", "u100", true), 1),
1428 ];
1429 let fresh = replicas(&["u1"]);
1430 let records = arrangement_sizes_records(live, hydration, &fresh, &fresh);
1431 assert_eq!(records.len(), 1);
1432 assert_eq!(records[0].replica_id, "u1");
1433 assert!(records[0].hydration_complete);
1434 }
1435
1436 #[mz_ore::test]
1437 fn stale_hydration_data_is_not_trusted() {
1438 let live = vec![(live_row("u1", "u100", Some(10)), 1)];
1441 let hydration = vec![(hydration_row("u1", "u100", true), 1)];
1442 let records =
1443 arrangement_sizes_records(live, hydration, &replicas(&["u1"]), &replicas(&[]));
1444 assert_eq!(records.len(), 1);
1445 assert!(!records[0].hydration_complete);
1446 }
1447}