1use futures::StreamExt;
11use maplit::btreemap;
12use mz_adapter_types::connection::ConnectionId;
13use mz_adapter_types::dyncfgs::SUBSCRIBE_MAX_BUFFERED_BYTES;
14use mz_cluster_client::ReplicaId;
15use mz_compute_types::ComputeInstanceId;
16use mz_compute_types::dataflows::LirDataflowDescription;
17use mz_ore::collections::CollectionExt;
18use mz_ore::instrument;
19use mz_repr::GlobalId;
20use mz_repr::Timestamp;
21use mz_repr::explain::{ExprHumanizerExt, TransientItem};
22use mz_repr::optimize::{OptimizerFeatures, OverrideFrom};
23use mz_sql::plan::{self, QueryWhen, SubscribeFrom};
24use mz_sql::session::metadata::SessionMetadata;
25use std::collections::BTreeSet;
26use std::sync::{Arc, Mutex};
27use timely::PartialOrder;
28use timely::progress::Antichain;
29use tokio::sync::mpsc;
30use tokio_stream::wrappers::UnboundedReceiverStream;
31use tracing::{Instrument, Span};
32use uuid::Uuid;
33
34use crate::active_compute_sink::{
35 ActiveComputeSink, ActiveComputeSinkRetireReason, ActiveSubscribe, ActiveSubscribeOwner,
36 SubscribeBacklogAccounting,
37};
38use crate::command::ExecuteResponse;
39use crate::coord::appends::BuiltinTableAppendNotify;
40use crate::coord::peek::PeekResponseUnary;
41use crate::coord::sequencer::inner::{return_if_err, spawn_linearized_read_ts};
42use crate::coord::sequencer::{check_log_reads, emit_optimizer_notices};
43use crate::coord::{
44 Coordinator, ExplainContext, ExplainPlanContext, Message, PlanValidity, StageResult, Staged,
45 SubscribeExplain, SubscribeFinish, SubscribeLinearizeTimestamp, SubscribeOptimizeMir,
46 SubscribeStage, SubscribeTimestampOptimizeLir, TargetCluster,
47};
48use crate::error::AdapterError;
49use crate::explain::optimizer_trace::OptimizerTrace;
50use crate::optimize::Optimize;
51use crate::session::{Session, TransactionOps};
52use crate::{
53 AdapterNotice, ExecuteContext, ExecuteContextGuard, ReadHolds, TimelineContext, optimize,
54};
55
56impl Staged for SubscribeStage {
57 type Ctx = ExecuteContext;
58
59 fn validity(&mut self) -> &mut PlanValidity {
60 match self {
61 SubscribeStage::OptimizeMir(stage) => &mut stage.validity,
62 SubscribeStage::LinearizeTimestamp(stage) => &mut stage.validity,
63 SubscribeStage::TimestampOptimizeLir(stage) => &mut stage.validity,
64 SubscribeStage::Finish(stage) => &mut stage.validity,
65 SubscribeStage::Explain(stage) => &mut stage.validity,
66 }
67 }
68
69 async fn stage(
70 self,
71 coord: &mut Coordinator,
72 ctx: &mut ExecuteContext,
73 ) -> Result<StageResult<Box<Self>>, AdapterError> {
74 match self {
75 SubscribeStage::OptimizeMir(stage) => coord.subscribe_optimize_mir(stage),
76 SubscribeStage::LinearizeTimestamp(stage) => {
77 coord
78 .subscribe_linearize_timestamp(ctx.session(), stage)
79 .await
80 }
81 SubscribeStage::TimestampOptimizeLir(stage) => {
82 coord.subscribe_timestamp_optimize_lir(ctx, stage).await
83 }
84 SubscribeStage::Finish(stage) => coord.subscribe_finish(ctx, stage).await,
85 SubscribeStage::Explain(stage) => coord.subscribe_explain(ctx.session(), stage).await,
86 }
87 }
88
89 fn message(self, ctx: ExecuteContext, span: Span) -> Message {
90 Message::SubscribeStageReady {
91 ctx,
92 span,
93 stage: self,
94 }
95 }
96
97 fn cancel_enabled(&self) -> bool {
98 !matches!(self, SubscribeStage::Finish(_))
102 }
103}
104
105impl Coordinator {
106 #[instrument]
107 pub(crate) async fn sequence_subscribe(
108 &mut self,
109 mut ctx: ExecuteContext,
110 plan: plan::SubscribePlan,
111 target_cluster: TargetCluster,
112 ) {
113 let stage = return_if_err!(
114 self.subscribe_validate(
115 ctx.session_mut(),
116 plan,
117 target_cluster,
118 ExplainContext::None
119 ),
120 ctx
121 );
122 self.sequence_staged(ctx, Span::current(), stage).await;
123 }
124
125 #[instrument]
126 pub(crate) async fn explain_subscribe(
127 &mut self,
128 mut ctx: ExecuteContext,
129 plan::ExplainPlanPlan {
130 stage,
131 format,
132 config,
133 explainee,
134 }: plan::ExplainPlanPlan,
135 target_cluster: TargetCluster,
136 ) {
137 let plan::Explainee::Statement(stmt) = explainee else {
138 unreachable!()
141 };
142 let plan::ExplaineeStatement::Subscribe { broken, plan } = stmt else {
143 unreachable!()
146 };
147
148 let desc = match &plan.from {
149 SubscribeFrom::Id(_) => None,
150 SubscribeFrom::Query { desc, .. } => Some(desc.clone()),
151 };
152
153 let optimizer_trace = OptimizerTrace::new(stage.paths());
156
157 let explain_ctx = ExplainContext::Plan(ExplainPlanContext {
158 broken,
159 config,
160 format,
161 stage,
162 replan: None,
163 desc,
164 optimizer_trace,
165 });
166 let stage = return_if_err!(
167 self.subscribe_validate(ctx.session_mut(), plan, target_cluster, explain_ctx),
168 ctx
169 );
170 self.sequence_staged(ctx, Span::current(), stage).await;
171 }
172
173 #[instrument]
174 fn subscribe_validate(
175 &self,
176 session: &mut Session,
177 plan: plan::SubscribePlan,
178 target_cluster: TargetCluster,
179 explain_ctx: ExplainContext,
180 ) -> Result<SubscribeStage, AdapterError> {
181 let plan::SubscribePlan { from, when, .. } = &plan;
182
183 let cluster = self
184 .catalog()
185 .resolve_target_cluster(target_cluster, session)?;
186 let cluster_id = cluster.id;
187
188 if explain_ctx.needs_cluster() && cluster.replicas().next().is_none() {
190 return Err(AdapterError::NoClusterReplicasAvailable {
191 name: cluster.name.clone(),
192 is_managed: cluster.is_managed(),
193 });
194 }
195
196 let mut replica_id = session
197 .vars()
198 .cluster_replica()
199 .map(|name| {
200 cluster
201 .replica_id(name)
202 .ok_or(AdapterError::UnknownClusterReplica {
203 cluster_name: cluster.name.clone(),
204 replica_name: name.to_string(),
205 })
206 })
207 .transpose()?;
208
209 if explain_ctx.needs_cluster() && when == &QueryWhen::Immediately {
212 session.add_transaction_ops(TransactionOps::Subscribe)?;
215 }
216
217 let depends_on = from.depends_on();
218
219 let notices = check_log_reads(
221 self.catalog(),
222 cluster,
223 &depends_on,
224 &mut replica_id,
225 session.vars(),
226 )?;
227 session.add_notices(notices);
228
229 let mut timeline = self
231 .catalog()
232 .validate_timeline_context(depends_on.iter().copied())?;
233 if matches!(timeline, TimelineContext::TimestampIndependent) && from.contains_temporal() {
234 timeline = TimelineContext::TimestampDependent;
237 }
238
239 let dependencies = depends_on
240 .iter()
241 .map(|id| self.catalog().resolve_item_id(id))
242 .collect();
243 let validity = PlanValidity::new(
244 self.catalog(),
245 dependencies,
246 Some(cluster_id),
247 replica_id,
248 session.role_metadata().clone(),
249 );
250
251 Ok(SubscribeStage::OptimizeMir(SubscribeOptimizeMir {
252 validity,
253 plan,
254 timeline,
255 dependency_ids: depends_on,
256 cluster_id,
257 replica_id,
258 explain_ctx,
259 }))
260 }
261
262 #[instrument]
263 fn subscribe_optimize_mir(
264 &self,
265 SubscribeOptimizeMir {
266 mut validity,
267 plan,
268 timeline,
269 dependency_ids,
270 cluster_id,
271 replica_id,
272 explain_ctx,
273 }: SubscribeOptimizeMir,
274 ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
275 let plan::SubscribePlan {
276 with_snapshot,
277 up_to,
278 ..
279 } = &plan;
280
281 let compute_instance = self
283 .instance_snapshot(cluster_id)
284 .expect("compute instance does not exist");
285 let (_, view_id) = self.allocate_transient_id();
286 let (_, sink_id) = self.allocate_transient_id();
287 let debug_name = format!("subscribe-{}", sink_id);
288 let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config())
289 .override_from(&self.catalog.get_cluster(cluster_id).config.features())
290 .override_from(&self.cluster_scoped_optimizer_overrides(cluster_id))
291 .override_from(&explain_ctx);
292
293 let mut optimizer = optimize::subscribe::Optimizer::new(
295 self.owned_catalog(),
296 compute_instance,
297 view_id,
298 sink_id,
299 *with_snapshot,
300 *up_to,
301 debug_name,
302 optimizer_config,
303 self.optimizer_metrics(),
304 );
305 let catalog = self.owned_catalog();
306
307 let span = Span::current();
308 Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
309 || "optimize subscribe (mir)",
310 move || {
311 span.in_scope(|| {
312 let _dispatch_guard = explain_ctx.dispatch_guard();
313
314 let global_mir_plan = optimizer.catch_unwind_optimize(plan.clone())?;
316 validity.extend_dependencies(
318 &catalog,
319 global_mir_plan
320 .id_bundle(optimizer.cluster_id())
321 .iter()
322 .map(|id| catalog.resolve_item_id(&id)),
323 );
324
325 let stage = SubscribeStage::LinearizeTimestamp(SubscribeLinearizeTimestamp {
326 validity,
327 plan,
328 timeline,
329 optimizer,
330 global_mir_plan,
331 dependency_ids,
332 replica_id,
333 explain_ctx,
334 });
335 Ok(Box::new(stage))
336 })
337 },
338 )))
339 }
340
341 #[instrument]
344 async fn subscribe_linearize_timestamp(
345 &self,
346 session: &Session,
347 SubscribeLinearizeTimestamp {
348 validity,
349 plan,
350 timeline,
351 optimizer,
352 global_mir_plan,
353 dependency_ids,
354 replica_id,
355 explain_ctx,
356 }: SubscribeLinearizeTimestamp,
357 ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
358 let oracle = self.linearized_read_ts_oracle(session, &timeline, &plan.when);
359
360 let build_stage = move |oracle_read_ts: Option<Timestamp>| {
361 SubscribeStage::TimestampOptimizeLir(SubscribeTimestampOptimizeLir {
362 validity,
363 plan,
364 timeline,
365 optimizer,
366 global_mir_plan,
367 dependency_ids,
368 replica_id,
369 oracle_read_ts,
370 explain_ctx,
371 })
372 };
373
374 Ok(spawn_linearized_read_ts(
375 oracle,
376 "subscribe linearize timestamp",
377 build_stage,
378 ))
379 }
380
381 #[instrument]
382 async fn subscribe_timestamp_optimize_lir(
383 &mut self,
384 ctx: &ExecuteContext,
385 SubscribeTimestampOptimizeLir {
386 validity,
387 plan,
388 timeline,
389 mut optimizer,
390 global_mir_plan,
391 dependency_ids,
392 replica_id,
393 oracle_read_ts,
394 explain_ctx,
395 }: SubscribeTimestampOptimizeLir,
396 ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
397 let plan::SubscribePlan { when, .. } = &plan;
398
399 let bundle = &global_mir_plan.id_bundle(optimizer.cluster_id());
402 let (determination, read_holds) = self.determine_timestamp(
403 ctx.session(),
404 bundle,
405 when,
406 optimizer.cluster_id(),
407 &timeline,
408 oracle_read_ts,
409 None,
410 )?;
411
412 let as_of = determination.timestamp_context.timestamp_or_default();
413
414 if let Some(id) = ctx.extra().contents() {
415 self.set_statement_execution_timestamp(id, as_of);
416 }
417 if let Some(up_to) = optimizer.up_to() {
418 if as_of == up_to {
419 ctx.session()
420 .add_notice(AdapterNotice::EqualSubscribeBounds { bound: up_to });
421 } else if as_of > up_to {
422 return Err(AdapterError::AbsurdSubscribeBounds { as_of, up_to });
423 }
424 }
425
426 self.store_transaction_read_holds(ctx.session().conn_id().clone(), read_holds);
427
428 let global_mir_plan = global_mir_plan.resolve(Antichain::from_elem(as_of));
429
430 let span = Span::current();
432 Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
433 || "optimize subscribe (lir)",
434 move || {
435 span.in_scope(|| {
436 let _dispatch_guard = explain_ctx.dispatch_guard();
437
438 let cluster_id = optimizer.cluster_id();
439
440 let mut pipeline = || -> Result<_, AdapterError> {
441 let global_lir_plan =
443 optimizer.catch_unwind_optimize(global_mir_plan.clone())?;
444 Ok(global_lir_plan)
445 };
446
447 let stage = match pipeline() {
448 Ok(global_lir_plan) => {
449 if let ExplainContext::Plan(explain_ctx) = explain_ctx {
450 let (_, df_meta) = global_lir_plan.unapply();
451 SubscribeStage::Explain(SubscribeExplain {
452 validity,
453 optimizer,
454 df_meta,
455 cluster_id,
456 explain_ctx,
457 })
458 } else {
459 SubscribeStage::Finish(SubscribeFinish {
460 validity,
461 cluster_id,
462 plan,
463 global_lir_plan,
464 dependency_ids,
465 replica_id,
466 })
467 }
468 }
469 Err(err) => {
470 let ExplainContext::Plan(explain_ctx) = explain_ctx else {
471 return Err(err);
472 };
473
474 if explain_ctx.broken {
475 tracing::error!("error while handling EXPLAIN statement: {}", err);
476 SubscribeStage::Explain(SubscribeExplain {
477 validity,
478 optimizer,
479 df_meta: Default::default(),
480 cluster_id,
481 explain_ctx,
482 })
483 } else {
484 return Err(err);
485 }
486 }
487 };
488
489 Ok(Box::new(stage))
490 })
491 },
492 )))
493 }
494
495 #[instrument]
496 async fn subscribe_finish(
497 &mut self,
498 ctx: &mut ExecuteContext,
499 SubscribeFinish {
500 validity: _,
501 cluster_id,
502 plan,
503 global_lir_plan,
504 dependency_ids,
505 replica_id,
506 }: SubscribeFinish,
507 ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
508 let (df_desc, df_meta) = global_lir_plan.unapply();
509 emit_optimizer_notices(&*self.catalog, ctx.session(), &df_meta.optimizer_notices);
510 let conn_id = ctx.session.conn_id().clone();
511 let session_uuid = ctx.session().uuid();
512 let txn_read_holds = self
513 .txn_read_holds
514 .remove(&conn_id)
515 .expect("must have previously installed read holds");
516 let (resp, write_notify) = self
517 .implement_subscribe(
518 ctx.extra_mut(),
519 df_desc,
520 dependency_ids,
521 cluster_id,
522 replica_id,
523 conn_id,
524 session_uuid,
525 txn_read_holds,
526 plan,
527 )
528 .await?;
529 let span = Span::current();
533 Ok(StageResult::HandleRetire(mz_ore::task::spawn(
534 || "subscribe_finish::await_bookkeeping",
535 async move {
536 write_notify.await;
537 Ok(resp)
538 }
539 .instrument(span),
540 )))
541 }
542
543 #[instrument]
544 pub(crate) async fn implement_subscribe(
545 &mut self,
546 ctx_extra: &mut ExecuteContextGuard,
547 df_desc: LirDataflowDescription,
548 dependency_ids: BTreeSet<GlobalId>,
549 cluster_id: ComputeInstanceId,
550 replica_id: Option<ReplicaId>,
551 conn_id: ConnectionId,
552 session_uuid: Uuid,
553 read_holds: ReadHolds,
554 plan: plan::SubscribePlan,
555 ) -> Result<(ExecuteResponse, BuiltinTableAppendNotify), AdapterError> {
556 let sink_id = df_desc.sink_id();
557
558 let (tx, rx) = mpsc::unbounded_channel::<PeekResponseUnary>();
559 let backlog_accounting = Arc::new(Mutex::new(SubscribeBacklogAccounting::default()));
560 let max_buffered_bytes =
561 SUBSCRIBE_MAX_BUFFERED_BYTES.get(self.catalog().system_config().dyncfgs());
562 let active_subscribe = ActiveSubscribe {
563 owner: ActiveSubscribeOwner::Session {
564 conn_id: conn_id.clone(),
565 session_uuid,
566 },
567 channel: tx,
568 backlog_accounting: Arc::clone(&backlog_accounting),
569 max_buffered_bytes,
570 emit_progress: plan.emit_progress,
571 as_of: df_desc
572 .as_of
573 .as_ref()
574 .and_then(|t| t.as_option())
575 .copied()
576 .expect("set to Some in an earlier stage"),
577 arity: df_desc
578 .sink_exports
579 .values()
580 .into_element()
581 .from_desc
582 .arity(),
583 cluster_id,
584 depends_on: dependency_ids,
585 start_time: self.now(),
586 output: plan.output,
587 internal: false,
588 };
589 active_subscribe.initialize();
590
591 let is_empty = df_desc
598 .as_of
599 .as_ref()
600 .is_some_and(|as_of| PartialOrder::less_equal(&df_desc.until, as_of));
601 let write_notify: BuiltinTableAppendNotify = if is_empty {
602 active_subscribe.retire(ActiveComputeSinkRetireReason::Finished);
603 Box::pin(std::future::ready(()))
605 } else {
606 let write_notify = self
613 .add_active_compute_sink(sink_id, ActiveComputeSink::Subscribe(active_subscribe));
614
615 if let Err(e) = self
620 .try_ship_dataflow(df_desc, cluster_id, replica_id)
621 .await
622 {
623 self.remove_active_compute_sink(sink_id).await;
629 return Err(
630 AdapterError::concurrent_dependency_drop_from_dataflow_creation_error(e),
631 );
632 }
633 write_notify
634 };
635
636 drop(read_holds);
638
639 let rx = UnboundedReceiverStream::new(rx).map(move |response| {
645 backlog_accounting
646 .lock()
647 .expect("subscribe backlog accounting poisoned")
648 .pop();
649 response
650 });
651 let resp = ExecuteResponse::Subscribing {
652 rx: Box::new(rx),
653 ctx_extra: std::mem::take(ctx_extra),
654 instance_id: cluster_id,
655 };
656 let resp = match plan.copy_to {
657 None => resp,
658 Some(format) => ExecuteResponse::CopyTo {
659 format,
660 resp: Box::new(resp),
661 },
662 };
663 Ok((resp, write_notify))
664 }
665
666 #[instrument]
667 async fn subscribe_explain(
668 &self,
669 session: &Session,
670 SubscribeExplain {
671 optimizer,
672 df_meta,
673 cluster_id,
674 explain_ctx:
675 ExplainPlanContext {
676 config,
677 format,
678 stage,
679 optimizer_trace,
680 desc,
681 ..
682 },
683 ..
684 }: SubscribeExplain,
685 ) -> Result<StageResult<Box<SubscribeStage>>, AdapterError> {
686 let session_catalog = self.catalog().for_session(session);
687
688 let expr_humanizer = {
689 let transient_items = btreemap! {
690 optimizer.sink_id() => TransientItem::new(
691 Some(vec![GlobalId::Explain.to_string()]),
692 desc.map(|d| d.iter_names().map(|c| c.to_string()).collect()),
693 )
694 };
695 ExprHumanizerExt::new(transient_items, &session_catalog)
696 };
697
698 let target_cluster = self.catalog().get_cluster(cluster_id);
699
700 let features = OptimizerFeatures::from(self.catalog().system_config())
701 .override_from(&target_cluster.config.features())
702 .override_from(&self.cluster_scoped_optimizer_overrides(cluster_id))
703 .override_from(&config.features);
704
705 let rows = optimizer_trace
706 .into_rows(
707 format,
708 &config,
709 &features,
710 &expr_humanizer,
711 None,
712 Some(target_cluster),
713 df_meta,
714 stage,
715 plan::ExplaineeStatementKind::Subscribe,
716 None,
717 )
718 .await?;
719
720 Ok(StageResult::Response(Self::send_immediate_rows(rows)))
721 }
722}