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