1use std::collections::BTreeMap;
11use std::sync::{Arc, Weak};
12
13use differential_dataflow::consolidation::consolidate;
14use mz_compute_client::controller::error::{CollectionMissing, InstanceMissing};
15use mz_compute_client::controller::instance_client::InstanceClient;
16use mz_compute_client::controller::instance_client::{AcquireReadHoldsError, InstanceShutDown};
17use mz_compute_client::protocol::command::PeekTarget;
18use mz_compute_types::ComputeInstanceId;
19use mz_expr::row::RowCollection;
20use mz_ore::cast::CastFrom;
21use mz_persist_client::PersistClient;
22use mz_repr::GlobalId;
23use mz_repr::Timestamp;
24use mz_repr::global_id::TransientIdGen;
25use mz_repr::{RelationDesc, Row};
26use mz_sql::optimizer_metrics::OptimizerMetrics;
27use mz_sql::plan::Params;
28use mz_storage_types::sources::Timeline;
29use mz_timestamp_oracle::TimestampOracle;
30use prometheus::Histogram;
31use qcell::QCell;
32use thiserror::Error;
33use timely::progress::Antichain;
34use tokio::sync::oneshot;
35use uuid::Uuid;
36
37use crate::catalog::Catalog;
38use crate::command::{CatalogSnapshot, Command};
39use crate::coord::peek::FastPathPlan;
40use crate::coord::{Coordinator, ExecuteContextGuard};
41use crate::session::{LifecycleTimestamps, Session};
42use crate::statement_logging::{
43 FrontendStatementLoggingEvent, PreparedStatementEvent, PreparedStatementLoggingInfo,
44 StatementLoggingFrontend, StatementLoggingId, WatchSetCreation,
45};
46use crate::{AdapterError, Client, CollectionIdBundle, ReadHolds, statement_logging};
47
48pub type StorageCollectionsHandle =
50 Arc<dyn mz_storage_client::storage_collections::StorageCollections + Send + Sync>;
51
52#[derive(Debug)]
54pub struct PeekClient {
55 coordinator_client: Client,
56 catalog_cache: Weak<Catalog>,
63 compute_instances: BTreeMap<ComputeInstanceId, InstanceClient>,
68 pub storage_collections: StorageCollectionsHandle,
70 pub transient_id_gen: Arc<TransientIdGen>,
72 pub optimizer_metrics: OptimizerMetrics,
73 oracles: BTreeMap<Timeline, Arc<dyn TimestampOracle<Timestamp> + Send + Sync>>,
75 persist_client: PersistClient,
76 pub statement_logging_frontend: StatementLoggingFrontend,
78}
79
80impl PeekClient {
81 pub fn new(
86 coordinator_client: Client,
87 catalog: &Arc<Catalog>,
88 storage_collections: StorageCollectionsHandle,
89 transient_id_gen: Arc<TransientIdGen>,
90 optimizer_metrics: OptimizerMetrics,
91 persist_client: PersistClient,
92 statement_logging_frontend: StatementLoggingFrontend,
93 ) -> Self {
94 Self {
95 coordinator_client,
96 catalog_cache: Arc::downgrade(catalog),
97 compute_instances: Default::default(), storage_collections,
99 transient_id_gen,
100 optimizer_metrics,
101 statement_logging_frontend,
102 oracles: Default::default(), persist_client,
104 }
105 }
106
107 pub async fn ensure_compute_instance_client(
108 &mut self,
109 compute_instance: ComputeInstanceId,
110 ) -> Result<InstanceClient, InstanceMissing> {
111 if !self.compute_instances.contains_key(&compute_instance) {
112 let client = self
113 .call_coordinator(|tx| Command::GetComputeInstanceClient {
114 instance_id: compute_instance,
115 tx,
116 })
117 .await?;
118 self.compute_instances.insert(compute_instance, client);
119 }
120 Ok(self
121 .compute_instances
122 .get(&compute_instance)
123 .expect("ensured above")
124 .clone())
125 }
126
127 pub async fn ensure_oracle(
128 &mut self,
129 timeline: Timeline,
130 ) -> Result<&mut Arc<dyn TimestampOracle<Timestamp> + Send + Sync>, AdapterError> {
131 if !self.oracles.contains_key(&timeline) {
132 let oracle = self
133 .call_coordinator(|tx| Command::GetOracle {
134 timeline: timeline.clone(),
135 tx,
136 })
137 .await?;
138 self.oracles.insert(timeline.clone(), oracle);
139 }
140 Ok(self.oracles.get_mut(&timeline).expect("ensured above"))
141 }
142
143 pub async fn catalog_snapshot(&mut self, context: &str) -> Arc<Catalog> {
156 let cached = self
162 .catalog_cache
163 .upgrade()
164 .filter(|catalog| catalog.transient_revision_is_current());
165 if let Some(catalog) = cached {
166 self.coordinator_client
167 .metrics()
168 .catalog_snapshot_cache
169 .with_label_values(&[context, "hit"])
170 .inc();
171 return catalog;
172 }
173
174 let start = std::time::Instant::now();
177 let CatalogSnapshot { catalog } = self
178 .call_coordinator(|tx| Command::CatalogSnapshot { tx })
179 .await;
180 let metrics = self.coordinator_client.metrics();
181 metrics
182 .catalog_snapshot_seconds
183 .with_label_values(&[context])
184 .observe(start.elapsed().as_secs_f64());
185 metrics
186 .catalog_snapshot_cache
187 .with_label_values(&[context, "miss"])
188 .inc();
189 self.catalog_cache = Arc::downgrade(&catalog);
190 catalog
191 }
192
193 pub(crate) async fn call_coordinator<T, F>(&self, f: F) -> T
194 where
195 F: FnOnce(oneshot::Sender<T>) -> Command,
196 {
197 let (tx, rx) = oneshot::channel();
198 self.coordinator_client.send(f(tx));
199 rx.await
200 .expect("if the coordinator is still alive, it shouldn't have dropped our call")
201 }
202
203 pub async fn acquire_read_holds_and_least_valid_write(
216 &mut self,
217 id_bundle: &CollectionIdBundle,
218 ) -> Result<(ReadHolds, Antichain<Timestamp>), CollectionLookupError> {
219 let mut read_holds = ReadHolds::new();
220 let mut upper = Antichain::new();
221
222 if !id_bundle.storage_ids.is_empty() {
223 let desired_storage: Vec<_> = id_bundle.storage_ids.iter().copied().collect();
224 let storage_read_holds = self
225 .storage_collections
226 .acquire_read_holds(desired_storage)?;
227 read_holds.storage_holds = storage_read_holds
228 .into_iter()
229 .map(|hold| (hold.id(), hold))
230 .collect();
231
232 let storage_ids: Vec<_> = id_bundle.storage_ids.iter().copied().collect();
233 for f in self
234 .storage_collections
235 .collections_frontiers(storage_ids)?
236 {
237 upper.extend(f.write_frontier);
238 }
239 }
240
241 for (&instance_id, collection_ids) in &id_bundle.compute_ids {
242 let client = self.ensure_compute_instance_client(instance_id).await?;
243
244 for (id, read_hold, write_frontier) in client
245 .acquire_read_holds_and_collection_write_frontiers(
246 collection_ids.iter().copied().collect(),
247 )
248 .await?
249 {
250 let prev = read_holds
251 .compute_holds
252 .insert((instance_id, id), read_hold);
253 assert!(
254 prev.is_none(),
255 "duplicate compute ID in id_bundle {id_bundle:?}"
256 );
257
258 upper.extend(write_frontier);
259 }
260 }
261
262 Ok((read_holds, upper))
263 }
264
265 pub(crate) async fn implement_fast_path_peek_plan(
281 &mut self,
282 fast_path: FastPathPlan,
283 timestamp: Timestamp,
284 finishing: mz_expr::RowSetFinishing,
285 compute_instance: ComputeInstanceId,
286 target_replica: Option<mz_cluster_client::ReplicaId>,
287 intermediate_result_type: mz_repr::SqlRelationType,
288 max_result_size: u64,
289 max_returned_query_size: Option<u64>,
290 row_set_finishing_seconds: Histogram,
291 input_read_holds: ReadHolds,
292 peek_stash_read_batch_size_bytes: usize,
293 peek_stash_read_memory_budget_bytes: usize,
294 conn_id: mz_adapter_types::connection::ConnectionId,
295 depends_on: std::collections::BTreeSet<mz_repr::GlobalId>,
296 watch_set: Option<WatchSetCreation>,
297 logging_guard: &mut StatementLoggingGuard,
298 ) -> Result<crate::ExecuteResponse, AdapterError> {
299 if let FastPathPlan::Constant(rows_res, _) = fast_path {
301 if let Some(ref ws) = watch_set {
304 self.log_lifecycle_event(
305 ws.logging_id,
306 statement_logging::StatementLifecycleEvent::StorageDependenciesFinished,
307 );
308 self.log_lifecycle_event(
309 ws.logging_id,
310 statement_logging::StatementLifecycleEvent::ComputeDependenciesFinished,
311 );
312 }
313
314 let mut rows = match rows_res {
315 Ok(rows) => rows,
316 Err(e) => return Err(e.into()),
317 };
318 consolidate(&mut rows);
319
320 let mut results = Vec::new();
321 for (row, count) in rows {
322 let count = match u64::try_from(count.into_inner()) {
323 Ok(u) => usize::cast_from(u),
324 Err(_) => {
325 return Err(AdapterError::Unstructured(anyhow::anyhow!(
326 "Negative multiplicity in constant result: {}",
327 count
328 )));
329 }
330 };
331 match std::num::NonZeroUsize::new(count) {
332 Some(nzu) => {
333 results.push((row, nzu));
334 }
335 None => {
336 }
338 };
339 }
340 let row_collection = RowCollection::new(results, &finishing.order_by);
341 return match finishing.finish(
342 row_collection,
343 max_result_size,
344 max_returned_query_size,
345 &row_set_finishing_seconds,
346 ) {
347 Ok((rows, _bytes)) => Ok(Coordinator::send_immediate_rows(rows)),
348 Err(e) => Err(AdapterError::ResultSize(e)),
350 };
351 }
352
353 let (peek_target, target_read_hold, literal_constraints, mfp, strategy) = match fast_path {
354 FastPathPlan::PeekExisting(_coll_id, idx_id, literal_constraints, mfp) => {
355 let peek_target = PeekTarget::Index { id: idx_id };
356 let target_read_hold = input_read_holds
357 .compute_holds
358 .get(&(compute_instance, idx_id))
359 .expect("missing compute read hold on PeekExisting peek target")
360 .clone();
361 let strategy = statement_logging::StatementExecutionStrategy::FastPath;
362 (
363 peek_target,
364 target_read_hold,
365 literal_constraints,
366 mfp,
367 strategy,
368 )
369 }
370 FastPathPlan::PeekPersist(coll_id, literal_constraint, mfp) => {
371 let literal_constraints = literal_constraint.map(|r| vec![r]);
372 let metadata = self
373 .storage_collections
374 .collection_metadata(coll_id)
375 .map_err(AdapterError::concurrent_dependency_drop_from_collection_missing)?
376 .clone();
377 let peek_target = PeekTarget::Persist {
378 id: coll_id,
379 metadata,
380 };
381 let target_read_hold = input_read_holds
382 .storage_holds
383 .get(&coll_id)
384 .expect("missing storage read hold on PeekPersist peek target")
385 .clone();
386 let strategy = statement_logging::StatementExecutionStrategy::PersistFastPath;
387 (
388 peek_target,
389 target_read_hold,
390 literal_constraints,
391 mfp,
392 strategy,
393 )
394 }
395 FastPathPlan::Constant(..) => {
396 unreachable!()
398 }
399 };
400
401 let (rows_tx, rows_rx) = oneshot::channel();
402 let uuid = Uuid::new_v4();
403
404 let cols = (0..intermediate_result_type.arity()).map(|i| format!("peek_{i}"));
407 let result_desc = RelationDesc::new(intermediate_result_type.clone(), cols);
408
409 let client = self
410 .ensure_compute_instance_client(compute_instance)
411 .await
412 .map_err(AdapterError::concurrent_dependency_drop_from_instance_missing)?;
413
414 self.call_coordinator(|tx| Command::RegisterFrontendPeek {
419 uuid,
420 conn_id: conn_id.clone(),
421 cluster_id: compute_instance,
422 depends_on,
423 is_fast_path: true,
424 watch_set,
425 tx,
426 })
427 .await?;
428
429 logging_guard.defuse();
435
436 fail::fail_point!("peek_after_register_before_issue");
441
442 let finishing_for_instance = finishing.clone();
443 let peek_result = client
444 .peek(
445 peek_target,
446 literal_constraints,
447 uuid,
448 timestamp,
449 result_desc,
450 finishing_for_instance,
451 mfp,
452 target_read_hold,
453 target_replica,
454 rows_tx,
455 )
456 .await;
457
458 if let Err(err) = peek_result {
459 let err = AdapterError::concurrent_dependency_drop_from_instance_peek_error(
460 err,
461 compute_instance,
462 );
463 self.call_coordinator(|tx| Command::UnregisterFrontendPeek {
469 uuid,
470 reason: statement_logging::StatementEndedExecutionReason::Errored {
471 error: err.to_string(),
472 },
473 tx,
474 })
475 .await;
476 return Err(err);
477 }
478
479 let peek_response_stream = Coordinator::create_peek_response_stream(
480 rows_rx,
481 finishing,
482 max_result_size,
483 max_returned_query_size,
484 row_set_finishing_seconds,
485 self.persist_client.clone(),
486 peek_stash_read_batch_size_bytes,
487 peek_stash_read_memory_budget_bytes,
488 );
489
490 Ok(crate::ExecuteResponse::SendingRowsStreaming {
491 rows: Box::pin(peek_response_stream),
492 instance_id: compute_instance,
493 strategy,
494 })
495 }
496
497 pub(crate) fn begin_statement_logging(
510 &self,
511 session: &mut Session,
512 params: &Params,
513 logging: &Arc<QCell<PreparedStatementLoggingInfo>>,
514 catalog: &Catalog,
515 lifecycle_timestamps: Option<LifecycleTimestamps>,
516 outer_ctx_extra: &mut Option<ExecuteContextGuard>,
517 ) -> StatementLoggingGuard {
518 let id = if outer_ctx_extra.is_none() {
519 let result = self.statement_logging_frontend.begin_statement_execution(
521 session,
522 params,
523 logging,
524 catalog.system_config(),
525 lifecycle_timestamps,
526 );
527
528 if let Some((logging_id, began_execution, mseh_update, prepared_statement)) = result {
529 self.log_began_execution(began_execution, mseh_update, prepared_statement);
530 Some(logging_id)
531 } else {
532 None
533 }
534 } else {
535 outer_ctx_extra
539 .take()
540 .and_then(|guard| guard.defuse().retire())
541 };
542
543 StatementLoggingGuard {
544 id,
545 coordinator_client: self.coordinator_client.clone(),
546 now: self.statement_logging_frontend.now.clone(),
547 }
548 }
549
550 pub(crate) fn log_began_execution(
552 &self,
553 record: statement_logging::StatementBeganExecutionRecord,
554 mseh_update: Row,
555 prepared_statement: Option<PreparedStatementEvent>,
556 ) {
557 self.coordinator_client
558 .send(Command::FrontendStatementLogging(
559 FrontendStatementLoggingEvent::BeganExecution {
560 record,
561 mseh_update,
562 prepared_statement,
563 },
564 ));
565 }
566
567 pub(crate) fn log_set_cluster(
569 &self,
570 id: StatementLoggingId,
571 cluster_id: mz_controller_types::ClusterId,
572 cluster_name: String,
573 ) {
574 self.coordinator_client
575 .send(Command::FrontendStatementLogging(
576 FrontendStatementLoggingEvent::SetCluster {
577 id,
578 cluster_id,
579 cluster_name,
580 },
581 ));
582 }
583
584 pub(crate) fn log_set_timestamp(&self, id: StatementLoggingId, timestamp: mz_repr::Timestamp) {
586 self.coordinator_client
587 .send(Command::FrontendStatementLogging(
588 FrontendStatementLoggingEvent::SetTimestamp { id, timestamp },
589 ));
590 }
591
592 pub(crate) fn log_set_transient_index_id(
594 &self,
595 id: StatementLoggingId,
596 transient_index_id: mz_repr::GlobalId,
597 ) {
598 self.coordinator_client
599 .send(Command::FrontendStatementLogging(
600 FrontendStatementLoggingEvent::SetTransientIndex {
601 id,
602 transient_index_id,
603 },
604 ));
605 }
606
607 pub(crate) fn log_lifecycle_event(
609 &self,
610 id: StatementLoggingId,
611 event: statement_logging::StatementLifecycleEvent,
612 ) {
613 let when = (self.statement_logging_frontend.now)();
614 self.coordinator_client
615 .send(Command::FrontendStatementLogging(
616 FrontendStatementLoggingEvent::Lifecycle { id, event, when },
617 ));
618 }
619
620 pub(crate) fn log_ended_execution(
626 &self,
627 id: StatementLoggingId,
628 reason: statement_logging::StatementEndedExecutionReason,
629 ) {
630 let ended_at = (self.statement_logging_frontend.now)();
631 let record = statement_logging::StatementEndedExecutionRecord {
632 id: id.0,
633 reason,
634 ended_at,
635 };
636 self.coordinator_client
637 .send(Command::FrontendStatementLogging(
638 FrontendStatementLoggingEvent::EndedExecution(record),
639 ));
640 }
641}
642
643#[must_use = "StatementLoggingGuard must be explicitly retired or handed off; \
659 otherwise `Drop` will log the statement as Aborted"]
660pub(crate) struct StatementLoggingGuard {
661 id: Option<StatementLoggingId>,
663 coordinator_client: Client,
664 now: mz_ore::now::NowFn,
665}
666
667impl StatementLoggingGuard {
668 pub(crate) fn id(&self) -> Option<StatementLoggingId> {
670 self.id
671 }
672
673 pub(crate) fn retire(mut self, reason: statement_logging::StatementEndedExecutionReason) {
676 self.emit(reason);
677 }
678
679 pub(crate) fn defuse(&mut self) {
683 self.id = None;
684 }
685
686 fn emit(&mut self, reason: statement_logging::StatementEndedExecutionReason) {
687 let Some(id) = self.id.take() else {
688 return;
689 };
690 let ended_at = (self.now)();
691 let record = statement_logging::StatementEndedExecutionRecord {
692 id: id.0,
693 reason,
694 ended_at,
695 };
696 self.coordinator_client
697 .send(Command::FrontendStatementLogging(
698 FrontendStatementLoggingEvent::EndedExecution(record),
699 ));
700 }
701}
702
703impl Drop for StatementLoggingGuard {
704 fn drop(&mut self) {
705 self.emit(statement_logging::StatementEndedExecutionReason::Aborted);
708 }
709}
710
711#[derive(Error, Debug)]
713pub enum CollectionLookupError {
714 #[error("instance does not exist: {0}")]
716 InstanceMissing(ComputeInstanceId),
717 #[error("the instance has shut down")]
719 InstanceShutDown,
720 #[error("collection does not exist: {0}")]
722 CollectionMissing(GlobalId),
723}
724
725impl From<InstanceMissing> for CollectionLookupError {
726 fn from(error: InstanceMissing) -> Self {
727 Self::InstanceMissing(error.0)
728 }
729}
730
731impl From<InstanceShutDown> for CollectionLookupError {
732 fn from(_error: InstanceShutDown) -> Self {
733 Self::InstanceShutDown
734 }
735}
736
737impl From<CollectionMissing> for CollectionLookupError {
738 fn from(error: CollectionMissing) -> Self {
739 Self::CollectionMissing(error.0)
740 }
741}
742
743impl From<AcquireReadHoldsError> for CollectionLookupError {
744 fn from(error: AcquireReadHoldsError) -> Self {
745 match error {
746 AcquireReadHoldsError::CollectionMissing(id) => Self::CollectionMissing(id),
747 AcquireReadHoldsError::InstanceShutDown => Self::InstanceShutDown,
748 }
749 }
750}