Skip to main content

mz_environmentd/http/
prometheus.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 Apach
9
10use mz_catalog::memory::objects::{Cluster, ClusterReplica};
11use mz_ore::sql;
12use mz_ore::sql::Sql;
13
14use super::sql::SqlRequest;
15
16#[derive(Debug)]
17pub(crate) struct PrometheusSqlQuery<'a> {
18    pub(crate) metric_name: &'a str,
19    pub(crate) help: &'a str,
20    pub(crate) query: &'static str,
21    pub(crate) value_column_name: &'a str,
22    pub(crate) per_replica: bool,
23}
24
25impl<'a> PrometheusSqlQuery<'a> {
26    pub(crate) fn to_sql_request(
27        &self,
28        cluster: Option<(&Cluster, &ClusterReplica)>,
29    ) -> SqlRequest {
30        let query = Sql::new(self.query);
31        let query = if let Some((cluster, replica)) = cluster {
32            sql!(
33                "SET auto_route_catalog_queries = false; SET CLUSTER = {}; SET CLUSTER_REPLICA = {}; {}",
34                Sql::literal(&cluster.name),
35                Sql::literal(&replica.name),
36                query
37            )
38        } else {
39            sql!(
40                "SET auto_route_catalog_queries = true; RESET CLUSTER; RESET CLUSTER_REPLICA; {}",
41                query
42            )
43        };
44        SqlRequest::Simple { query }
45    }
46}
47
48pub(crate) static FRONTIER_METRIC_QUERIES: &[PrometheusSqlQuery] = &[
49    PrometheusSqlQuery {
50        metric_name: "mz_write_frontier",
51        help: "The global write frontiers of compute and storage collections.",
52        query: "SELECT
53                    object_id AS collection_id,
54                    coalesce(write_frontier::text::uint8, 18446744073709551615::uint8) AS write_frontier
55                FROM mz_internal.mz_frontiers
56                WHERE object_id NOT LIKE 't%';",
57        value_column_name: "write_frontier",
58        per_replica: false,
59    },
60    PrometheusSqlQuery {
61        metric_name: "mz_read_frontier",
62        help: "The global read frontiers of compute and storage collections.",
63        query: "SELECT
64                    object_id AS collection_id,
65                    coalesce(read_frontier::text::uint8, 18446744073709551615::uint8) AS read_frontier
66                FROM mz_internal.mz_frontiers
67                WHERE object_id NOT LIKE 't%';",
68        value_column_name: "read_frontier",
69        per_replica: false,
70    },
71    PrometheusSqlQuery {
72        metric_name: "mz_replica_write_frontiers",
73        help: "The per-replica write frontiers of compute and storage collections.",
74        query: "SELECT
75                    object_id AS collection_id,
76                    coalesce(write_frontier::text::uint8, 18446744073709551615::uint8) AS write_frontier,
77                    cluster_id AS instance_id,
78                    replica_id AS replica_id
79                FROM mz_catalog.mz_cluster_replica_frontiers
80                JOIN mz_cluster_replicas ON (id = replica_id)
81                WHERE object_id NOT LIKE 't%';",
82        value_column_name: "write_frontier",
83        per_replica: false,
84    },
85    PrometheusSqlQuery {
86        metric_name: "mz_replica_write_frontiers",
87        help: "The per-replica write frontiers of compute and storage collections.",
88        query: "SELECT
89                    object_id AS collection_id,
90                    coalesce(write_frontier::text::uint8, 18446744073709551615::uint8) AS write_frontier,
91                    cluster_id AS instance_id,
92                    replica_id AS replica_id
93                FROM mz_catalog.mz_cluster_replica_frontiers
94                JOIN mz_cluster_replicas ON (id = replica_id)
95                WHERE object_id NOT LIKE 't%';",
96        value_column_name: "write_frontier",
97        per_replica: false,
98    },
99];
100
101pub(crate) static USAGE_METRIC_QUERIES: &[PrometheusSqlQuery] = &[
102    PrometheusSqlQuery {
103        metric_name: "mz_compute_cluster_status",
104        help: "Reports the name, ID, size, and availability zone of each cluster replica. Value is always 1.",
105        query: "SELECT
106                1 AS status,
107                cr.cluster_id AS compute_cluster_id,
108                cr.id AS compute_replica_id,
109                c.name AS compute_cluster_name,
110                cr.name AS compute_replica_name,
111                COALESCE(cr.size, '') AS size,
112                COALESCE(cr.availability_zone, '') AS availability_zone,
113                mz_version() AS mz_version
114          FROM
115            mz_cluster_replicas AS cr
116            JOIN mz_clusters AS c
117              ON cr.cluster_id = c.id",
118        value_column_name: "status",
119        per_replica: false,
120    },
121    PrometheusSqlQuery {
122        metric_name: "mz_workload_clusters",
123        help: "Reports the workload_class of each user cluster. Value is always 1.",
124        query: "select
125                    c.id as cluster_id,
126                    c.name as cluster_name,
127                    coalesce(wc.workload_class,'false') as workload_class,
128                    1 as value
129                from mz_clusters c
130                join mz_internal.mz_cluster_workload_classes wc
131                    on c.id = wc.id",
132        value_column_name: "value",
133        per_replica: false,
134    },
135    PrometheusSqlQuery {
136        metric_name: "mz_clusters_count",
137        help: "Number of active clusters in the instance.",
138        query: "select
139                count(id) as clusters
140            from mz_clusters
141            where id like 'u%'",
142        value_column_name: "clusters",
143        per_replica: false,
144    },
145    PrometheusSqlQuery {
146        metric_name: "mz_cluster_reps_count",
147        help: "Number of active cluster replicas in the instance, by replica size.",
148        // `mz_cluster_replicas.size` is NULL for unmanaged replicas. Coalesce
149        // to an empty string so the resulting Prometheus label is a string.
150        query: "select COALESCE(size, '') as size
151                , count(id) as replicas
152            from mz_cluster_replicas
153            where cluster_id like 'u%'
154            group by size",
155        value_column_name: "replicas",
156        per_replica: false,
157    },
158    PrometheusSqlQuery {
159        metric_name: "mz_indexes_count",
160        help: "Number of active indexes in the instance, by the type of relation on which the index is built.",
161        query: "select
162                o.type as relation_type
163                , count(i.id) as indexes
164            from mz_catalog.mz_indexes i
165            join mz_catalog.mz_objects o
166                on i.on_id = o.id
167            where i.id like 'u%'
168            group by relation_type",
169        value_column_name: "indexes",
170        per_replica: false,
171    },
172    PrometheusSqlQuery {
173        metric_name: "mz_sources_count",
174        help: "Number of active sources in the instance, by type, envelope type, and size of source.",
175        query: "SELECT
176                type,
177                COALESCE(envelope_type, '<none>') AS envelope_type,
178                COALESCE(mz_cluster_replicas.size, '') AS size,
179                count(mz_sources.id) AS sources
180            FROM
181                mz_sources, mz_cluster_replicas
182            WHERE mz_sources.id LIKE 'u%'
183            AND mz_sources.type != 'subsource'
184            AND mz_sources.cluster_id = mz_cluster_replicas.cluster_id
185            GROUP BY
186            type, envelope_type, mz_cluster_replicas.size",
187        value_column_name: "sources",
188        per_replica: false,
189    },
190    PrometheusSqlQuery {
191        metric_name: "mz_views_count",
192        help: "Number of active views in the instance.",
193        query: "select count(id) as views from mz_views where id like 'u%'",
194        value_column_name: "views",
195        per_replica: false,
196    },
197    PrometheusSqlQuery {
198        metric_name: "mz_mzd_views_count",
199        help: "Number of active materialized views in the instance.",
200        query: "select count(id) as mzd_views from mz_materialized_views where id like 'u%'",
201        value_column_name: "mzd_views",
202        per_replica: false,
203    },
204    PrometheusSqlQuery {
205        metric_name: "mz_secrets_count",
206        help: "Number of active secrets in the instance.",
207        query: "select count(id) as secrets from mz_secrets where id like 'u%'",
208        value_column_name: "secrets",
209        per_replica: false,
210    },
211    PrometheusSqlQuery {
212        metric_name: "mz_sinks_count",
213        help: "Number of active sinks in the instance, by type, envelope type, and size.",
214        query: "SELECT
215                type,
216                COALESCE(envelope_type, '<none>') AS envelope_type,
217                COALESCE(mz_cluster_replicas.size, '') AS size,
218                count(mz_sinks.id) AS sinks
219            FROM
220                mz_sinks, mz_cluster_replicas
221            WHERE mz_sinks.id LIKE 'u%'
222            AND mz_sinks.cluster_id = mz_cluster_replicas.cluster_id
223            GROUP BY type, envelope_type, mz_cluster_replicas.size",
224        value_column_name: "sinks",
225        per_replica: false,
226    },
227    PrometheusSqlQuery {
228        metric_name: "mz_connections_count",
229        help: "Number of active connections in the instance, by type.",
230        query: "select
231                type
232                , count(id) as connections
233            from mz_connections
234            where id like 'u%'
235            group by type",
236        value_column_name: "connections",
237        per_replica: false,
238    },
239    PrometheusSqlQuery {
240        metric_name: "mz_tables_count",
241        help: "Number of active tables in the instance.",
242        query: "select count(id) as tables from mz_tables where id like 'u%'",
243        value_column_name: "tables",
244        per_replica: false,
245    },
246    PrometheusSqlQuery {
247        metric_name: "mz_catalog_items",
248        help: "Mapping internal id for catalog item.",
249        query: "SELECT
250                concat(d.name, '.', s.name, '.', o.name) AS label,
251                0 AS value
252            FROM mz_objects o
253            JOIN mz_schemas s ON (o.schema_id = s.id)
254            JOIN mz_databases d ON (s.database_id = d.id)
255            WHERE o.id LIKE 'u%'",
256        value_column_name: "value",
257        per_replica: false,
258    },
259    PrometheusSqlQuery {
260        metric_name: "mz_object_id",
261        help: "Mapping external name for catalog item.",
262        query: "SELECT
263                o.id AS label1,
264                concat(d.name, '.', s.name, '.', o.name) AS label2,
265                0 AS value
266            FROM mz_objects o
267            JOIN mz_schemas s ON (o.schema_id = s.id)
268            JOIN mz_databases d ON (s.database_id = d.id)
269            WHERE o.id LIKE 'u%'",
270        value_column_name: "value",
271        per_replica: false,
272    },
273];
274
275pub(crate) static COMPUTE_METRIC_QUERIES: &[PrometheusSqlQuery] = &[
276    PrometheusSqlQuery {
277        metric_name: "mz_arrangement_count",
278        help: "The number of arrangements in a dataflow.",
279        query: "WITH
280            arrangements AS (
281                SELECT DISTINCT operator_id AS id
282                FROM mz_internal.mz_arrangement_records_raw
283            ),
284            collections AS (
285                SELECT
286                    id,
287                    CASE
288                        WHEN starts_with(export_id, 't') THEN 'transient'
289                        ELSE export_id
290                    END AS export_id
291                FROM
292                    mz_internal.mz_dataflow_addresses,
293                    mz_internal.mz_compute_exports
294                WHERE address[1] = dataflow_id
295            )
296        SELECT
297            COALESCE(export_id, 'none') AS collection_id,
298            count(*) as count
299        FROM arrangements
300        LEFT JOIN collections USING (id)
301        GROUP BY export_id",
302        value_column_name: "count",
303        per_replica: true,
304    },
305    PrometheusSqlQuery {
306        metric_name: "mz_arrangement_record_count",
307        help: "The number of records in all arrangements in a dataflow.",
308        query: "WITH
309            collections AS (
310                SELECT
311                    id,
312                    CASE
313                        WHEN starts_with(export_id, 't') THEN 'transient'
314                        ELSE export_id
315                    END AS export_id
316                FROM
317                    mz_internal.mz_dataflow_addresses,
318                    mz_internal.mz_compute_exports
319                WHERE address[1] = dataflow_id
320            )
321        SELECT
322            worker_id,
323            COALESCE(export_id, 'none') AS collection_id,
324            count(*) as count
325        FROM mz_internal.mz_arrangement_records_raw
326        LEFT JOIN collections ON (operator_id = id)
327        GROUP BY worker_id, export_id",
328        value_column_name: "count",
329        per_replica: true,
330    },
331    PrometheusSqlQuery {
332        metric_name: "mz_arrangement_batch_count",
333        help: "The number of batches in all arrangements in a dataflow.",
334        query: "WITH
335            collections AS (
336                SELECT
337                    id,
338                    CASE
339                        WHEN starts_with(export_id, 't') THEN 'transient'
340                        ELSE export_id
341                    END AS export_id
342                FROM
343                    mz_internal.mz_dataflow_addresses,
344                    mz_internal.mz_compute_exports
345                WHERE address[1] = dataflow_id
346            )
347        SELECT
348            worker_id,
349            COALESCE(export_id, 'none') AS collection_id,
350            count(*) as count
351        FROM mz_internal.mz_arrangement_batches_raw
352        LEFT JOIN collections ON (operator_id = id)
353        GROUP BY worker_id, export_id",
354        value_column_name: "count",
355        per_replica: true,
356    },
357    PrometheusSqlQuery {
358        metric_name: "mz_arrangement_size_bytes",
359        help: "The size of all arrangements in a dataflow.",
360        query: "WITH
361            collections AS (
362                SELECT
363                    id,
364                    CASE
365                        WHEN starts_with(export_id, 't') THEN 'transient'
366                        ELSE export_id
367                    END AS export_id
368                FROM
369                    mz_internal.mz_dataflow_addresses,
370                    mz_internal.mz_compute_exports
371                WHERE address[1] = dataflow_id
372            )
373        SELECT
374            worker_id,
375            COALESCE(export_id, 'none') AS collection_id,
376            count(*) as count
377        FROM mz_internal.mz_arrangement_heap_size_raw
378        LEFT JOIN collections ON (operator_id = id)
379        GROUP BY worker_id, export_id",
380        value_column_name: "count",
381        per_replica: true,
382    },
383    PrometheusSqlQuery {
384        metric_name: "mz_arrangement_capacity_bytes",
385        help: "The capacity of all arrangements in all dataflows.",
386        query: "SELECT
387            worker_id,
388            count(*) as count
389        FROM mz_internal.mz_arrangement_heap_capacity_raw
390        GROUP BY worker_id",
391        value_column_name: "count",
392        per_replica: true,
393    },
394    PrometheusSqlQuery {
395        metric_name: "mz_arrangement_allocation_count",
396        help: "The number of allocations in all arrangements in all dataflows.",
397        query: "SELECT
398            worker_id,
399            count(*) as count
400        FROM mz_internal.mz_arrangement_heap_allocations_raw
401        GROUP BY worker_id",
402        value_column_name: "count",
403        per_replica: true,
404    },
405    PrometheusSqlQuery {
406        metric_name: "mz_compute_replica_park_duration_seconds_total",
407        help: "The total time workers were parked since restart.",
408        query: "SELECT
409            worker_id,
410            sum(slept_for_ns * count)::float8 / 1000000000 AS duration_s
411        FROM mz_internal.mz_scheduling_parks_histogram_per_worker
412        GROUP BY worker_id",
413        value_column_name: "duration_s",
414        per_replica: true,
415    },
416    PrometheusSqlQuery {
417        metric_name: "mz_compute_replica_peek_count",
418        help: "The number of pending peeks.",
419        query: "SELECT worker_id, count(*) as count
420        FROM mz_internal.mz_active_peeks_per_worker
421        GROUP BY worker_id",
422        value_column_name: "count",
423        per_replica: true,
424    },
425    PrometheusSqlQuery {
426        metric_name: "mz_dataflow_elapsed_seconds_total",
427        help: "The total time spent computing a dataflow.",
428        query: "SELECT
429            worker_id,
430            CASE
431                WHEN starts_with(export_id, 't') THEN 'transient'
432                ELSE export_id
433            END AS collection_id,
434            sum(elapsed_ns)::float8 / 1000000000 AS elapsed_s
435        FROM
436            mz_internal.mz_scheduling_elapsed_per_worker AS s,
437            mz_internal.mz_dataflow_operators AS o,
438            mz_internal.mz_dataflow_addresses AS a,
439            mz_internal.mz_compute_exports AS e
440        WHERE
441            o.id = s.id AND
442            o.id = a.id AND
443            list_length(a.address) = 1 AND
444            e.dataflow_id = a.address[1]
445        GROUP BY worker_id, collection_id",
446        value_column_name: "elapsed_s",
447        per_replica: true,
448    },
449    PrometheusSqlQuery {
450        metric_name: "mz_dataflow_error_count",
451        help: "The number of errors in a dataflow",
452        query: "SELECT
453            CASE
454                WHEN starts_with(export_id, 't') THEN 'transient'
455                ELSE export_id
456            END AS collection_id,
457            count::uint8 as count
458        FROM mz_internal.mz_compute_error_counts",
459        value_column_name: "count",
460        per_replica: true,
461    },
462];
463
464pub(crate) static STORAGE_METRIC_QUERIES: &[PrometheusSqlQuery] = &[
465    PrometheusSqlQuery {
466        metric_name: "mz_storage_objects",
467        help: "Nicely labeled information about existing sources and sinks.",
468        value_column_name: "value",
469        per_replica: false,
470        query: "
471        WITH
472            -- All user, non- progress or subsource sources.
473            top_level_sources AS (
474                SELECT id, connection_id, type, envelope_type, cluster_id
475                FROM mz_sources
476                WHERE id LIKE 'u%' AND type NOT IN ('progress', 'subsource')
477            ),
478            -- Sources enriched with the type of the core connection.
479            source_and_conns AS (
480                SELECT top_level_sources.*, mc.type AS connection_type
481                FROM top_level_sources
482                LEFT OUTER JOIN mz_connections mc ON mc.id = top_level_sources.connection_id
483            ),
484
485            -- All user sinks.
486            top_level_sinks AS (
487                SELECT id, connection_id, type, envelope_type, cluster_id
488                FROM mz_sinks
489                WHERE id LIKE 'u%'
490            ),
491            -- Sinks enriched with the type of the core connection.
492            sink_and_conns AS (
493                SELECT top_level_sinks.*, mc.type AS connection_type
494                FROM top_level_sinks
495                LEFT OUTER JOIN mz_connections mc ON mc.id = top_level_sinks.connection_id
496            ),
497
498            -- All objects we care about
499            object_and_conns AS (
500                SELECT * FROM source_and_conns
501                UNION ALL
502                SELECT * FROM sink_and_conns
503            ),
504
505            -- The networking the object connection uses, if any.
506            networking AS (
507                SELECT object_and_conns.id, mc.id AS networking_id, mc.type AS networking_type
508                FROM object_and_conns
509                JOIN mz_internal.mz_object_dependencies mod ON object_and_conns.connection_id = mod.object_id
510                JOIN mz_connections mc ON mc.id = mod.referenced_object_id
511                -- Not required but made explicit
512                WHERE object_and_conns.connection_id IS NOT NULL
513            ),
514            -- The connection the format of the object uses, if any. This uses `mz_object_dependencies`
515            -- and a filter to find non-core objects the object depends on.
516            format_conns AS (
517                SELECT object_and_conns.id, mc.id AS format_connection_id, mc.type AS format_connection
518                FROM mz_internal.mz_object_dependencies mod
519                JOIN object_and_conns ON mod.object_id = object_and_conns.id
520                JOIN mz_connections mc ON mc.id = mod.referenced_object_id
521                WHERE mc.id NOT IN (SELECT connection_id FROM top_level_sources UNION ALL SELECT connection_id FROM top_level_sinks)
522                -- Not required but made explicit
523                AND object_and_conns.connection_id IS NOT NULL
524            ),
525            -- The networking used by `format_conns`, if any.
526            format_conn_deps AS (
527                SELECT format_conns.id, mc.id AS format_connection_networking_id, mc.type AS format_connection_networking
528                FROM format_conns
529                JOIN mz_internal.mz_object_dependencies mod ON mod.object_id = format_conns.format_connection_id
530                JOIN mz_connections mc
531                ON mc.id = mod.referenced_object_id
532            ),
533
534            -- When aggregating values that are known to be the same, we just use `MAX` for simplicity.
535
536            -- source_and_conns LEFT JOINed with the networking and format connection information.
537            -- Missing networking/format connections are coalesced to `none`, and aggregated with a comma.
538            -- This is because sources can have multiple type of networking (i.e. different kafka brokers with
539            -- different configuration), and multiple connections for formats (for key and value formats).
540            --
541            -- The actual format type is not yet included, as it depends on https://github.com/MaterializeInc/materialize/pull/23880
542            sources AS (
543                SELECT
544                -- Whether its a source or sink
545                'source' AS type,
546                1 AS value,
547                source_and_conns.id AS id,
548                -- What type of source/sink it is
549                MAX(type) AS object_type,
550                COALESCE(MAX(connection_type), 'none') AS connection_type,
551                COALESCE(MAX(envelope_type), 'none') AS envelope_type,
552                STRING_AGG(
553                    DISTINCT COALESCE(networking_type, 'none'),
554                    ','
555                    ORDER BY COALESCE(networking_type, 'none') ASC
556                ) AS networking_type,
557                STRING_AGG(
558                    DISTINCT COALESCE(format_connection, 'none'),
559                    ','
560                    ORDER BY COALESCE(format_connection, 'none') ASC
561                ) AS format_connection,
562                STRING_AGG(
563                    DISTINCT COALESCE(format_connection_networking, 'none'),
564                    ','
565                    ORDER BY COALESCE(format_connection_networking, 'none') ASC
566                ) AS format_connection_networking,
567                MAX(cluster_id) AS cluster_id
568                FROM source_and_conns
569                LEFT OUTER JOIN networking ON networking.id = source_and_conns.id
570                LEFT OUTER JOIN format_conns ON format_conns.id = source_and_conns.id
571                LEFT OUTER JOIN format_conn_deps ON format_conn_deps.id = source_and_conns.id
572                GROUP BY source_and_conns.id
573            ),
574
575            sinks AS (
576                SELECT
577                'sink' AS type,
578                1 AS value,
579                sink_and_conns.id AS id,
580                MAX(type) AS object_type,
581                COALESCE(MAX(connection_type), 'none') AS connection_type,
582                COALESCE(MAX(envelope_type), 'none') AS envelope_type,
583                STRING_AGG(
584                    DISTINCT COALESCE(networking_type, 'none'),
585                    ','
586                    ORDER BY COALESCE(networking_type, 'none') ASC
587                ) AS networking_type,
588                -- Sinks can only have 1 format connection but we aggregate
589                -- for consistency.
590                STRING_AGG(
591                    DISTINCT COALESCE(format_connection, 'none'),
592                    ','
593                    ORDER BY COALESCE(format_connection, 'none') ASC
594                ) AS format_connection,
595                STRING_AGG(
596                    DISTINCT COALESCE(format_connection_networking, 'none'),
597                    ','
598                    ORDER BY COALESCE(format_connection_networking, 'none') ASC
599                ) AS format_connection_networking,
600                MAX(cluster_id) AS cluster_id
601                FROM sink_and_conns
602                LEFT OUTER JOIN networking ON networking.id = sink_and_conns.id
603                LEFT OUTER JOIN format_conns ON format_conns.id = sink_and_conns.id
604                LEFT OUTER JOIN format_conn_deps ON format_conn_deps.id = sink_and_conns.id
605                GROUP BY sink_and_conns.id
606            ),
607
608            -- everything without replicas
609            together AS (
610                SELECT * FROM sources
611                UNION ALL
612                SELECT * from sinks
613            ),
614
615            with_cluster_replicas AS (
616                SELECT
617                -- `together.*` doesn't work because we need to aggregate the columns :(
618                MAX(together.id) AS id,
619                MAX(together.type) AS type,
620                MAX(together.object_type) AS object_type,
621                -- We just report 1 to the gauge. The `replica_id` labels aggregates the 0-many replicas associated
622                -- with this object.
623                MAX(together.value) AS value,
624                MAX(together.connection_type) AS connection_type,
625                MAX(together.envelope_type) AS envelope_type,
626                MAX(together.networking_type) AS networking_type,
627                MAX(together.format_connection) AS format_connection,
628                MAX(together.format_connection_networking) AS format_connection_networking,
629                MAX(together.cluster_id) AS cluster_id,
630                mcr.id AS replica_id,
631                -- Coalesce to 0 when there is no replica. This ensures `with_pod` below will generate
632                -- 1 row for the object. Also, -1 because `generate_series` is inclusive.
633                COALESCE((mcrs.processes - 1)::int, 0) AS processes
634                FROM together
635                LEFT OUTER JOIN mz_cluster_replicas mcr ON together.cluster_id = mcr.cluster_id
636                LEFT OUTER JOIN mz_catalog.mz_cluster_replica_sizes mcrs ON mcr.size = mcrs.size
637                GROUP BY together.id, mcr.id, mcrs.processes
638            ),
639
640            with_pod AS (
641                SELECT
642                id,
643                type,
644                object_type,
645                value,
646                connection_type,
647                envelope_type,
648                networking_type,
649                format_connection,
650                format_connection_networking,
651                cluster_id,
652                COALESCE(replica_id, 'none') AS replica_id,
653                -- When `replica_id` is NULL`, this coalesces to `none`.
654                COALESCE('cluster-' || cluster_id || '-replica-' || replica_id || '-' || generated, 'none') AS synthesized_pod
655                FROM with_cluster_replicas, generate_series(0, processes) generated
656            )
657
658        SELECT * FROM with_pod;
659        ",
660    },
661];