Skip to main content

mz_adapter/coord/sequencer/inner/
explain_timestamp.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use std::sync::Arc;
11
12use chrono::{DateTime, Utc};
13use itertools::Itertools;
14use mz_adapter_types::connection::ConnectionId;
15use mz_controller_types::ClusterId;
16use mz_expr::CollectionPlan;
17use mz_ore::instrument;
18use mz_repr::explain::ExplainFormat;
19use mz_repr::{Datum, Row, Timestamp};
20use mz_sql::plan::{self};
21use mz_sql::session::metadata::SessionMetadata;
22use tracing::{Instrument, Span};
23
24use crate::coord::sequencer::inner::{return_if_err, spawn_linearized_read_ts};
25use crate::coord::timestamp_selection::{TimestampDetermination, TimestampSource};
26use crate::coord::{
27    Coordinator, ExplainTimestampFinish, ExplainTimestampLinearizeTimestamp,
28    ExplainTimestampOptimize, ExplainTimestampRealTimeRecency, ExplainTimestampStage, Message,
29    PlanValidity, StageResult, Staged, TargetCluster,
30};
31use crate::error::AdapterError;
32use crate::optimize::{self, Optimize};
33use crate::session::{RequireLinearization, Session};
34use crate::{CollectionIdBundle, ExecuteContext, TimelineContext, TimestampExplanation};
35
36impl Staged for ExplainTimestampStage {
37    type Ctx = ExecuteContext;
38
39    fn validity(&mut self) -> &mut PlanValidity {
40        match self {
41            ExplainTimestampStage::Optimize(stage) => &mut stage.validity,
42            ExplainTimestampStage::RealTimeRecency(stage) => &mut stage.validity,
43            ExplainTimestampStage::LinearizeTimestamp(stage) => &mut stage.validity,
44            ExplainTimestampStage::Finish(stage) => &mut stage.validity,
45        }
46    }
47
48    async fn stage(
49        self,
50        coord: &mut Coordinator,
51        ctx: &mut ExecuteContext,
52    ) -> Result<StageResult<Box<Self>>, AdapterError> {
53        match self {
54            ExplainTimestampStage::Optimize(stage) => coord.explain_timestamp_optimize(stage),
55            ExplainTimestampStage::RealTimeRecency(stage) => {
56                coord
57                    .explain_timestamp_real_time_recency(ctx.session(), stage)
58                    .await
59            }
60            ExplainTimestampStage::LinearizeTimestamp(stage) => {
61                coord
62                    .explain_timestamp_linearize_timestamp(ctx.session(), stage)
63                    .await
64            }
65            ExplainTimestampStage::Finish(stage) => {
66                coord.explain_timestamp_finish(ctx.session_mut(), stage)
67            }
68        }
69    }
70
71    fn message(self, ctx: ExecuteContext, span: Span) -> Message {
72        Message::ExplainTimestampStageReady {
73            ctx,
74            span,
75            stage: self,
76        }
77    }
78
79    fn cancel_enabled(&self) -> bool {
80        true
81    }
82}
83
84impl Coordinator {
85    #[instrument]
86    pub async fn sequence_explain_timestamp(
87        &mut self,
88        ctx: ExecuteContext,
89        plan: plan::ExplainTimestampPlan,
90        target_cluster: TargetCluster,
91    ) {
92        let stage = return_if_err!(
93            self.explain_timestamp_validity(ctx.session(), plan, target_cluster),
94            ctx
95        );
96        self.sequence_staged(ctx, Span::current(), stage).await;
97    }
98
99    #[instrument]
100    fn explain_timestamp_validity(
101        &self,
102        session: &Session,
103        plan: plan::ExplainTimestampPlan,
104        target_cluster: TargetCluster,
105    ) -> Result<ExplainTimestampStage, AdapterError> {
106        let cluster = self
107            .catalog()
108            .resolve_target_cluster(target_cluster, session)?;
109        let cluster_id = cluster.id;
110        let dependencies = plan
111            .raw_plan
112            .depends_on()
113            .into_iter()
114            .map(|id| self.catalog().resolve_item_id(&id))
115            .collect();
116        let validity = PlanValidity::new(
117            self.catalog(),
118            dependencies,
119            Some(cluster_id),
120            None,
121            session.role_metadata().clone(),
122        );
123        Ok(ExplainTimestampStage::Optimize(ExplainTimestampOptimize {
124            validity,
125            plan,
126            cluster_id,
127        }))
128    }
129
130    #[instrument]
131    fn explain_timestamp_optimize(
132        &self,
133        ExplainTimestampOptimize {
134            validity,
135            plan,
136            cluster_id,
137        }: ExplainTimestampOptimize,
138    ) -> Result<StageResult<Box<ExplainTimestampStage>>, AdapterError> {
139        // Collect optimizer parameters.
140        let optimizer_config = optimize::OptimizerConfig::from(self.catalog().system_config());
141
142        let mut optimizer = optimize::view::Optimizer::new(optimizer_config, None);
143
144        let span = Span::current();
145        Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
146            || "optimize explain timestamp",
147            move || {
148                span.in_scope(|| {
149                    let plan::ExplainTimestampPlan {
150                        format,
151                        raw_plan,
152                        when,
153                    } = plan;
154
155                    // HIR ⇒ MIR lowering and MIR ⇒ MIR optimization (local)
156                    let optimized_plan = optimizer.optimize(raw_plan)?;
157
158                    let stage =
159                        ExplainTimestampStage::RealTimeRecency(ExplainTimestampRealTimeRecency {
160                            validity,
161                            format,
162                            optimized_plan,
163                            cluster_id,
164                            when,
165                        });
166                    Ok(Box::new(stage))
167                })
168            },
169        )))
170    }
171
172    #[instrument]
173    async fn explain_timestamp_real_time_recency(
174        &self,
175        session: &Session,
176        ExplainTimestampRealTimeRecency {
177            validity,
178            format,
179            optimized_plan,
180            cluster_id,
181            when,
182        }: ExplainTimestampRealTimeRecency,
183    ) -> Result<StageResult<Box<ExplainTimestampStage>>, AdapterError> {
184        let source_ids = optimized_plan.depends_on();
185        let fut = self
186            .determine_real_time_recent_timestamp_if_needed(session, source_ids.iter().copied())
187            .await?;
188
189        match fut {
190            Some(fut) => {
191                let catalog = Arc::clone(&self.catalog);
192                let span = Span::current();
193                Ok(StageResult::Handle(mz_ore::task::spawn(
194                    || "explain timestamp real time recency",
195                    async move {
196                        let real_time_recency_ts =
197                            Coordinator::await_real_time_recent_timestamp(catalog, fut).await?;
198                        let stage = ExplainTimestampStage::LinearizeTimestamp(
199                            ExplainTimestampLinearizeTimestamp {
200                                validity,
201                                format,
202                                optimized_plan,
203                                cluster_id,
204                                source_ids,
205                                when,
206                                real_time_recency_ts: Some(real_time_recency_ts),
207                            },
208                        );
209                        Ok(Box::new(stage))
210                    }
211                    .instrument(span),
212                )))
213            }
214            None => Ok(StageResult::Immediate(Box::new(
215                ExplainTimestampStage::LinearizeTimestamp(ExplainTimestampLinearizeTimestamp {
216                    validity,
217                    format,
218                    optimized_plan,
219                    cluster_id,
220                    source_ids,
221                    when,
222                    real_time_recency_ts: None,
223                }),
224            ))),
225        }
226    }
227
228    /// Possibly linearize a timestamp from a `TimestampOracle`, off the
229    /// coordinator loop.
230    #[instrument]
231    async fn explain_timestamp_linearize_timestamp(
232        &self,
233        session: &Session,
234        ExplainTimestampLinearizeTimestamp {
235            validity,
236            format,
237            optimized_plan,
238            cluster_id,
239            source_ids,
240            when,
241            real_time_recency_ts,
242        }: ExplainTimestampLinearizeTimestamp,
243    ) -> Result<StageResult<Box<ExplainTimestampStage>>, AdapterError> {
244        let mut timeline_context = self
245            .catalog()
246            .validate_timeline_context(source_ids.iter().copied())?;
247        if matches!(timeline_context, TimelineContext::TimestampIndependent)
248            && optimized_plan.contains_temporal()
249        {
250            // If the source IDs are timestamp independent but the query contains temporal functions,
251            // then the timeline context needs to be upgraded to timestamp dependent. This is
252            // required because `source_ids` doesn't contain functions.
253            timeline_context = TimelineContext::TimestampDependent;
254        }
255
256        let oracle = self.linearized_read_ts_oracle(session, &timeline_context, &when);
257
258        let build_stage = move |oracle_read_ts: Option<Timestamp>| {
259            ExplainTimestampStage::Finish(ExplainTimestampFinish {
260                validity,
261                format,
262                cluster_id,
263                source_ids,
264                when,
265                real_time_recency_ts,
266                timeline_context,
267                oracle_read_ts,
268            })
269        };
270
271        Ok(spawn_linearized_read_ts(
272            oracle,
273            "explain timestamp linearize timestamp",
274            build_stage,
275        ))
276    }
277
278    pub(crate) fn explain_timestamp(
279        &self,
280        conn_id: &ConnectionId,
281        session_wall_time: DateTime<Utc>,
282        cluster_id: ClusterId,
283        id_bundle: &CollectionIdBundle,
284        determination: TimestampDetermination,
285    ) -> TimestampExplanation {
286        let mut sources = Vec::new();
287        {
288            let storage_ids = id_bundle.storage_ids.iter().cloned().collect_vec();
289            let frontiers = self
290                .controller
291                .storage
292                .collections_frontiers(storage_ids)
293                .expect("missing collection");
294
295            for (id, since, upper) in frontiers {
296                let name = self
297                    .catalog()
298                    .try_get_entry_by_global_id(&id)
299                    .map(|item| item.name())
300                    .map(|name| {
301                        self.catalog()
302                            .resolve_full_name(name, Some(conn_id))
303                            .to_string()
304                    })
305                    .unwrap_or_else(|| id.to_string());
306                sources.push(TimestampSource {
307                    name: format!("{name} ({id}, storage)"),
308                    read_frontier: since.elements().to_vec(),
309                    write_frontier: upper.elements().to_vec(),
310                });
311            }
312        }
313        {
314            if let Some(compute_ids) = id_bundle.compute_ids.get(&cluster_id) {
315                let catalog = self.catalog();
316                for id in compute_ids {
317                    let frontiers = self
318                        .controller
319                        .compute
320                        .collection_frontiers(*id, Some(cluster_id))
321                        .expect("id does not exist");
322                    let name = catalog
323                        .try_get_entry_by_global_id(id)
324                        .map(|item| item.name())
325                        .map(|name| catalog.resolve_full_name(name, Some(conn_id)).to_string())
326                        .unwrap_or_else(|| id.to_string());
327                    sources.push(TimestampSource {
328                        name: format!("{name} ({id}, compute)"),
329                        read_frontier: frontiers.read_frontier.to_vec(),
330                        write_frontier: frontiers.write_frontier.to_vec(),
331                    });
332                }
333            }
334        }
335        let respond_immediately = determination.respond_immediately();
336        TimestampExplanation {
337            determination,
338            sources,
339            session_wall_time,
340            respond_immediately,
341        }
342    }
343
344    #[instrument]
345    fn explain_timestamp_finish(
346        &mut self,
347        session: &mut Session,
348        ExplainTimestampFinish {
349            validity: _,
350            format,
351            cluster_id,
352            source_ids,
353            when,
354            real_time_recency_ts,
355            timeline_context,
356            oracle_read_ts,
357        }: ExplainTimestampFinish,
358    ) -> Result<StageResult<Box<ExplainTimestampStage>>, AdapterError> {
359        let id_bundle = self
360            .index_oracle(cluster_id)
361            .sufficient_collections(source_ids.iter().copied());
362
363        let is_json = match format {
364            ExplainFormat::Text => false,
365            ExplainFormat::Json => true,
366            ExplainFormat::Dot => {
367                return Err(AdapterError::Unsupported("EXPLAIN TIMESTAMP AS DOT"));
368            }
369        };
370
371        let determination = self.sequence_peek_timestamp(
372            session,
373            &when,
374            cluster_id,
375            timeline_context,
376            oracle_read_ts,
377            &id_bundle,
378            &source_ids,
379            real_time_recency_ts,
380            RequireLinearization::NotRequired,
381        )?;
382        let explanation = self.explain_timestamp(
383            session.conn_id(),
384            session.pcx().wall_time,
385            cluster_id,
386            &id_bundle,
387            determination,
388        );
389
390        let s = if is_json {
391            serde_json::to_string_pretty(&explanation).expect("failed to serialize explanation")
392        } else {
393            explanation.to_string()
394        };
395        let rows = vec![Row::pack_slice(&[Datum::from(s.as_str())])];
396        Ok(StageResult::Response(Self::send_immediate_rows(rows)))
397    }
398}