Airflow Backfill: Taming the 90-Day Catchup Storm Safely
Airflow backfill and catchup explained: catchup=True, the backfill CLI, safe reruns, and the checklist that keeps production backfills from double-loading..
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
- ✓Scheduling semantics: start_date, schedule, and data intervals (see airflow-scheduling).
- ✓Task states and reruns basics (see airflow-task-lifecycle).
- ✓A production DAG with a load task you can make idempotent.
- Catchup is Airflow backfilling every missed data interval between start_date and now — automatically.
- catchup=True with a 90-day-old start_date queues 90 DAG runs at once and can flatten your workers.
- The backfill CLI (
airflow dags backfill) targets a date range and limits concurrency per run. - Clearing task instances re-runs them; safe reruns depend on idempotent tasks.
- Production rule: catchup=False in prod, backfill deliberately with explicit ranges.
- Airflow 3 renamed the CLI to
airflow backfill create(--from-date/--to-date/--reprocess-behavior) and added a Trigger → Backfill form in the UI.
Imagine a newspaper that missed 90 days of editions. Catchup is the printer frantically printing all 90 at once the moment it's turned back on. Backfill is you calmly telling the printer: print only March, one edition at a time, and skip the ones we already have. Both print old editions — one floods the building, the other fills the archive safely.
Every Airflow engineer learns the catchup lesson the hard way: a DAG deployed with a start_date six months in the past and catchup=True, and the scheduler suddenly queues 180 runs that hammer the database, the API, and the warehouse simultaneously.
Catchup isn't evil — it's a mechanism. The problem is deploying it by accident and running it without limits.
Here's what catchup actually does, the backfill CLI that gives you control, safe rerun patterns, and the production checklist that keeps a backfill from becoming an incident.
1. What Catchup Actually Does
Airflow schedules one DAG run per data interval between start_date and now. Catchup is the setting that decides what happens to intervals you missed while the DAG was paused, undeployed, or failing.
With catchup=True, the scheduler queues runs for every missed interval as fast as it can. With catchup=False, it skips the missed intervals entirely and runs only the most recent one.
Modern Airflow defaults to catchup=False, but the setting can be flipped per DAG — and that's where incidents come from.
- A daily DAG gets one run per day between start_date and now
- Missed intervals are 'pending' until catchup or backfill handles them
- catchup=True queues them automatically, all at once
- catchup=False skips them silently — which is its own trap
2. The Backfill CLI
The backfill CLI gives you manual control over missed intervals: an explicit date range, a concurrency limit, and a reset flag for reruns.
The core command targets a range and caps concurrent runs. Add --reset-db-runs to re-run intervals that already have run records — the command that turns "redo the failed ones" into one line.
The range semantics matter: --start-date is inclusive, --end-date is exclusive. Mistake the boundary and you either re-run a day you didn't want or skip the day you needed.
Airflow 3 renamed the command and added flags: airflow backfill create --dag-id market_etl --from-date 2026-03-01 --to-date 2026-03-31 --reprocess-behavior failed --max-active-runs 4 --run-backwards — --reprocess-behavior picks which existing runs to redo, and --run-backwards processes the range in reverse order, most recent interval first. The same form exists in the UI: DAG Details → Trigger → Backfill.
3. Clearing Task Instances Safely
Clearing is the surgical rerun tool: airflow tasks clear resets task instances to their initial state so the scheduler re-queues them.
Scope it narrowly. Clear one task across a date range, or one interval, or one task instance in the grid view. The common production mistake is clearing with a wide range and letting everything re-run — including tasks that never failed.
Clearing does not delete data. It resets Airflow's bookkeeping. Whether the re-run double-loads the warehouse depends entirely on whether your tasks are idempotent.
The mechanics are precise: clearing sets the task instance's state back to None and resets max_tries to 0, which forces the scheduler to re-queue it — and the Grid view preserves the previous attempts as task instance history. Scope flags exist for every rerun shape: --past, --future, --upstream, --downstream, --recursive, and --failed, which re-runs only the instances that actually failed.
- Clearing resets state; the scheduler re-queues immediately
- --downstream re-runs everything after the cleared task
- Wide clears re-run healthy tasks and healthy data loads
- Clear narrowly: one task, one interval, one reason
4. Reruns Without Double Loads
Every rerun story ends at the same question: will running this task again produce the same data, or duplicate it?
The answer is idempotency. A load task that inserts with no key check duplicates on rerun. A load task that upserts by a natural key — or deletes the interval's partition first — reproduces the same rows.
The production pattern for date-partitioned loads: delete the partition for the data interval, then insert. Rerun the backfill as many times as you like; the data is identical.
5. Scheduled vs Manually Triggered Runs
The grid view distinguishes scheduled runs from manual runs, and the distinction matters for backfills.
Scheduled runs carry the data interval in their run metadata. Manual triggers (the Trigger DAG button, the API, the CLI) also get a data interval — the next one — which is why a manual run during an active interval can confuse people: it runs the same interval as the scheduler is about to run.
The backfill CLI creates runs with their own intervals, marked as backfill runs. When you look at the grid, backfill runs and scheduled runs are different entries with the same interval — which is exactly why duplicate data protection (idempotent loads) matters again.
6. The Production Backfill Checklist
A backfill is a controlled operation. Before running one on production, walk this checklist.
- Confirm the loads are idempotent — test the rerun on a staging interval.
- Set catchup=False on the DAG so the scheduler doesn't race you.
- Pick an explicit range, inclusive start, exclusive end.
- Cap concurrency: --max-active-runs 2-4 for warehouse loads.
- Monitor the grid view and the warehouse's load; throttle if queries queue.
- After completion, compare row counts against the source per interval.
- Document the backfill in the runbook: range, duration, row counts.
The 90-Day Catchup Storm
airflow dags backfill with an explicit date range for the 90 missing days, with --max-active-runs limited to 4. The storm stopped; the backfill completed over a weekend at controlled concurrency.- Never deploy a DAG with catchup=True and a historical start_date.
- Backfill is a deliberate operation with an explicit range, not a deploy side-effect.
- max_active_runs is the shock absorber for any backfill.
- Idempotent tasks are what make a rerun safe — catchup multiplies their importance.
airflow dags pause <dag_id>airflow config get-value core catchup_by_default| File | Command / Code | Purpose |
|---|---|---|
| backfill_commands.sh | airflow dags backfill market_etl \ | 2. The Backfill CLI |
| clear_commands.sh | airflow tasks clear market_etl --task-id load_market_data \ | 3. Clearing Task Instances Safely |
| dags | from airflow.decorators import task | 4. Reruns Without Double Loads |
| backfill_checklist.sh | airflow dags backfill market_etl \ | 6. The Production Backfill Checklist |
Key takeaways
Common mistakes to avoid
4 patternsDeploying with catchup=True and a historical start_date
Backfilling without a concurrency cap
Re-running a failed day with a manual trigger
Backfilling a non-idempotent pipeline
Interview Questions on This Topic
What is catchup in Airflow and why is it dangerous?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Drawn from code that ran under real load.
That's Airflow. Mark it forged?
3 min read · try the examples if you haven't