← All cheatsheets
Data Engineer · #062 · October 1, 2026 · 2 min read

What is a backfill? Rerunning the past without doubling your rows

What a backfill is, how to scope the blast radius, why it must be idempotent and partitioned, the Airflow and dbt commands, and the three ways a backfill silently lies: one page.

Get the free PDF

One page, print-ready, free to share. No signup needed.

Download the PDF

You fixed the bug. The past is still wrong. The deploy only changes what the pipeline builds from today, and every day between the first bad run and now still holds the old numbers. One page on scoping a backfill, running it safely, and the three ways it doubles your rows. The print-ready A4 PDF is at the bottom.

What it is

  • A backfill recomputes past periods of a table.
  • The trigger is a bug fix, a new column, or late data.
  • It is not a rerun: a rerun is one period, a backfill is every affected one.

Before you start

  • Measure the blast radius: from the first bad day to today.
  • Freeze the code: one version for all days, or you backfill twice.
  • Tell downstream: dashboards will move, and people should know why.

Run it right

  • Per partition: one day at a time, delete then insert.
  • Oldest first, so late data lands in order.
  • Idempotent: run it twice, get the same rows.

Airflow / dbt

  • airflow backfill -s start -e end schedules one run per interval.
  • dbt --full-refresh rebuilds incremental models from zero.
  • Cap max_active_runs, or the warehouse melts.

The cost

  • 6 months x 2 TB: price it before you launch it.
  • Use an off-peak window: nights, weekends.
  • Sample one day, verify it, then run the rest.

One partition, safely, forever

-- one day of the blast radius, oldest first
DELETE FROM fct_orders
 WHERE order_date = '2026-03-14';

INSERT INTO fct_orders
SELECT ... FROM stg_orders
 WHERE order_date = '2026-03-14';

-- run it twice: same rows, never double rows

Pass the day in as a parameter and loop from the first bad day to today. Never compute the date with CURRENT_DATE inside the query: March rows would be built as if it were today.

Gotchas

  • Append-only sink: every rerun doubles the rows.
  • today() in SQL: past days get today's date.
  • Downstream tables need the rerun too.

The trap: the three ways a backfill lies

The backfillWhat goes wrongThe fix
INSERT onlyevery rerun doubles rowsdelete + insert per day
CURRENT_DATE in SQLMarch built as todaypass the run date in
layer 1 onlymarts still show the bugrerun the whole lineage

The quiz

Which one is safe to run twice?

A) INSERT INTO fct SELECT ...
   WHERE day = :d

B) DELETE FROM fct WHERE day = :d;
   INSERT INTO fct SELECT ...
   WHERE day = :d

B. A appends a second copy of the day on every rerun. B rebuilds the partition, so running it twice leaves the same rows.

Interview phrasing worth memorizing: a backfill is a rerun of every affected partition with frozen code, and it is only safe if the job is idempotent.

Frequently asked questions

What is a backfill in a data pipeline?
A backfill recomputes past periods of a table after something changed: a bug fix, a new column, or late-arriving data. It differs from a rerun, which rebuilds one period. A backfill covers the whole blast radius, from the first bad day to today, with a single frozen version of the code.
Why must a backfill job be idempotent?
Because you will run it more than once, on purpose or by accident, and an append-only job doubles the rows every time. The safe unit is one partition: delete the day, insert the day, move to the next. Run it twice and you get the same rows, never double rows.
How do you backfill with Airflow or dbt?
Airflow has a backfill command that takes a start and end date and schedules one run per interval. dbt uses --full-refresh to rebuild incremental models from zero. In both cases cap concurrency, for example with max_active_runs in Airflow, or the warehouse melts under dozens of parallel days.
What are the most common backfill mistakes?
Three. Writing to an append-only sink, so every rerun duplicates the day. Using CURRENT_DATE or today() inside the SQL, so March rows are built as if it were today instead of the run date passed in. And rerunning only the first layer, so downstream marts and dashboards keep showing the bug for another week.

Get the free PDF

One page, print-ready, free to share. No signup needed.

Download the PDF

More cheatsheets