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