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
140 let mut optimizer = optimize::metric_sink::Optimizer::new(
142 self.owned_catalog(),
143 compute_instance,
144 view_id,
145 global_id,
146 optimizer_config,
147 self.optimizer_metrics(),
148 );
149 let span = Span::current();
150 Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
151 || "optimize create metric sink",
152 move || {
153 span.in_scope(|| {
154 let metric_sink = optimize::metric_sink::MetricSink::new(
155 plan.name.clone(),
156 plan.metric_sink.from,
157 plan.metric_sink.prefix.clone(),
158 );
159
160 let global_mir_plan = optimizer.catch_unwind_optimize(metric_sink)?;
162 let global_lir_plan =
164 optimizer.catch_unwind_optimize(global_mir_plan.clone())?;
165
166 let stage = CreateMetricSinkStage::Finish(CreateMetricSinkFinish {
167 validity,
168 item_id,
169 global_id,
170 plan,
171 resolved_ids,
172 global_mir_plan,
173 global_lir_plan,
174 optimizer_features,
175 });
176 Ok(Box::new(stage))
177 })
178 },
179 )))
180 }
181
182 #[instrument]
183 async fn create_metric_sink_finish(
184 &mut self,
185 ctx: &mut ExecuteContext,
186 stage: CreateMetricSinkFinish,
187 ) -> Result<StageResult<Box<CreateMetricSinkStage>>, AdapterError> {
188 let CreateMetricSinkFinish {
189 item_id,
190 global_id,
191 plan:
192 plan::CreateMetricSinkPlan {
193 name,
194 metric_sink,
195 if_not_exists,
196 },
197 resolved_ids,
198 global_mir_plan,
199 global_lir_plan,
200 optimizer_features,
201 ..
202 } = stage;
203 let cluster_id = metric_sink.cluster_id;
204 let id_bundle = dataflow_import_id_bundle(global_lir_plan.df_desc(), cluster_id);
205
206 self.ensure_metric_sink_prefix_is_free(&name, cluster_id, &metric_sink.prefix)?;
210
211 let owner_id = *ctx.session().current_role_id();
212 let ops = vec![catalog::Op::CreateItem {
213 id: item_id,
214 name: name.clone(),
215 item: CatalogItem::MetricSink(MetricSink {
216 create_sql: metric_sink.create_sql,
217 global_id,
218 from: metric_sink.from,
219 resolved_ids,
220 cluster_id,
221 prefix: metric_sink.prefix,
222 optimized_plan: None,
223 physical_plan: None,
224 dataflow_metainfo: None,
225 }),
226 owner_id,
227 }];
228
229 let (df_desc, raw_df_meta) = global_lir_plan.unapply();
233 let from_entry = self.catalog().get_entry_by_global_id(&metric_sink.from);
234 let from_desc = from_entry
235 .relation_desc()
236 .expect("can only create a metric sink on items with a valid description");
237 let df_meta = self.render_create_item_notices(&name, global_id, &from_desc, &raw_df_meta);
238
239 self.catalog()
244 .cache_expressions(
245 global_id,
246 None,
247 global_mir_plan.df_desc().clone(),
248 df_desc.clone(),
249 df_meta.clone(),
250 optimizer_features,
251 )
252 .await;
253
254 let transact_result = self
255 .catalog_transact_with_side_effects(Some(ctx), ops, move |coord, _ctx| {
256 Box::pin(async move {
257 coord
259 .catalog_mut()
260 .set_optimized_plan(global_id, global_mir_plan.df_desc().clone());
261 coord
262 .catalog_mut()
263 .set_physical_plan(global_id, df_desc.clone());
264
265 let notice_builtin_updates_fut =
266 coord.persist_dataflow_metainfo(df_meta, global_id);
267
268 coord
269 .ship_new_dataflow(
270 &id_bundle,
271 df_desc,
272 cluster_id,
273 notice_builtin_updates_fut,
274 )
275 .await;
276 })
279 })
280 .await;
281
282 match transact_result {
283 Ok(_) => {
284 self.emit_raw_optimizer_notices_to_user(ctx, &raw_df_meta.optimizer_notices);
285 Ok(StageResult::Response(ExecuteResponse::CreatedMetricSink))
286 }
287 Err(AdapterError::Catalog(mz_catalog::memory::error::Error {
288 kind: ErrorKind::Sql(CatalogError::ItemAlreadyExists(_, _)),
289 })) if if_not_exists => {
290 ctx.session()
291 .add_notice(AdapterNotice::ObjectAlreadyExists {
292 name: name.item,
293 ty: "metric sink",
294 });
295 Ok(StageResult::Response(ExecuteResponse::CreatedMetricSink))
296 }
297 Err(err) => Err(err),
298 }
299 }
300
301 fn ensure_metric_sink_prefix_is_free(
317 &self,
318 name: &QualifiedItemName,
319 cluster_id: ClusterId,
320 prefix: &str,
321 ) -> Result<(), AdapterError> {
322 let cluster = self.catalog().get_cluster(cluster_id);
323 for item_id in &cluster.bound_objects {
324 let entry = self.catalog().get_entry(item_id);
325 let CatalogItem::MetricSink(existing) = entry.item() else {
326 continue;
327 };
328 if entry.name() == name {
329 continue;
330 }
331 if existing.prefix.starts_with(prefix) || prefix.starts_with(&existing.prefix) {
332 return Err(AdapterError::Unstructured(anyhow!(
333 "metric sink prefix {:?} conflicts with prefix {:?} of metric sink {} on \
334 cluster {}",
335 prefix,
336 existing.prefix,
337 entry.name().item,
338 cluster.name,
339 )));
340 }
341 }
342 Ok(())
343 }
344}