Dashboard

What goes in an ETL dashboard in Metabase?

An ETL dashboard is the portfolio view of your pipelines: status across the fleet, data volume moved, freshness SLAs per destination table, run-duration trends, and cost per pipeline. It's built from the orchestrator's run metadata — Airflow's metadata DB, dbt artifacts, or vendor sync logs — landed in the warehouse (see build a data pipeline) and queried with plain SQL. For the on-call failure view, see the ETL monitoring dashboard.

For: data engineering leads and platform managers. Grain: one row per pipeline run. Source: apipeline_runs table synced from the orchestrator, plus atable_slas reference table for deadlines.

What does an ETL dashboard look like?

Here’s the layout this guide builds. Fleet-level counts and the SLA figure sit at the top so a lead can open it and answer “are we on time” in one glance; throughput and freshness detail follow, because that’s where commitments live; duration trends, cost ranking, and portfolio status sit at the bottom for the weekly review.

ETL dashboard in Metabase showing pipeline status, runs, data volume moved, freshness SLA attainment, duration trends, and cost per pipeline.
An example ETL dashboard in Metabase, built from orchestrator run metadata synced to the warehouse. Figures are illustrative.

Which cards belong on an ETL dashboard?

The eight below cover reliability, throughput, freshness, and cost — the portfolio questions, with incident detail deliberately left to the monitoring dashboard.

  • Runs per day, succeeded versus failed (stacked bar)
  • Data volume moved per day (area)
  • Freshness SLA attainment, weekly, against goal (line)
  • Destination-table freshness — deadline, last refresh, status (table)
  • Run duration trend for the five longest pipelines (line)
  • Cost per pipeline, month to date (row)
  • Pipelines by status — healthy, degraded, failing, paused (donut)
  • On-time completion by domain (row)

What data does the dashboard need?

  • A pipeline_runs table — pipeline, run id, status, started and completed timestamps, rows and bytes moved — synced from Airflow’s dag_run, Dagster/Prefect APIs, or dbt’s run_results.json.
  • A table_slas reference table — destination table, expected-by deadline, owning domain — so SLA cards are joins, not hardcoded rules.
  • Per-pipeline cost: warehouse query cost attributed by service account or query tag, plus orchestrator infrastructure allocated by runtime.
  • A pipeline dimension — pipeline, domain, owner, schedule window — for grouping and for duration-versus-window math.
  • Optionally, last-refresh timestamps read directly from destination tables as a cross-check on the orchestrator’s claims.

How do you build it?

  1. Sync orchestrator run history into reporting.pipeline_runs on a schedule — for Airflow, export from the metadata database rather than pointing Metabase at it directly.
  2. Create table_slas with an expected-by deadline in local business time for every table a consumer depends on — and resist adding tables nobody waits for.
  3. Attribute cost: join warehouse query history on the pipeline’s service account or query tag, add apportioned worker cost, and store a monthly figure per pipeline.
  4. Build the eight cards from those three tables; derive pipeline status (healthy, degraded, failing) from the last N runs in SQL so the donut and the KPI row can’t disagree.
  5. Add filters for domain, pipeline, destination table, and date range, then schedule a Monday-morning subscription for the weekly review.

Example card SQL

Destination-table freshness against SLA deadlines PostgreSQL
SELECT
s.destination_table,
s.pipeline,
s.sla_deadline_local,
MAX(r.completed_at)                                   AS last_refresh,
ROUND(EXTRACT(EPOCH FROM (now() - MAX(r.completed_at))) / 3600.0, 1)
                                                      AS hours_since_refresh,
CASE
  WHEN MAX(r.completed_at) >= s.expected_by THEN 'On time'
  WHEN now() < s.expected_by THEN 'Pending'
  ELSE 'Late'
END                                                   AS sla_status
FROM reporting.table_slas s
LEFT JOIN reporting.pipeline_runs r
ON r.destination_table = s.destination_table
AND r.status = 'success'
GROUP BY 1, 2, 3, s.expected_by
ORDER BY sla_status DESC, hours_since_refresh DESC;

Metrics

Integrations

Dashboards

FAQ

What is an ETL dashboard?
An ETL dashboard is the portfolio view of every pipeline you run: how many are healthy, how much data they move, whether destination tables meet their freshness SLAs, how run durations are trending, and what each pipeline costs. It answers the questions a data engineering lead gets asked — "is the data on time?" and "why is the bill growing?" — from the orchestrator's own run metadata, landed in the warehouse and queried with plain SQL in Metabase (see build a data pipeline for the landing patterns).
How is this different from an ETL monitoring dashboard?
Altitude. This dashboard is the portfolio and SLA view — trends over weeks, cost per pipeline, freshness commitments — read by a lead in a weekly review. The ETL monitoring dashboard is the operational failure-watching view — failed runs in the last 24 hours, error taxonomy, retry outcomes, the on-call queue — read by whoever is on call, today. Splitting them keeps both honest: incident detail buries a portfolio review, and weekly trends are useless mid-incident. Same underlying run data, two different refresh cadences and audiences.
Where does the data come from?
From the orchestrator's own bookkeeping. Airflow's metadata database has dag_run and task_instance; Dagster and Prefect expose run history via their APIs; dbt writes run_results.json per invocation; managed EL vendors expose sync logs through their APIs. The pattern is the same for all of them: sync run-level records into a pipeline_runs table in the warehouse — one row per run with pipeline, status, started, completed, rows and bytes moved — and build every card from that table rather than pointing Metabase at the orchestrator's production database.
How should I define freshness SLAs per table?
Work backwards from consumers, and don't SLA everything. The finance mart that feeds a 08:00 board dashboard gets an "expected by 07:00" deadline; an internal scratch table gets none. Store the deadlines in a small table_slas reference table (destination, deadline, owner) so the SLA card is a join, not hardcoded logic. Express deadlines in local business time — a UTC deadline quietly shifts an hour at daylight-saving changes. Then track attainment as a weekly percentage against an explicit goal, and treat a data freshness metric page as the definition of record.
How do I measure cost per pipeline?
Sum two components. Warehouse compute: attribute query cost to the pipeline's service account or query tag — every major engine exposes per-query cost or credits, so this is a join against query history. Infrastructure: the orchestrator's workers, containers, or vendor's per-row pricing, allocated by runtime share. Perfect allocation isn't the goal; ranking is. The cost card exists to show that two of sixty-eight pipelines drive 40% of spend, which is where optimization effort should go first. Recompute monthly and watch the trend rather than agonizing over the last dollar.
Run durations are creeping up — when should I care?
Chart duration as a share of the schedule window, not just minutes. A daily job going from 22 to 44 minutes is invisible in absolute terms but matters enormously if it runs hourly with a 60-minute window — the trend card exists to catch exactly that before it becomes missed runs. Distinguish the two causes: smooth growth tracking data volume is capacity planning, while a step change after a deploy is a code regression to bisect. A useful alert threshold is duration exceeding 80% of the window, or doubling against a 28-day baseline.
What's a good pipeline success rate?
Measure it two ways before judging. Final success rate — did the table land, retries included — should sit near 99% for mature pipelines; the example's 97.6% is worth a look but not an incident. First-attempt success rate tells a different story: if it's 85% and retries rescue the rest, you're paying for flakiness in compute and latency even though dashboards look green. Track both, and weight by importance — one failing revenue pipeline matters more than five failing scratch syncs. Failure-mode detail belongs on the monitoring dashboard, not here.