Common Queries#
These queries are used in various dashboards and may be a good starting point for a dashboard developer or for someone looking into particulars of a dashboard.
Most environments use
mz_for mzSqlPrefix and do not setmzEnvironmentFilter.
materialize-clusters#
Inventory and sizing of a Materialize deployment’s clusters and replicas. Adapted from the Overview dashboard’s “Cluster Objects / Replicas” tab.
materialize.clusters.count #
How many clusters exist, split into the Materialize-managed system clusters (mz_catalog_server, mz_system, mz_probe, …) that every environment has and the user clusters you created. The gap between the two is your own footprint.count(
group by (compute_cluster_id) (
${mzSqlPrefix}compute_cluster_status{${mzEnvironmentFilter}, compute_cluster_id=~"${mzClusterList}"}
)
)
count(
group by (compute_cluster_id) (
${mzSqlPrefix}compute_cluster_status{${mzEnvironmentFilter}, compute_cluster_id=~"${mzClusterList}", compute_cluster_id=~"^s.*"}
)
)
materialize.clusters.replicas.count #
How many replicas back the selected clusters, and how many of those are redundancy beyond the first. Every cluster needs one replica to run; anything above that is capacity or availability headroom you’ve opted into, so a non-zero “additional” count is the quick check that HA is actually configured where you expect it.count(
group by (compute_cluster_id, compute_replica_id) (
${mzSqlPrefix}compute_cluster_status{${mzEnvironmentFilter}, compute_cluster_id=~"${mzClusterList}", compute_replica_id=~"${mzReplicaList}"}
)
)
count(
group by (compute_cluster_id, compute_replica_id) (
${mzSqlPrefix}compute_cluster_status{${mzEnvironmentFilter}, compute_cluster_id=~"${mzClusterList}", compute_replica_id=~"${mzReplicaList}", compute_replica_name!="r1"}
)
)
materialize.clusters.replicas.sizes #
The replica fleet grouped by configured size. Most deployments settle on a handful of sizes; a long tail of one-off sizes usually means an experiment or a half-finished migration. The total agrees with the replica count.count by (size) (
${mzSqlPrefix}compute_cluster_status{${mzEnvironmentFilter}, compute_cluster_id=~"${mzClusterList}", compute_replica_id=~"${mzReplicaList}"}
)
materialize.clusters.info #
A reference row per (cluster, replica): ids, names, size, version, and scheduling metadata. The “what does my fleet actually look like” lookup — most useful for grabbing a cluster or replica id to scope the rest of a dashboard to.${mzSqlPrefix}compute_cluster_status{${mzEnvironmentFilter}, compute_cluster_id=~"${mzClusterList}", compute_replica_id=~"${mzReplicaList}"}
materialize-compute#
Queries for the compute side of a Materialize deployment — the indexes, materialized views, and subscribes that run as dataflows on cluster replicas, plus their freshness, hydration, and resource footprint.
materialize.compute.materialized_views.count #
Materialized views Materialize is actively maintaining. Each one is a query whose result is kept continuously up to date, so this tracks roughly how much standing compute the environment carries.max(${mzSqlPrefix}mzd_views_count{${mzEnvironmentFilter}})
materialize.compute.indexes.count #
Indexes in the catalog. An index is an in-memory arrangement that makes reads against its relation effectively instant, in exchange for memory — so growth here is a leading indicator of cluster memory growth.max(sum by (instance) (${mzSqlPrefix}indexes_count{${mzEnvironmentFilter}}))
materialize.compute.views.count #
Non-materialized views — query templates evaluated on demand. They cost nothing until something reads them, so this is a catalog-shape signal rather than a load one.max(${mzSqlPrefix}views_count{${mzEnvironmentFilter}})
materialize.compute.subscribes.active #
Live SUBSCRIBE sessions — long-running queries that stream updates to a client as data changes. A handful ofsystem subscribes are
Materialize’s own internal probes; a persistently climbing user count
is a classic leaked-connection signal.sum by (session_type) (mz_active_subscribes{${mzEnvironmentFilter}})
materialize.compute.indexes.by_type #
Indexes split by the kind of relation they sit on. Workloads normally lean heavily on indexes over views (the standard “keep a query’s result hot” pattern); a large share of indexes on base tables is unusual and usually worth a second look.sum by (relation_type) (${mzSqlPrefix}indexes_count{${mzEnvironmentFilter}})
materialize.compute.hydration.currently_hydrating #
Collections still rebuilding their in-memory state — a live hydration-queue proxy. After a restart, replica creation, or some DDL, a dataflow has to rebuild from persisted storage before it can serve, and until it does it has no output frontier.count(
max by (instance_id, collection_id) (
mz_dataflow_wallclock_lag_seconds{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}", instance_id!="", quantile="1"} > 1e15
)
)
materialize.compute.hydration.queue_size #
Collections waiting in each replica’s hydration queue. environmentd schedules hydration in batches; a backlog means work is arriving faster than the replica can rebuild it.sum by (instance_id, replica_id) (
mz_compute_controller_hydration_queue_size{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}", replica_id=~"${mzReplicaList}"}
) > 0
materialize.compute.hydration.slowest_collections #
The 15 collections that took longest to finish hydrating. Hydration time scales with the size of the state being rebuilt, so large materialized views and indexes naturally top the list.topk(15,
${mzSqlPrefix}compute_hydration_time_seconds{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}", replica_id=~"${mzReplicaList}", hydrated="1"}
)
materialize.compute.freshness.lag_by_cluster #
How far behind real time each cluster’s most-lagged collection is — the worst-case freshness across every index, materialized view, and source on the cluster.max by (instance_id) (
mz_dataflow_wallclock_lag_seconds{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}", instance_id!="", quantile="1"} < 1e9
)
materialize.compute.freshness.top_collections #
The 15 collections whose output frontier is furthest behind real time — the per-collection breakdown behind the per-cluster freshness lag, labeled by object name.topk(15,
max by (instance_id, collection_id) (
mz_dataflow_wallclock_lag_seconds{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}", instance_id!="", replica_id=~"${mzReplicaList}", quantile="1"} < 1e9
)
)
materialize.compute.dataflows.count #
Active dataflows on each replica. Every index, materialized view, and live SUBSCRIBE runs as one or more dataflows, so this count rises with DDL and subscribe activity.max by (cluster_environmentd_materialize_cloud_cluster_id, cluster_environmentd_materialize_cloud_replica_id) (
mz_compute_replica_history_dataflow_count{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}
)
materialize.compute.dataflows.count_by_worker #
The dataflow count broken out per worker. Workers in a replica run in lockstep and should see exactly the same dataflows, so their series should overlap perfectly.max by (cluster_environmentd_materialize_cloud_cluster_id, cluster_environmentd_materialize_cloud_replica_id, worker_id) (
mz_compute_replica_history_dataflow_count{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}
)
materialize.compute.dataflows.elapsed_rate #
CPU-cores busy inside dataflows, per cluster — the whole of dataflow work: arrangement maintenance, query evaluation, and hydration. Capped by cluster size (a 400cc cluster can’t exceed 400 cores).sum by (instance_id) (
rate(
${mzSqlPrefix}dataflow_elapsed_seconds_total{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}", replica_id=~"${mzReplicaList}"}${interval}
)
)
materialize.compute.arrangements.maintenance_rate #
CPU-cores spent maintaining arrangements — the in-memory indexed snapshots behind every index and materialized view — summed across a replica’s workers, so an N-worker replica can reach N.sum by (cluster_environmentd_materialize_cloud_cluster_id, cluster_environmentd_materialize_cloud_replica_id) (
rate(
mz_arrangement_maintenance_seconds_total{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval}
)
)
materialize.compute.arrangements.maintenance_rate_by_worker #
The same maintenance CPU, split per worker — each worker tops out at 1.0.sum by (cluster_environmentd_materialize_cloud_cluster_id, cluster_environmentd_materialize_cloud_replica_id, worker_id) (
rate(
mz_arrangement_maintenance_seconds_total{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval}
)
)
materialize.compute.arrangements.records.system #
Row counts of arrangements for Materialize’s internal system collections (collection id starts withs). These back the catalog and internal
probes, not user data, so they shouldn’t grow with your workload —
unexpected growth here can point at a Materialize bug.max by (collection_id) (
${mzSqlPrefix}arrangement_record_count{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}", replica_id=~"${mzReplicaList}", collection_id=~"s.*"}
)
materialize.compute.arrangements.records.user #
Row counts of arrangements for your compute objects (collection id starts withu) — the row count of every user index and materialized view, and
the primary driver of cluster memory. Growth on a collection tracks the
size of its underlying data.max by (collection_id) (
${mzSqlPrefix}arrangement_record_count{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}", replica_id=~"${mzReplicaList}", collection_id=~"u.*"}
)
materialize.compute.arrangements.records.transient #
Row counts of transient (collection idt*) and uncategorized (none)
arrangements — short-lived intermediates from query optimization and
dataflow execution. Normally small and ephemeral.max by (collection_id) (
${mzSqlPrefix}arrangement_record_count{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}", replica_id=~"${mzReplicaList}", collection_id=~"t.*|none"}
)
materialize-connections#
Sessions, query activity, and SQL control-plane (adapter) traffic for a Materialize deployment. Adapted from the Overview dashboard’s “Connections / Activity” tab.
materialize.connections.sessions.active #
Open SQL sessions, split intosystem (Materialize’s internal probing —
a few are always present) and user (client connections).sum by (session_type) (mz_active_sessions{${mzEnvironmentFilter}})
materialize.connections.queries.rate #
Queries per second by session type —user tracks your client traffic,
system is the steady single-digit baseline of internal health checks.
Bursty is normal.sum by (session_type) (rate(mz_query_total{${mzEnvironmentFilter}}${interval}))
materialize.connections.adapter.command_rate #
Commands per second through the adapter — the SQL protocol layer (parse, execute, prepare, fetch). Normally runs higher than the query rate, since one query is several commands.sum(rate(mz_adapter_commands{${mzEnvironmentFilter}}${interval}))
materialize.connections.queries.distribution #
The mix of query kinds over the selected window — a workload-shape signal, not a rate. Heavyset_variable/fetch traffic is normal
(that’s how Postgres clients manage session state); heavy
insert/update/delete on something you think of as read-mostly is
worth a look.sum by (statement_type) (increase(mz_query_total{${mzEnvironmentFilter}}${range})) > 0
materialize.connections.queries.rate_by_statement #
Query rate broken down by statement type and session type, fully time-resolved — the moving picture behind the distribution donut. A spike inselect/user is the thing to line up against peek latency
to confirm the system kept pace.sum by (statement_type, session_type) (rate(mz_query_total{${mzEnvironmentFilter}}${interval})) > 0
materialize.connections.peek_latency.p50 #
Median read-query latency — the typical time to look up the current state of an arrangement, which is the operation behind everySELECT
against an index. Your “what does a normal query feel like” number.histogram_quantile(0.50,
sum by (le, instance_id) (
rate(mz_compute_peek_duration_seconds_bucket{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}"}${interval})
)
)
materialize.connections.peek_latency.p90 #
90th-percentile read-query latency — how slow the slowest 10% of queries feel. Catches the contention bursts and cold paths that the median hides.histogram_quantile(0.90,
sum by (le, instance_id) (
rate(mz_compute_peek_duration_seconds_bucket{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}"}${interval})
)
)
materialize.connections.peek_latency.p99 #
Tail read-query latency — the slowest 1% of queries, the ones users complain about.histogram_quantile(0.99,
sum by (le, instance_id) (
rate(mz_compute_peek_duration_seconds_bucket{${mzEnvironmentFilter}, instance_id=~"${mzClusterList}"}${interval})
)
)
materialize.connections.adapter.commands_by_application #
SQL control-plane command totals per clientapplication_name over the
window, so you can see which clients drive the adapter and which are
failing. Most clients set application_name in their connection string;
those that don’t bucket as unrecognized/unspecified (normal).sum by (application_name, status) (increase(mz_adapter_commands{${mzEnvironmentFilter}}${range}))
materialize-health#
Common queries for checking the health of a Materialize deployment.
materialize.scraper.mzmon.environmentd #
environmentd metrics are reaching the gateway. If this goes to 0 or disappears, every environmentd-backed panel and alert for the environment is blind — the environment may be perfectly healthy and you simply can’t see it, so rule this out first.up{
job="monitoring/mzmon-materialize-environmentd",
${mzEnvironmentNamespaceFilter}
} == 1
materialize.scraper.mzmon.clusterd #
clusterd (compute replica) metrics are reaching the gateway. When this drops, per-cluster compute signals — arrangements, peeks, dataflows — go dark even though the replicas may still be serving.up{
job="monitoring/mzmon-materialize-clusterd",
${mzEnvironmentNamespaceFilter}
} == 1
materialize.scraper.mzmon.orchestratord #
The Materialize operator (orchestratord) is being scraped. Losing it blinds you to cluster and replica lifecycle — creation, resize, and rollout progress — not to the running workloads themselves.up{
job="materialize/mzmon-materialize-operator",
${mzOperatorNamespaceFilter}
} == 1
materialize.health.clusters.status.percentage #
The share of the environment’s clusters currently reporting ready — the at-a-glance health headline for the whole environment.count(
${mzSqlPrefix}compute_cluster_status{
${mzEnvironmentFilter}
} == 1
) / count(
${mzSqlPrefix}compute_cluster_status{
${mzEnvironmentFilter}
}
) * 100
materialize.health.environment.availability.percentage #
An SLO-style snapshot: how much of the selected window the environment’s clusters were ready. Sustained dips are the signal that something restarted or went down while you weren’t watching.avg by (materialize_cloud_organization_namespace) (
avg_over_time(
${mzSqlPrefix}compute_cluster_status{
${mzEnvironmentFilter}
}${range}
) * 100
)
materialize.info.version #
The version of Materialize running in the environment. A single version is the steady state; multiple values appear briefly during a rolling upgrade.group by (mz_version) (
${mzSqlPrefix}compute_cluster_status{${mzEnvironmentFilter}}
)
materialize.info.max_lag #
The worst frontier lag seen anywhere in the environment over the selected window — how far the most-behind collection’s output trailed real time. A top-level freshness pointer.max(
max_over_time(
(
mz_dataflow_wallclock_lag_seconds{${mzEnvironmentFilter}, instance_id!="", quantile="1"} < 1e9
)[1h:1m]
)
)
materialize-kubernetes#
Kubernetes-side view of a Materialize deployment: capacity, workload readiness, per-pod resource usage, and networking. Adapted from the Overview dashboard’s “Kubernetes Workloads” tab and the k8s-sourced Summary panels.
These read kube-state-metrics and cAdvisor (via the kubelet), NOT Materialize
metrics — the same meta-monitoring surface other targets (e.g. Loki health)
will draw on. %%{cAdvisorFilter} is the container-scoping filter fragment
(namespace + drop empty/pause series); %%{mzNamespaceList} is the raw
namespace selector used by the kube_* and container_network_* metrics.
The percent-of-limit panels have an absolute-units sibling for deployments whose metrics source (e.g. GKE’s managed cAdvisor/KSM) doesn’t expose resource limits; the dashboard picks whichever fits the environment.
materialize.kubernetes.cpu.capacity #
Total CPU cores configured across the environment’s containers (sum of cAdvisor CPU limits), excluding the monitoring exporter — i.e. the CPU available to the actual workload.sum by (container) (
container_spec_cpu_quota{${cAdvisorFilter}, container!="new-promsql-exporter"}
/ container_spec_cpu_period{${cAdvisorFilter}, container!="new-promsql-exporter"}
)
materialize.kubernetes.memory.capacity #
Total memory configured across the environment’s containers (sum of cAdvisor memory limits), excluding the monitoring exporter. Memory is Materialize’s dominant constraint — in-memory arrangements live in here.sum by (container) (
container_spec_memory_limit_bytes{${cAdvisorFilter}, container!="new-promsql-exporter"}
)
materialize.kubernetes.cpu.usage.percent #
Current CPU usage per container type as a fraction of its limit, averaged over the last 5 minutes — shows the worst-loaded container types.sum by (namespace, container) (
rate(container_cpu_usage_seconds_total{${cAdvisorFilter}}[5m])
) / sum by (namespace, container) (
kube_pod_container_resource_limits{resource="cpu", namespace=~"${mzNamespaceList}"}
)
materialize.kubernetes.cpu.usage.absolute #
Current CPU usage per container type in cores (rate over 5 minutes), for deployments whose metrics source doesn’t expose CPU limits — read it against the replica sizes you configured.sum by (namespace, container) (
rate(container_cpu_usage_seconds_total{${cAdvisorFilter}}[5m])
)
materialize.kubernetes.memory.usage.percent #
Current memory usage per container type as a fraction of its limit — shows the worst-loaded container types.sum by (namespace, container) (
avg by (namespace, pod, container) (
container_memory_working_set_bytes{${cAdvisorFilter}, container!="new-promsql-exporter"}
)
) / sum by (namespace, container) (
avg by (namespace, pod, container) (
container_spec_memory_limit_bytes{${cAdvisorFilter}, container!="new-promsql-exporter"}
)
)
materialize.kubernetes.memory.usage.absolute #
Current memory (working set) per container type in bytes, for deployments whose metrics source doesn’t expose memory limits.sum by (namespace, container) (
container_memory_working_set_bytes{${cAdvisorFilter}, container!="new-promsql-exporter"}
)
materialize.kubernetes.last_restart #
Seconds since the most recent container restart in the environment.time()
- topk(1,
container_start_time_seconds{${cAdvisorFilter}, container!="new-promsql-exporter"}
)
materialize.kubernetes.pods.readiness #
Pods in the Materialize namespace grouped by phase (Running, Pending, Failed, …).max by (phase, namespace) (
sum by (phase, namespace, instance) (
kube_pod_status_phase{namespace=~"${mzNamespaceList}"}
)
)
materialize.kubernetes.statefulsets.ready #
StatefulSet replicas reporting Ready. environmentd and the cluster pods are StatefulSets.max by (namespace) (
sum by (namespace, instance) (
kube_statefulset_status_replicas_ready{namespace=~"${mzNamespaceList}"}
)
)
materialize.kubernetes.deployments.readiness #
Deployment replica health — Ready vs Unavailable. Deployments back stateless services (e.g. the promsql exporter).max by (namespace) (
sum by (namespace, instance) (
kube_deployment_status_replicas_ready{namespace=~"${mzNamespaceList}"}
)
)
max by (namespace) (
sum by (namespace, instance) (
kube_deployment_status_replicas_unavailable{namespace=~"${mzNamespaceList}"}
)
)
materialize.kubernetes.pods.cpu_usage #
CPU utilization per pod as a fraction of the pod’s limit. Split so the cluster/replica selectors filter the cluster pods while envd/balancer/ exporter stay visible.sum by (namespace, pod, container) (
rate(container_cpu_usage_seconds_total{${cAdvisorFilter}, pod=~".*-cluster-${mzClusterList}-replica-${mzReplicaList}-.*"}${interval})
) / sum by (namespace, pod, container) (
kube_pod_container_resource_limits{resource="cpu", namespace=~"${mzNamespaceList}", pod=~".*-cluster-${mzClusterList}-replica-${mzReplicaList}-.*"}
)
sum by (namespace, pod, container) (
rate(container_cpu_usage_seconds_total{${cAdvisorFilter}, pod!~".*-cluster-.*-replica-.*"}${interval})
) / sum by (namespace, pod, container) (
kube_pod_container_resource_limits{resource="cpu", namespace=~"${mzNamespaceList}", pod!~".*-cluster-.*-replica-.*"}
)
materialize.kubernetes.pods.memory_usage #
Memory usage per pod as a fraction of the pod’s limit (working-set basis), same cluster/non-cluster split as pod CPU.avg by (namespace, pod, container) (
container_memory_working_set_bytes{${cAdvisorFilter}, container!="new-promsql-exporter", pod=~".*-cluster-${mzClusterList}-replica-${mzReplicaList}-.*"}
) / avg by (namespace, pod, container) (
container_spec_memory_limit_bytes{${cAdvisorFilter}, container!="new-promsql-exporter", pod=~".*-cluster-${mzClusterList}-replica-${mzReplicaList}-.*"}
)
avg by (namespace, pod, container) (
container_memory_working_set_bytes{${cAdvisorFilter}, container!="new-promsql-exporter", pod!~".*-cluster-.*-replica-.*"}
) / avg by (namespace, pod, container) (
container_spec_memory_limit_bytes{${cAdvisorFilter}, container!="new-promsql-exporter", pod!~".*-cluster-.*-replica-.*"}
)
materialize.kubernetes.pods.network_rx #
Network bytes/sec received per pod. For cluster pods, Rx tracks ingest from upstream and inter-pod replication; for envd/balancer it’s client SQL traffic. Surges alongside hydration are normal catchup.sum by (namespace, pod) (
rate(container_network_receive_bytes_total{namespace=~"${mzNamespaceList}", pod=~".*-cluster-${mzClusterList}-replica-${mzReplicaList}-.*"}${interval})
)
sum by (namespace, pod) (
rate(container_network_receive_bytes_total{namespace=~"${mzNamespaceList}", pod!~".*-cluster-.*-replica-.*"}${interval})
)
materialize.kubernetes.pods.network_tx #
Network bytes/sec transmitted per pod. For cluster pods Tx covers sink output, inter-pod replication, and query results returning to envd; for envd it’s client query responses.sum by (namespace, pod) (
rate(container_network_transmit_bytes_total{namespace=~"${mzNamespaceList}", pod=~".*-cluster-${mzClusterList}-replica-${mzReplicaList}-.*"}${interval})
)
sum by (namespace, pod) (
rate(container_network_transmit_bytes_total{namespace=~"${mzNamespaceList}", pod!~".*-cluster-.*-replica-.*"}${interval})
)
materialize.kubernetes.pods.network_errors #
Network rx + tx errors per pod per second (counted at the NIC/kernel level).sum by (namespace, pod) (
rate(container_network_receive_errors_total{namespace=~"${mzNamespaceList}", pod=~".*-cluster-${mzClusterList}-replica-${mzReplicaList}-.*"}${interval})
)
sum by (namespace, pod) (
rate(container_network_receive_errors_total{namespace=~"${mzNamespaceList}", pod!~".*-cluster-.*-replica-.*"}${interval})
)
sum by (namespace, pod) (
rate(container_network_transmit_errors_total{namespace=~"${mzNamespaceList}", pod=~".*-cluster-${mzClusterList}-replica-${mzReplicaList}-.*"}${interval})
)
sum by (namespace, pod) (
rate(container_network_transmit_errors_total{namespace=~"${mzNamespaceList}", pod!~".*-cluster-.*-replica-.*"}${interval})
)
materialize.kubernetes.pods.network_drops #
Network packets dropped (rx + tx) per pod per second — when kernel buffers fill faster than the app reads (rx) or egress rate-limiting kicks in (tx).sum by (namespace, pod) (
rate(container_network_receive_packets_dropped_total{namespace=~"${mzNamespaceList}", pod=~".*-cluster-${mzClusterList}-replica-${mzReplicaList}-.*"}${interval})
)
sum by (namespace, pod) (
rate(container_network_receive_packets_dropped_total{namespace=~"${mzNamespaceList}", pod!~".*-cluster-.*-replica-.*"}${interval})
)
sum by (namespace, pod) (
rate(container_network_transmit_packets_dropped_total{namespace=~"${mzNamespaceList}", pod=~".*-cluster-${mzClusterList}-replica-${mzReplicaList}-.*"}${interval})
)
sum by (namespace, pod) (
rate(container_network_transmit_packets_dropped_total{namespace=~"${mzNamespaceList}", pod!~".*-cluster-.*-replica-.*"}${interval})
)
materialize-storage#
Sources and sinks for a Materialize deployment — catalog shape, throughput, lag, and upstream/downstream health. Adapted from the Overview dashboard’s “Sources and Sinks” tab.
The clusterd-side throughput/lag/error metrics (mz_source_* / mz_sink_) carry
the long-form cluster_environmentd_materialize_cloud_ id labels. These queries
assume one Prometheus job per clusterd endpoint; if the same endpoint is
scraped by several jobs, a plain sum-rate reads N× — dedupe the job at the
deployment (fix the scrape config, or wrap the inner rate in max without (job)) rather than baking it into the canonical query.
materialize.storage.sources.count #
Active sources in the catalog — each is a continuous ingestion connection from an external system (Kafka, Postgres, MySQL, S3, …), so this is roughly how many upstream feeds the environment maintains. Counts distinct source objects (the hidden per-source_progress
subsources are excluded), matching mz_sources.count(group by (id) (${mzSqlPrefix}storage_objects{${mzEnvironmentFilter}, type="source"}))
materialize.storage.sinks.count #
Active sinks in the catalog — each emits the results of a materialized view or query to an external system (Kafka, Iceberg, …). Counts distinct sink objects (excluding_progress subsources), matching mz_sinks.count(group by (id) (${mzSqlPrefix}storage_objects{${mzEnvironmentFilter}, type="sink"}))
materialize.storage.tables.count #
User-created tables in the catalog. Tables are write-once-read-many;INSERTs feed dataflows downstream. Mostly a catalog-shape signal — for
actual ingest activity look at source throughput.max(sum by (instance) (${mzSqlPrefix}tables_count{${mzEnvironmentFilter}}))
materialize.storage.sources.by_type #
Sources by connector type (kafka / postgres / mysql / …) — what flavors of upstream feed make up the ingest workload. Most environments concentrate on one or two.count by (object_type) (
group by (id, object_type) (
${mzSqlPrefix}storage_objects{${mzEnvironmentFilter}, type="source"}
)
) > 0
materialize.storage.sources.catalog #
A catalog of sources — one row per source (by name) with its connector type, envelope, and the cluster it ingests on. The metric-side “what sources do I have” reference.group by (id, object_type, connection_type, envelope_type, cluster_id) (
${mzSqlPrefix}storage_objects{${mzEnvironmentFilter}, type="source"}
)
materialize.storage.sources.bytes_received #
Inbound throughput per primary source — bytes/second pulled from upstream. Subsources (e.g. per-table Postgres replication) roll up to their primary, so each line is one logical source.sum by (parent_source_id) (
rate(mz_source_bytes_received{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval})
) > 0
materialize.storage.sources.ingestion_by_replica #
Messages ingested per second, split per source AND replica. Replicas read their upstream independently and should track together.sum by (parent_source_id, cluster_environmentd_materialize_cloud_replica_id) (
rate(mz_source_messages_received{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval})
)
materialize.storage.sources.upstream_errors #
Per-source upstream health, with two complementary signals — both nominal at 0, so an empty panel is healthy and any series means a source needs attention.sum by (source_id) (
rate(mz_source_offset_commit_failures{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval})
) > 0
(
max by (source_id) (mz_source_offset_committed{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"})
> bool max by (source_id) (mz_source_offset_known{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"})
) > 0
materialize.storage.sinks.by_type #
Sinks by (type, envelope) — e.g.kafka / upsert, kafka / debezium,
iceberg / upsert. The envelope is how Materialize encodes changes:
upsert writes the latest value per key, debezium writes change
events with old+new values.count by (object_type, envelope_type) (
group by (id, object_type, envelope_type) (
${mzSqlPrefix}storage_objects{${mzEnvironmentFilter}, type="sink"}
)
) > 0
materialize.storage.sinks.throughput #
Outbound throughput per sink — bytes/second successfully committed to the downstream system (Kafka broker, Iceberg catalog, …).sum by (sink_id) (
rate(mz_sink_bytes_committed{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval})
) > 0
materialize.storage.sinks.lag #
Bytes staged for a sink but not yet committed downstream — an in-flight queue depth in bytes. Oscillates around a small value in normal operation as commits happen periodically.clamp_min(
sum by (sink_id) (mz_sink_bytes_staged{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"})
- sum by (sink_id) (mz_sink_bytes_committed{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}),
0
)
materialize.storage.sinks.iceberg.commit_latency #
Iceberg commit-duration percentiles (p50/p90/p99) — how long eachCOMMIT against the Iceberg catalog takes (write a snapshot manifest,
ask the catalog to atomically swap it in).histogram_quantile(0.50, sum by (le) (rate(mz_sink_iceberg_commit_duration_seconds_bucket{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval})))
histogram_quantile(0.90, sum by (le) (rate(mz_sink_iceberg_commit_duration_seconds_bucket{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval})))
histogram_quantile(0.99, sum by (le) (rate(mz_sink_iceberg_commit_duration_seconds_bucket{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval})))
materialize.storage.sinks.iceberg.commit_failures #
Per-sink rate of failed and conflicting Iceberg commits. Conflicts (concurrent-writer races on the snapshot pointer) are recoverable — Materialize retries — but a high rate means something else is writing the same Iceberg table; failures are commit-side errors (network, auth, schema).sum by (sink_id) (rate(mz_sink_iceberg_commit_failures{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval}))
sum by (sink_id) (rate(mz_sink_iceberg_commit_conflicts{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval}))
materialize.storage.sinks.iceberg.file_rate #
Per-sink rate of files and snapshots written to Iceberg. Each commit produces one snapshot with data files (new rows) and delete files (tombstones for upserts). The data:delete ratio reflects your workload — pure-insert sinks produce ~0 deletes; upsert-heavy ones roughly 1:1.sum by (sink_id) (rate(mz_sink_iceberg_data_files_written{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval}))
sum by (sink_id) (rate(mz_sink_iceberg_delete_files_written{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval}))
sum by (sink_id) (rate(mz_sink_iceberg_snapshots_committed{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval}))
materialize.storage.sinks.kafka.tx_errors #
Per-sink rate of TX errors from the librdkafka client — each is one failed produce-request against the broker.sum by (sink_id) (rate(mz_sink_rdkafka_txerrs{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval}))
materialize.storage.sinks.kafka.output_buffer #
Messages sitting in the librdkafka output buffer, waiting to be sent to the broker. Normal buffer fluctuates briefly as messages flow through.sum by (sink_id) (mz_sink_rdkafka_outbuf_msg_cnt{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"})
materialize.storage.sinks.kafka.connect_rate #
Connect and disconnect events per sink against the Kafka broker. Healthy connections are persistent — a couple of connects at startup and zero disconnects afterward.sum by (sink_id) (rate(mz_sink_rdkafka_connects{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval}))
sum by (sink_id) (rate(mz_sink_rdkafka_disconnects{${mzEnvironmentFilter}, cluster_environmentd_materialize_cloud_cluster_id=~"${mzClusterList}", cluster_environmentd_materialize_cloud_replica_id=~"${mzReplicaList}"}${interval}))