Pipelines, Orchestration, and Idempotency
A DAG of scheduled jobs moves data raw to useful (medallion Bronze/Silver/Gold, ELT). The reliability ideas that matter: idempotent re-runs, backfills, and freshness SLAs, all queryable from a run log.
From queries to pipelines
A single query is not a data platform. A pipeline is a set of jobs wired by their dependencies, run on a schedule, that move data from raw to useful. In Apache Airflow a pipeline is a DAG, a Directed Acyclic Graph of tasks: "clean events" runs after "ingest events," "daily metrics" runs after "clean events," and a scheduler runs the whole graph every day. Acyclic means no loops, so there is always a valid order to run the tasks in.
A very common way to layer a pipeline is the medallion architecture:
- Bronzeraw, as received, append-only
- Silvercleaned, validated, conformed
- Goldaggregated, business-ready
- Bronze is the raw data exactly as it arrived, append-only, so you can always reprocess from source.
- Silver is cleaned, validated, deduplicated, and conformed.
- Gold is the business-level aggregates the dashboards read.
Each layer is a scheduled job that reads the one before it. You built exactly this medallion in Level 5's streaming capstone, one script that rebuilt Silver and Gold from Bronze with idempotent DELETE-then-INSERT loads; this lesson adds the orchestration layer that schedules those jobs as a DAG. Modern cloud pipelines favor ELT (load raw into the lake or warehouse, then transform there with SQL) over the older ETL (transform before loading), because storage is cheap and the warehouse is powerful.
Batch vs streaming. Everything so far is batch: a job runs on a schedule over a chunk of data. The other mode is streaming, where events are processed continuously as they arrive, usually off a message queue or log like Apache Kafka (an append-only, partitioned log whose consumers read by offset, and a common home for the Avro records from the last module). Micro-batch sits in between, running tiny batches every few seconds. Most analytics pipelines are batch or micro-batch; true streaming is for low-latency needs.
Two ideas that separate juniors from mid-levels
- Idempotency. A job will be retried after a failure, and a backfill will re-run last week. If re-running doubles the data, your pipeline is broken. An idempotent job produces the same result no matter how many times it runs, achieved by overwriting a partition, using MERGE (upsert), or delete-then-insert, never a blind append. This is the single most important reliability property of a batch job. You built exactly these idempotent rebuilds as SQL scripts in Level 5; here you reason about them operationally, and the duplicate-detection check in the next lesson is how you catch a re-run that was not idempotent.
- Backfill and freshness. When logic changes or late data arrives, you backfill: re-run the pipeline over historical partitions. Incremental loads process only new data since a high-water mark (the last-seen timestamp), usually with a small look-back window to catch late arrivals. Freshness is how recent the data is, and a freshness SLA ("gold is never more than 30 minutes behind") is what you page someone about. In this lesson freshness is proxied by whether a run finishes within its SLA minutes; real freshness also depends on when the run started, not just how long it took.
Operating a pipeline with SQL
You run a pipeline by watching its run log: which jobs succeeded, how many rows they wrote, how long they took, whether they met their SLA. Querying that operational metadata is a daily DE task. The capstone exercises query a pipeline_runs table to compute per-job reliability and to catch the runs that breached their freshness SLA.
Common mistake: treating a green run as proof of good data. A job can succeed and still write duplicates, nulls, or half the rows, so a success status tells you the job ran, not that the data is correct.
Interview nuance: "make this backfill safe to re-run" is a direct idempotency question: overwrite the target partition or MERGE on a key, so a second run replaces rather than duplicates. "How do you know the data is late" is a freshness/SLA question answered by comparing each run's duration or finish time to its SLA, which is exactly the capstone query.
On a real platform this differs. Real run metadata lives in Airflow's database, dbt's run results, or a warehouse audit table, with far more detail. The
pipeline_runstable here keeps job, date, status, rows, duration, and SLA, which is enough to compute the reliability and freshness questions an on-call DE actually asks.
CREATE TABLE pipeline_runs (
job TEXT,
run_date TEXT,
status TEXT, -- success | failed
rows_out INTEGER,
duration_min REAL,
sla_min INTEGER -- freshness SLA: the run should finish within this many minutes of its scheduled start
);
INSERT INTO pipeline_runs (job, run_date, status, rows_out, duration_min, sla_min) VALUES
('bronze_ingest', '2026-01-01', 'success', 5200000, 22.0, 30),
('bronze_ingest', '2026-01-02', 'success', 5400000, 24.0, 30),
('bronze_ingest', '2026-01-03', 'failed', 0, 5.0, 30),
('bronze_ingest', '2026-01-04', 'success', 5000000, 41.0, 30), -- SLA breach
('silver_clean', '2026-01-01', 'success', 5100000, 18.0, 25),
('silver_clean', '2026-01-02', 'success', 5300000, 19.0, 25),
('silver_clean', '2026-01-03', 'failed', 0, 3.0, 25),
('silver_clean', '2026-01-04', 'success', 4900000, 28.0, 25), -- SLA breach
('gold_metrics', '2026-01-01', 'success', 120, 6.0, 15),
('gold_metrics', '2026-01-02', 'success', 120, 6.5, 15),
('gold_metrics', '2026-01-03', 'success', 120, 7.0, 15),
('gold_metrics', '2026-01-04', 'success', 120, 20.0, 15); -- SLA breach-- Per-job overview: how many runs and total rows written to the target.
SELECT job,
COUNT(*) AS runs,
SUM(rows_out) AS rows_written
FROM pipeline_runs
GROUP BY job
ORDER BY job;Apply
Your turn
The task this lesson builds to.
Write a query that returns each job's reliability, as (job, runs, failures, success_pct), least reliable first, over pipeline_runs(job, run_date, status, rows_out, duration_min, sla_min).
runs is the total runs, failures counts status = 'failed', and success_pct is the percentage of runs that succeeded. Round success_pct to 2 decimals, ordered by success_pct ascending and then job.
3 hints and 1 automated check are waiting in the workspace.
Practice
Make it stick
A second problem on the same idea, plus 4 bonus drills.
Write a query that returns every successful run that missed its freshness SLA, as (job, run_date, duration_min, sla_min, over_by_min), worst overrun first, over the same pipeline_runs table.
A run missed its SLA when it succeeded but its duration_min exceeded its sla_min. over_by_min is how many minutes over it ran. Order by over_by_min descending. (Failed runs are excluded: a failure is a different alert.)
3 hints and 1 automated check are waiting in the workspace.