mz_adapter/coord/sequencer/inner/
create_metric_sink.rs1use anyhow::anyhow;
16use mz_catalog::memory::error::ErrorKind;
17use mz_catalog::memory::objects::{CatalogItem, MetricSink};
18use mz_controller_types::ClusterId;
19use mz_ore::instrument;
20use mz_repr::optimize::OverrideFrom;
21use mz_sql::catalog::CatalogError;
22use mz_sql::names::{QualifiedItemName, ResolvedIds};
23use mz_sql::plan;
24use mz_sql::session::metadata::SessionMetadata;
25use tracing::Span;
26
27use crate::command::ExecuteResponse;
28use crate::coord::sequencer::inner::return_if_err;
29use crate::coord::{
30 Coordinator, CreateMetricSinkFinish, CreateMetricSinkOptimize, CreateMetricSinkStage, Message,
31 PlanValidity, StageResult, Staged,
32};
33use crate::error::AdapterError;
34use crate::optimize::dataflows::dataflow_import_id_bundle;
35use crate::optimize::{self, Optimize};
36use crate::session::Session;
37use crate::{AdapterNotice, ExecuteContext, catalog};
38
39impl Staged for CreateMetricSinkStage {
40 type Ctx = ExecuteContext;
41
42 fn validity(&mut self) -> &mut PlanValidity {
43 match self {
44 Self::Optimize(stage) => &mut stage.validity,
45 Self::Finish(stage) => &mut stage.validity,
46 }
47 }
48
49 async fn stage(
50 self,
51 coord: &mut Coordinator,
52 ctx: &mut ExecuteContext,
53 ) -> Result<StageResult<Box<Self>>, AdapterError> {
54 match self {
55 CreateMetricSinkStage::Optimize(stage) => {
56 coord.create_metric_sink_optimize(stage).await
57 }
58 CreateMetricSinkStage::Finish(stage) => {
59 coord.create_metric_sink_finish(ctx, stage).await
60 }
61 }
62 }
63
64 fn message(self, ctx: ExecuteContext, span: Span) -> Message {
65 Message::CreateMetricSinkStageReady {
66 ctx,
67 span,
68 stage: self,
69 }
70 }
71
72 fn cancel_enabled(&self) -> bool {
73 true
74 }
75}
76
77impl Coordinator {
78 #[instrument]
79 pub(crate) async fn sequence_create_metric_sink(
80 &mut self,
81 ctx: ExecuteContext,
82 plan: plan::CreateMetricSinkPlan,
83 resolved_ids: ResolvedIds,
84 ) {
85 let stage = return_if_err!(
86 self.create_metric_sink_validate(ctx.session(), plan, resolved_ids),
87 ctx
88 );
89 self.sequence_staged(ctx, Span::current(), stage).await;
90 }
91
92 #[instrument]
93 fn create_metric_sink_validate(
94 &self,
95 session: &Session,
96 plan: plan::CreateMetricSinkPlan,
97 resolved_ids: ResolvedIds,
98 ) -> Result<CreateMetricSinkStage, AdapterError> {
99 let validity = PlanValidity::new(
102 self.catalog(),
103 resolved_ids.items().copied().collect(),
104 Some(plan.metric_sink.cluster_id),
105 None,
106 session.role_metadata().clone(),
107 );
108 Ok(CreateMetricSinkStage::Optimize(CreateMetricSinkOptimize {
109 validity,
110 plan,
111 resolved_ids,
112 }))
113 }
114
115 #[instrument]
116 async fn create_metric_sink_optimize(
117 &mut self,
118 CreateMetricSinkOptimize {
119 validity,
120 plan,
121 resolved_ids,
122 }: CreateMetricSinkOptimize,
123 ) -> Result<StageResult<Box<CreateMetricSinkStage>>, AdapterError> {
124 let cluster_id = plan.metric_sink.cluster_id;
125
126 let compute_instance = self
128 .instance_snapshot(cluster_id)
129 .expect("compute instance does not exist");
130 let (item_id, global_id) = self.allocate_user_id().await?;
131 let (_, view_id) = self.allocate_transient_id();
134
135 let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config())
136 .override_from(&self.catalog.get_cluster(cluster_id).config.features())
137 .override_from(&self.cluster_scoped_optimizer_overrides(cluster_id));
138 let optimizer_features = optimizer_config.features.clone();
139 let debug_name = self
140 .catalog()
141 .resolve_full_name(&plan.name, None)
142 .to_string();
143
144 let mut optimizer = optimize::metric_sink::Optimizer::new(
146 self.owned_catalog(),
147 compute_instance,
148 view_id,
149 global_id,
150 optimizer_config,
151 self.optimizer_metrics(),
152 );
153 let span = Span::current();
154 Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
155 || "optimize create metric sink",
156 move || {
157 span.in_scope(|| {
158 let metric_sink = optimize::metric_sink::MetricSink::new(
159 debug_name,
160 optimize::metric_sink::MetricSinkFrom::Id(plan.metric_sink.from),
161 plan.metric_sink.prefix.clone(),
162 None,
163 );
164
165 let global_mir_plan = optimizer.catch_unwind_optimize(metric_sink)?;
167 let global_lir_plan =
169 optimizer.catch_unwind_optimize(global_mir_plan.clone())?;
170
171 let stage = CreateMetricSinkStage::Finish(CreateMetricSinkFinish {
172 validity,
173 item_id,
174 global_id,
175 plan,
176 resolved_ids,
177 global_mir_plan,
178 global_lir_plan,
179 optimizer_features,
180 });
181 Ok(Box::new(stage))
182 })
183 },
184 )))
185 }
186
187 #[instrument]
188 async fn create_metric_sink_finish(
189 &mut self,
190 ctx: &mut ExecuteContext,
191 stage: CreateMetricSinkFinish,
192 ) -> Result<StageResult<Box<CreateMetricSinkStage>>, AdapterError> {
193 let CreateMetricSinkFinish {
194 item_id,
195 global_id,
196 plan:
197 plan::CreateMetricSinkPlan {
198 name,
199 metric_sink,
200 if_not_exists,
201 },
202 resolved_ids,
203 global_mir_plan,
204 global_lir_plan,
205 optimizer_features,
206 ..
207 } = stage;
208 let cluster_id = metric_sink.cluster_id;
209 let id_bundle = dataflow_import_id_bundle(global_lir_plan.df_desc(), cluster_id);
210
211 self.ensure_metric_sink_prefix_is_free(&name, cluster_id, &metric_sink.prefix)?;
215
216 let owner_id = *ctx.session().current_role_id();
217 let ops = vec![catalog::Op::CreateItem {
218 id: item_id,
219 name: name.clone(),
220 item: CatalogItem::MetricSink(MetricSink {
221 create_sql: metric_sink.create_sql,
222 global_id,
223 from: metric_sink.from,
224 resolved_ids,
225 cluster_id,
226 prefix: metric_sink.prefix,
227 optimized_plan: None,
228 physical_plan: None,
229 dataflow_metainfo: None,
230 }),
231 owner_id,
232 }];
233
234 let (df_desc, raw_df_meta) = global_lir_plan.unapply();
238 let from_entry = self.catalog().get_entry_by_global_id(&metric_sink.from);
239 let from_desc = from_entry
240 .relation_desc()
241 .expect("can only create a metric sink on items with a valid description");
242 let df_meta = self.render_create_item_notices(&name, global_id, &from_desc, &raw_df_meta);
243
244 self.catalog()
249 .cache_expressions(
250 global_id,
251 None,
252 global_mir_plan.df_desc().clone(),
253 df_desc.clone(),
254 df_meta.clone(),
255 optimizer_features,
256 )
257 .await;
258
259 let transact_result = self
260 .catalog_transact_with_side_effects(Some(ctx), ops, move |coord, _ctx| {
261 Box::pin(async move {
262 coord
264 .catalog_mut()
265 .set_optimized_plan(global_id, global_mir_plan.df_desc().clone());
266 coord
267 .catalog_mut()
268 .set_physical_plan(global_id, df_desc.clone());
269
270 let notice_builtin_updates_fut =
271 coord.persist_dataflow_metainfo(df_meta, global_id);
272
273 coord
274 .ship_new_dataflow(
275 &id_bundle,
276 df_desc,
277 cluster_id,
278 notice_builtin_updates_fut,
279 )
280 .await;
281 })
284 })
285 .await;
286
287 match transact_result {
288 Ok(_) => {
289 self.emit_raw_optimizer_notices_to_user(ctx, &raw_df_meta.optimizer_notices);
290 Ok(StageResult::Response(ExecuteResponse::CreatedMetricSink))
291 }
292 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
293 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
294 })) if if_not_exists => {
295 ctx.session()
296 .add_notice(AdapterNotice::ObjectAlreadyExists {
297 name: name.item,
298 ty: "metric sink",
299 });
300 Ok(StageResult::Response(ExecuteResponse::CreatedMetricSink))
301 }
302 Err(err) => Err(err),
303 }
304 }
305
306 fn ensure_metric_sink_prefix_is_free(
322 &self,
323 name: &QualifiedItemName,
324 cluster_id: ClusterId,
325 prefix: &str,
326 ) -> Result<(), AdapterError> {
327 let cluster = self.catalog().get_cluster(cluster_id);
328 for item_id in &cluster.bound_objects {
329 let entry = self.catalog().get_entry(item_id);
330 let CatalogItem::MetricSink(existing) = entry.item() else {
331 continue;
332 };
333 if entry.name() == name {
334 continue;
335 }
336 if existing.prefix.starts_with(prefix) || prefix.starts_with(&existing.prefix) {
337 return Err(AdapterError::Unstructured(anyhow!(
338 "metric sink prefix {:?} conflicts with prefix {:?} of metric sink {} on \
339 cluster {}",
340 prefix,
341 existing.prefix,
342 entry.name().item,
343 cluster.name,
344 )));
345 }
346 }
347 Ok(())
348 }
349}