Skip to main content

mz_adapter/coord/
introspection.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
10//! Support for unified compute introspection.
11//!
12//! Unified compute introspection is the process of collecting introspection data exported by
13//! individual replicas through their logging indexes and then writing that data, tagged with the
14//! respective replica ID, to "unified" storage collections. These storage collections then allow
15//! querying introspection data across all replicas and regardless of the health of individual
16//! replicas.
17//!
18//! # Lifecycle of Introspection Subscribes
19//!
20//! * After a new replica was created, the coordinator calls `install_introspection_subscribes` to
21//!   install all defined introspection subscribes on the new replica.
22//! * The coordinator calls `handle_introspection_subscribe_batch` for each response it receives
23//!   from an introspection subscribe, to write received updates to their corresponding
24//!   storage-managed collection.
25//! * Before a replica is dropped, the coordinator calls `drop_introspection_subscribes` to drop
26//!   all introspection subscribes previously installed on the replica.
27//! * When a replica disconnects without being dropped (e.g. because of a crash or network
28//!   failure), `handle_introspection_subscribe_batch` reacts on the corresponding error responses
29//!   by reinstalling the failed introspection subscribes.
30
31use std::collections::BTreeSet;
32use std::time::{Duration, Instant};
33
34use anyhow::bail;
35use derivative::Derivative;
36use mz_adapter_types::dyncfgs::ENABLE_INTROSPECTION_SUBSCRIBES;
37use mz_cluster_client::ReplicaId;
38use mz_compute_client::controller::error::ERROR_TARGET_REPLICA_FAILED;
39use mz_compute_client::protocol::response::SubscribeBatch;
40use mz_controller_types::ClusterId;
41use mz_ore::collections::CollectionExt;
42use mz_ore::soft_panic_or_log;
43use mz_repr::optimize::OverrideFrom;
44use mz_repr::{Datum, GlobalId, Row};
45use mz_sql::catalog::SessionCatalog;
46use mz_sql::plan::{Params, Plan, SubscribePlan};
47use mz_sql::session::user::{MZ_SYSTEM_ROLE_ID, RoleMetadata};
48use mz_storage_client::controller::{IntrospectionType, StorageWriteOp};
49use tracing::{Span, info};
50
51use crate::coord::{
52    Coordinator, IntrospectionSubscribeFinish, IntrospectionSubscribeOptimizeMir,
53    IntrospectionSubscribeStage, IntrospectionSubscribeTimestampOptimizeLir, Message, PlanValidity,
54    StageResult, Staged,
55};
56use crate::optimize::Optimize;
57use crate::{AdapterError, ExecuteResponse, optimize};
58
59// State tracked about an active introspection subscribe.
60#[derive(Derivative)]
61#[derivative(Debug)]
62pub(super) struct IntrospectionSubscribe {
63    /// The ID of the targeted cluster.
64    cluster_id: ClusterId,
65    /// The ID of the targeted replica.
66    replica_id: ReplicaId,
67    /// The spec from which this subscribe was created.
68    spec: &'static SubscribeSpec,
69    /// A storage write to be applied the next time the introspection subscribe produces any
70    /// output.
71    ///
72    /// This mechanism exists to delay the deletion of previous subscribe results from the target
73    /// storage collection when an introspection subscribe is reinstalled. After reinstallation it
74    /// can take a while for the new subscribe dataflow to produce its snapshot and keeping the old
75    /// introspection data around in the meantime makes for a better UX than removing it.
76    #[derivative(Debug = "ignore")]
77    deferred_write: Option<StorageWriteOp>,
78    /// When this subscribe first appended data to the target storage collection, if it has.
79    ///
80    /// Until then, the target collection may still contain rows written by a previous incarnation
81    /// of this subscribe (before an environmentd restart, or before the target replica
82    /// reconnected). Consumers that must not observe such stale rows, like the
83    /// `mz_object_arrangement_size_history` snapshots, use this to judge per-replica freshness.
84    first_data_at: Option<Instant>,
85}
86
87impl IntrospectionSubscribe {
88    /// Returns a `StorageWriteOp` that instructs the deletion of all data previously written by
89    /// this subscribe.
90    fn delete_write_op(&self) -> StorageWriteOp {
91        let target_replica = self.replica_id.to_string();
92        let filter = Box::new(move |row: &Row| {
93            let replica_id = row.unpack_first();
94            replica_id == Datum::String(&target_replica)
95        });
96        StorageWriteOp::Delete { filter }
97    }
98}
99
100impl Coordinator {
101    /// Installs introspection subscribes on all existing replicas.
102    ///
103    /// Meant to be invoked during coordinator bootstrapping.
104    pub(super) async fn bootstrap_introspection_subscribes(&mut self) {
105        let mut cluster_replicas = Vec::new();
106        for cluster in self.catalog.clusters() {
107            for replica in cluster.replicas() {
108                cluster_replicas.push((cluster.id, replica.replica_id));
109            }
110        }
111
112        for (cluster_id, replica_id) in cluster_replicas {
113            self.install_introspection_subscribes(cluster_id, replica_id)
114                .await;
115        }
116    }
117
118    /// Installs introspection subscribes on the given replica.
119    pub(super) async fn install_introspection_subscribes(
120        &mut self,
121        cluster_id: ClusterId,
122        replica_id: ReplicaId,
123    ) {
124        let dyncfgs = self.catalog().system_config().dyncfgs();
125        if !ENABLE_INTROSPECTION_SUBSCRIBES.get(dyncfgs) {
126            return;
127        }
128
129        for spec in SUBSCRIBES {
130            self.install_introspection_subscribe(cluster_id, replica_id, spec)
131                .await;
132        }
133    }
134
135    async fn install_introspection_subscribe(
136        &mut self,
137        cluster_id: ClusterId,
138        replica_id: ReplicaId,
139        spec: &'static SubscribeSpec,
140    ) {
141        let (_, id) = self.allocate_transient_id();
142        info!(
143            %id,
144            %replica_id,
145            type_ = ?spec.introspection_type,
146            "installing introspection subscribe",
147        );
148
149        // Sequencing is performed asynchronously, and the target replica may be dropped before it
150        // completes. To ensure the subscribe does not leak in this case, we need to already add it
151        // to the coordinator state here, rather than at the end of sequencing.
152        let subscribe = IntrospectionSubscribe {
153            cluster_id,
154            replica_id,
155            spec,
156            deferred_write: None,
157            first_data_at: None,
158        };
159        self.introspection_subscribes.insert(id, subscribe);
160
161        self.sequence_introspection_subscribe(id, spec, cluster_id, replica_id)
162            .await;
163    }
164
165    async fn sequence_introspection_subscribe(
166        &mut self,
167        subscribe_id: GlobalId,
168        spec: &'static SubscribeSpec,
169        cluster_id: ClusterId,
170        replica_id: ReplicaId,
171    ) {
172        let catalog = self.catalog().for_system_session();
173        let plan = spec.to_plan(&catalog).expect("valid spec");
174
175        let role_metadata = RoleMetadata::new(MZ_SYSTEM_ROLE_ID);
176        let dependencies = plan
177            .from
178            .depends_on()
179            .iter()
180            .map(|id| self.catalog().resolve_item_id(id))
181            .collect();
182        let validity = PlanValidity::new(
183            &self.catalog,
184            dependencies,
185            Some(cluster_id),
186            Some(replica_id),
187            role_metadata,
188        );
189
190        let stage = IntrospectionSubscribeStage::OptimizeMir(IntrospectionSubscribeOptimizeMir {
191            validity,
192            plan,
193            subscribe_id,
194            cluster_id,
195            replica_id,
196        });
197        self.sequence_staged((), Span::current(), stage).await;
198    }
199
200    fn sequence_introspection_subscribe_optimize_mir(
201        &self,
202        stage: IntrospectionSubscribeOptimizeMir,
203    ) -> Result<StageResult<Box<IntrospectionSubscribeStage>>, AdapterError> {
204        let IntrospectionSubscribeOptimizeMir {
205            mut validity,
206            plan,
207            subscribe_id,
208            cluster_id,
209            replica_id,
210        } = stage;
211
212        let compute_instance = self.instance_snapshot(cluster_id).expect("must exist");
213        let (_, view_id) = self.allocate_transient_id();
214
215        let vars = self.catalog().system_config();
216        let overrides = self.catalog.get_cluster(cluster_id).config.features();
217        let optimizer_config = optimize::OptimizerConfig::from(vars)
218            .override_from(&overrides)
219            .override_from(&self.cluster_scoped_optimizer_overrides(cluster_id));
220
221        let mut optimizer = optimize::subscribe::Optimizer::new(
222            self.owned_catalog(),
223            compute_instance,
224            view_id,
225            subscribe_id,
226            plan.with_snapshot,
227            None,
228            format!("introspection-subscribe-{subscribe_id}"),
229            optimizer_config,
230            self.optimizer_metrics(),
231        );
232        let catalog = self.owned_catalog();
233
234        let span = Span::current();
235        Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
236            || "optimize introspection subscribe (mir)",
237            move || {
238                span.in_scope(|| {
239                    // MIR ⇒ MIR optimization (global)
240                    let global_mir_plan = optimizer.catch_unwind_optimize(plan)?;
241                    // Add introduced indexes as validity dependencies.
242                    let id_bundle = global_mir_plan.id_bundle(cluster_id);
243                    let item_ids = id_bundle.iter().map(|id| catalog.resolve_item_id(&id));
244                    validity.extend_dependencies(&catalog, item_ids);
245
246                    let stage = IntrospectionSubscribeStage::TimestampOptimizeLir(
247                        IntrospectionSubscribeTimestampOptimizeLir {
248                            validity,
249                            optimizer,
250                            global_mir_plan,
251                            cluster_id,
252                            replica_id,
253                        },
254                    );
255                    Ok(Box::new(stage))
256                })
257            },
258        )))
259    }
260
261    fn sequence_introspection_subscribe_timestamp_optimize_lir(
262        &self,
263        stage: IntrospectionSubscribeTimestampOptimizeLir,
264    ) -> Result<StageResult<Box<IntrospectionSubscribeStage>>, AdapterError> {
265        let IntrospectionSubscribeTimestampOptimizeLir {
266            validity,
267            mut optimizer,
268            global_mir_plan,
269            cluster_id,
270            replica_id,
271        } = stage;
272
273        // Timestamp selection.
274        let id_bundle = global_mir_plan.id_bundle(cluster_id);
275        let read_holds = self.acquire_read_holds(&id_bundle);
276        let as_of = read_holds.least_valid_read();
277
278        let global_mir_plan = global_mir_plan.resolve(as_of);
279
280        let span = Span::current();
281        Ok(StageResult::Handle(mz_ore::task::spawn_blocking(
282            || "optimize introspection subscribe (lir)",
283            move || {
284                span.in_scope(|| {
285                    // MIR ⇒ LIR lowering and LIR ⇒ LIR optimization (global)
286                    let global_lir_plan =
287                        optimizer.catch_unwind_optimize(global_mir_plan.clone())?;
288
289                    let stage = IntrospectionSubscribeStage::Finish(IntrospectionSubscribeFinish {
290                        validity,
291                        global_lir_plan,
292                        read_holds,
293                        cluster_id,
294                        replica_id,
295                    });
296                    Ok(Box::new(stage))
297                })
298            },
299        )))
300    }
301
302    async fn sequence_introspection_subscribe_finish(
303        &mut self,
304        stage: IntrospectionSubscribeFinish,
305    ) -> Result<StageResult<Box<IntrospectionSubscribeStage>>, AdapterError> {
306        let IntrospectionSubscribeFinish {
307            validity: _,
308            global_lir_plan,
309            read_holds,
310            cluster_id,
311            replica_id,
312        } = stage;
313
314        let subscribe_id = global_lir_plan.sink_id();
315
316        // The subscribe may already have been dropped, in which case we must not install a
317        // dataflow for it.
318        let response = if self.introspection_subscribes.contains_key(&subscribe_id) {
319            let (df_desc, _df_meta) = global_lir_plan.unapply();
320            self.ship_dataflow(df_desc, cluster_id, Some(replica_id))
321                .await;
322
323            Ok(StageResult::Response(
324                ExecuteResponse::CreatedIntrospectionSubscribe,
325            ))
326        } else {
327            Err(AdapterError::internal(
328                "introspection",
329                "introspection subscribe has already been dropped",
330            ))
331        };
332
333        drop(read_holds);
334        response
335    }
336
337    /// Drops the introspection subscribes installed on the given replica.
338    ///
339    /// Dropping an introspection subscribe entails:
340    ///  * removing it from [`Coordinator::introspection_subscribes`]
341    ///  * dropping its compute collection
342    ///  * retracting any rows previously omitted by it from its corresponding storage-managed
343    ///    collection
344    pub(super) fn drop_introspection_subscribes(&mut self, replica_id: ReplicaId) {
345        let to_drop: Vec<_> = self
346            .introspection_subscribes
347            .iter()
348            .filter(|(_, s)| s.replica_id == replica_id)
349            .map(|(id, _)| *id)
350            .collect();
351
352        for id in to_drop {
353            self.drop_introspection_subscribe(id);
354        }
355    }
356
357    fn drop_introspection_subscribe(&mut self, id: GlobalId) {
358        let Some(subscribe) = self.introspection_subscribes.remove(&id) else {
359            soft_panic_or_log!("attempt to remove unknown introspection subscribe (id={id})");
360            return;
361        };
362
363        info!(
364            %id,
365            replica_id = %subscribe.replica_id,
366            type_ = ?subscribe.spec.introspection_type,
367            "dropping introspection subscribe",
368        );
369
370        // This can fail if the sequencing hasn't finished yet for the subscribe. In this case,
371        // `sequence_introspection_subscribe_finish` will skip installing the compute collection in
372        // the first place.
373        let _ = self
374            .controller
375            .compute
376            .drop_collections(subscribe.cluster_id, vec![id]);
377
378        self.controller.storage.update_introspection_collection(
379            subscribe.spec.introspection_type,
380            subscribe.delete_write_op(),
381        );
382    }
383
384    async fn reinstall_introspection_subscribe(&mut self, id: GlobalId) {
385        let Some(mut subscribe) = self.introspection_subscribes.remove(&id) else {
386            soft_panic_or_log!("attempt to reinstall unknown introspection subscribe (id={id})");
387            return;
388        };
389
390        // Note that we don't simply call `drop_introspection_subscribe` here because that would
391        // cause an immediate deletion of all data previously reported by the subscribe from its
392        // target storage collection. We'd like to not present empty introspection data while the
393        // replica reconnects, so we want to delay the `StorageWriteOp::Delete` until then.
394
395        let IntrospectionSubscribe {
396            cluster_id,
397            replica_id,
398            spec,
399            ..
400        } = subscribe;
401        let old_id = id;
402        let (_, new_id) = self.allocate_transient_id();
403
404        info!(
405            %old_id, %new_id, %replica_id,
406            type_ = ?subscribe.spec.introspection_type,
407            "reinstalling introspection subscribe",
408        );
409
410        if let Err(error) = self
411            .controller
412            .compute
413            .drop_collections(cluster_id, vec![old_id])
414        {
415            soft_panic_or_log!(
416                "error dropping compute collection for introspection subscribe: {error} \
417                 (id={old_id}, cluster_id={cluster_id})"
418            );
419        }
420
421        // Ensure that the contents of the target storage collection are cleaned when the new
422        // subscribe starts reporting data.
423        subscribe.deferred_write = Some(subscribe.delete_write_op());
424        // Until then, the collection serves the previous subscribe's data, which the replica may
425        // have invalidated by restarting.
426        subscribe.first_data_at = None;
427
428        self.introspection_subscribes.insert(new_id, subscribe);
429        self.sequence_introspection_subscribe(new_id, spec, cluster_id, replica_id)
430            .await;
431    }
432
433    /// Processes a batch returned by an introspection subscribe.
434    ///
435    /// Depending on the contents of the batch, this either appends received updates to the
436    /// corresponding storage-managed collection, or reinstalls a disconnected subscribe.
437    pub(super) async fn handle_introspection_subscribe_batch(
438        &mut self,
439        id: GlobalId,
440        batch: SubscribeBatch,
441    ) {
442        let Some(subscribe) = self.introspection_subscribes.get_mut(&id) else {
443            soft_panic_or_log!("updates for unknown introspection subscribe (id={id})");
444            return;
445        };
446
447        let updates = match batch.updates {
448            Ok(updates) if updates.is_empty() => return,
449            Ok(updates) => updates,
450            Err(error) if error == ERROR_TARGET_REPLICA_FAILED => {
451                // The target replica disconnected, reinstall the subscribe.
452                self.reinstall_introspection_subscribe(id).await;
453                return;
454            }
455            Err(error) => {
456                soft_panic_or_log!(
457                    "introspection subscribe produced an error: {error} \
458                     (id={id}, subscribe={subscribe:?})",
459                );
460                return;
461            }
462        };
463
464        // Prepend the `replica_id` to each row.
465        let replica_id = subscribe.replica_id.to_string();
466        let mut new_updates = Vec::with_capacity(updates.len());
467        let mut new_row = Row::default();
468        for collection in updates {
469            for (row, _time, diff) in collection.iter() {
470                let mut packer = new_row.packer();
471                packer.push(Datum::String(&replica_id));
472                packer.extend_by_row_ref(row);
473                new_updates.push((new_row.clone(), diff));
474            }
475        }
476
477        // If we have a pending deferred write, we need to apply it _before_ the append of the new
478        // rows.
479        if let Some(op) = subscribe.deferred_write.take() {
480            self.controller
481                .storage
482                .update_introspection_collection(subscribe.spec.introspection_type, op);
483        }
484
485        subscribe.first_data_at.get_or_insert_with(Instant::now);
486
487        self.controller.storage.update_introspection_collection(
488            subscribe.spec.introspection_type,
489            StorageWriteOp::Append {
490                updates: new_updates,
491            },
492        );
493    }
494
495    /// Invalidates introspection-subscribe freshness for the given replica.
496    ///
497    /// Called when a cluster event reports one of the replica's processes offline or restarted.
498    /// The target collections may keep serving data written for the previous incarnation of the
499    /// replica, and the subscribe failures that will reinstall them can arrive after further
500    /// consumers of `fresh_introspection_replicas` have run. Freshness returns once a subscribe
501    /// delivers data again.
502    pub(super) fn invalidate_introspection_freshness(&mut self, replica_id: ReplicaId) {
503        for subscribe in self.introspection_subscribes.values_mut() {
504            if subscribe.replica_id == replica_id {
505                subscribe.first_data_at = None;
506            }
507        }
508    }
509
510    /// Returns the IDs of replicas whose introspection subscribe of the given type first
511    /// delivered data at least `margin` ago.
512    ///
513    /// Rows for other replicas in the corresponding storage collection are not trustworthy: they
514    /// either predate this environmentd process or were written before the replica reconnected,
515    /// and may describe a previous incarnation of the replica. The margin accounts for the
516    /// subscribe's first append becoming visible to readers only asynchronously (the collection
517    /// manager flushes writes in batches, and snapshot reads use an oracle timestamp that trails
518    /// the wall clock).
519    pub(super) fn fresh_introspection_replicas(
520        &self,
521        introspection_type: IntrospectionType,
522        margin: Duration,
523    ) -> BTreeSet<String> {
524        self.introspection_subscribes
525            .values()
526            .filter(|s| s.spec.introspection_type == introspection_type)
527            .filter(|s| s.first_data_at.is_some_and(|at| at.elapsed() >= margin))
528            .map(|s| s.replica_id.to_string())
529            .collect()
530    }
531}
532
533impl Staged for IntrospectionSubscribeStage {
534    type Ctx = ();
535
536    fn validity(&mut self) -> &mut PlanValidity {
537        match self {
538            Self::OptimizeMir(stage) => &mut stage.validity,
539            Self::TimestampOptimizeLir(stage) => &mut stage.validity,
540            Self::Finish(stage) => &mut stage.validity,
541        }
542    }
543
544    async fn stage(
545        self,
546        coord: &mut Coordinator,
547        _ctx: &mut (),
548    ) -> Result<StageResult<Box<Self>>, AdapterError> {
549        match self {
550            Self::OptimizeMir(stage) => coord.sequence_introspection_subscribe_optimize_mir(stage),
551            Self::TimestampOptimizeLir(stage) => {
552                coord.sequence_introspection_subscribe_timestamp_optimize_lir(stage)
553            }
554            Self::Finish(stage) => coord.sequence_introspection_subscribe_finish(stage).await,
555        }
556    }
557
558    fn message(self, _ctx: (), span: Span) -> super::Message {
559        Message::IntrospectionSubscribeStageReady { span, stage: self }
560    }
561
562    fn cancel_enabled(&self) -> bool {
563        false
564    }
565}
566
567/// The specification for an introspection subscribe.
568#[derive(Debug)]
569pub(super) struct SubscribeSpec {
570    /// An [`IntrospectionType`] identifying the storage-managed collection to which updates
571    /// received from subscribes instantiated from this spec are written.
572    introspection_type: IntrospectionType,
573    /// The SQL definition of the subscribe.
574    sql: &'static str,
575}
576
577impl SubscribeSpec {
578    fn to_plan(&self, catalog: &dyn SessionCatalog) -> Result<SubscribePlan, anyhow::Error> {
579        let parsed = mz_sql::parse::parse(self.sql)?.into_element();
580        let (stmt, resolved_ids) = mz_sql::names::resolve(catalog, parsed.ast)?;
581        let (plan, _sql_impl_ids) =
582            mz_sql::plan::plan(None, catalog, stmt, &Params::empty(), &resolved_ids)?;
583        match plan {
584            Plan::Subscribe(plan) => Ok(plan),
585            _ => bail!("unexpected plan type: {plan:?}"),
586        }
587    }
588}
589
590const SUBSCRIBES: &[SubscribeSpec] = &[
591    SubscribeSpec {
592        introspection_type: IntrospectionType::ComputeErrorCounts,
593        sql: "SUBSCRIBE (
594            SELECT export_id, sum(count)
595            FROM mz_introspection.mz_compute_error_counts_raw
596            GROUP BY export_id
597        )",
598    },
599    SubscribeSpec {
600        introspection_type: IntrospectionType::ComputeHydrationTimes,
601        sql: "SUBSCRIBE (
602            SELECT
603                export_id,
604                CASE count(*) = count(time_ns)
605                    WHEN true THEN max(time_ns)
606                    ELSE NULL
607                END AS time_ns
608            FROM mz_introspection.mz_compute_hydration_times_per_worker
609            WHERE export_id NOT LIKE 't%'
610            GROUP BY export_id
611            OPTIONS (AGGREGATE INPUT GROUP SIZE = 1)
612        )",
613    },
614    SubscribeSpec {
615        introspection_type: IntrospectionType::ComputeOperatorHydrationStatus,
616        sql: "SUBSCRIBE (
617            SELECT
618                export_id,
619                lir_id,
620                bool_and(hydrated) AS hydrated
621            FROM mz_introspection.mz_compute_operator_hydration_statuses_per_worker
622            GROUP BY export_id, lir_id
623        )",
624    },
625    // Per-object arrangement sizes, one row per `(object_id, replica)`,
626    // populating `mz_object_arrangement_sizes`.
627    //
628    // `mz_arrangement_heap_size_raw` and `mz_arrangement_batcher_size_raw` are
629    // differential logs where each `+1` row represents one byte of heap delta;
630    // after consolidation, `COUNT(*)` is the current arrangement size in bytes.
631    //
632    // Sizes are quantized to the nearest 10 MiB: the heap-size collection
633    // wiggles by a few bytes per second from ordinary allocator activity, and
634    // emitting exact bytes would push a downstream update on every wiggle.
635    // Arrangements below 5 MiB quantize to a size of 0. They are kept rather
636    // than dropped so that every arranged object is present in the collection.
637    //
638    // `mz_dataflow_addresses.address[1]` is the root of each operator's address
639    // tree, which equals the owning `dataflow_id` — so we can go addresses →
640    // operator → dataflow without joining `mz_dataflow_operator_dataflows`.
641    //
642    // Joining on `ce.dataflow_id` assumes one dataflow exports a single object;
643    // if that stops holding, the same arrangement bytes would be attributed
644    // to multiple `export_id`s and we'd need to revisit the granularity.
645    //
646    // Transient export IDs (`t*`) are ephemeral dataflows (peeks, subscribes,
647    // including this one); we drop them to avoid self-feedback churn.
648    SubscribeSpec {
649        introspection_type: IntrospectionType::ComputeObjectArrangementSizes,
650        sql: "SUBSCRIBE (
651            SELECT
652                ce.export_id AS object_id,
653                ((COUNT(*) + 5242880) / 10485760 * 10485760)::int8 AS size
654            FROM mz_introspection.mz_compute_exports AS ce
655            JOIN (
656                SELECT addrs.address[1] AS dataflow_id, addrs.id AS operator_id
657                FROM mz_introspection.mz_dataflow_addresses addrs
658            ) AS od ON od.dataflow_id = ce.dataflow_id
659            JOIN (
660                SELECT operator_id FROM mz_introspection.mz_arrangement_heap_size_raw
661                UNION ALL
662                SELECT operator_id FROM mz_introspection.mz_arrangement_batcher_size_raw
663            ) AS rs ON rs.operator_id = od.operator_id
664            WHERE ce.export_id NOT LIKE 't%'
665            GROUP BY ce.export_id
666        )",
667    },
668];