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    /// Explains `determination`, the timestamp chosen for a statement that reads `id_bundle`.
279    ///
280    /// Fails with [`AdapterError::ConcurrentDependencyDrop`] if a collection in `id_bundle` no
281    /// longer exists. Callers outside the coordinator determine the timestamp and its read holds
282    /// before sending [`crate::command::Command::ExplainTimestamp`], and a DROP can land in
283    /// between: read holds hold back compaction, not drops.
284    pub(crate) fn explain_timestamp(
285        &self,
286        conn_id: &ConnectionId,
287        session_wall_time: DateTime<Utc>,
288        cluster_id: ClusterId,
289        id_bundle: &CollectionIdBundle,
290        determination: TimestampDetermination,
291    ) -> Result<TimestampExplanation, AdapterError> {
292        let mut sources = Vec::new();
293        {
294            let storage_ids = id_bundle.storage_ids.iter().cloned().collect_vec();
295            let frontiers = self
296                .controller
297                .storage
298                .collections_frontiers(storage_ids)
299                .map_err(AdapterError::concurrent_dependency_drop_from_collection_missing)?;
300
301            for (id, since, upper) in frontiers {
302                let name = self
303                    .catalog()
304                    .try_get_entry_by_global_id(&id)
305                    .map(|item| item.name())
306                    .map(|name| {
307                        self.catalog()
308                            .resolve_full_name(name, Some(conn_id))
309                            .to_string()
310                    })
311                    .unwrap_or_else(|| id.to_string());
312                sources.push(TimestampSource {
313                    name: format!("{name} ({id}, storage)"),
314                    read_frontier: since.elements().to_vec(),
315                    write_frontier: upper.elements().to_vec(),
316                });
317            }
318        }
319        {
320            if let Some(compute_ids) = id_bundle.compute_ids.get(&cluster_id) {
321                let catalog = self.catalog();
322                for id in compute_ids {
323                    let frontiers = self
324                        .controller
325                        .compute
326                        .collection_frontiers(*id, Some(cluster_id))
327                        .map_err(
328                            AdapterError::concurrent_dependency_drop_from_compute_lookup_error,
329                        )?;
330                    let name = catalog
331                        .try_get_entry_by_global_id(id)
332                        .map(|item| item.name())
333                        .map(|name| catalog.resolve_full_name(name, Some(conn_id)).to_string())
334                        .unwrap_or_else(|| id.to_string());
335                    sources.push(TimestampSource {
336                        name: format!("{name} ({id}, compute)"),
337                        read_frontier: frontiers.read_frontier.to_vec(),
338                        write_frontier: frontiers.write_frontier.to_vec(),
339                    });
340                }
341            }
342        }
343        let respond_immediately = determination.respond_immediately();
344        Ok(TimestampExplanation {
345            determination,
346            sources,
347            session_wall_time,
348            respond_immediately,
349        })
350    }
351
352    #[instrument]
353    fn explain_timestamp_finish(
354        &mut self,
355        session: &mut Session,
356        ExplainTimestampFinish {
357            validity: _,
358            format,
359            cluster_id,
360            source_ids,
361            when,
362            real_time_recency_ts,
363            timeline_context,
364            oracle_read_ts,
365        }: ExplainTimestampFinish,
366    ) -> Result<StageResult<Box<ExplainTimestampStage>>, AdapterError> {
367        let id_bundle = self
368            .index_oracle(cluster_id)
369            .sufficient_collections(source_ids.iter().copied());
370
371        let is_json = match format {
372            ExplainFormat::Text => false,
373            ExplainFormat::Json => true,
374            ExplainFormat::Dot => {
375                return Err(AdapterError::Unsupported("EXPLAIN TIMESTAMP AS DOT"));
376            }
377        };
378
379        let determination = self.sequence_peek_timestamp(
380            session,
381            &when,
382            cluster_id,
383            timeline_context,
384            oracle_read_ts,
385            &id_bundle,
386            &source_ids,
387            real_time_recency_ts,
388            RequireLinearization::NotRequired,
389        )?;
390        let explanation = self.explain_timestamp(
391            session.conn_id(),
392            session.pcx().wall_time,
393            cluster_id,
394            &id_bundle,
395            determination,
396        )?;
397
398        let s = if is_json {
399            serde_json::to_string_pretty(&explanation).expect("failed to serialize explanation")
400        } else {
401            explanation.to_string()
402        };
403        let rows = vec![Row::pack_slice(&[Datum::from(s.as_str())])];
404        Ok(StageResult::Response(Self::send_immediate_rows(rows)))
405    }
406}