◐ ai-teacher

🔀 Pipelines & Orchestration · Chapter 3

Airflow: DAGs, tasks, sensors, retries

A DAG that works on a good day is easy. The real Airflow skill is the five knobs that decide what happens on a bad one: sensor mode, timeouts, retries, pools and trigger rules.

Study this lesson free in the app →

The basics

Apache Airflow runs your pipeline as a DAG of tasks; each task is an instance of an operator — a template for one kind of work (run SQL, run a Python function, launch a Spark job, wait for a file). This lesson is the hands-on toolkit; pl-001 covered why tasks must be idempotent and backfill-safe.

The problem it solves: real pipelines wait for partners, call flaky APIs and share limited resources. Without explicit rules, a late file blocks workers for hours, a 503 kills the night's run, and ten DAGs hammer the same rate-limited API at once.

The mental model: each task is a worker on a factory floor with a rulebook:

Words you must know 6

operatorsensorXCompooltrigger ruledeferrable

Explained

Meghalaya Cabs' settlement DAG waits for Razorpay's daily file, calls their API for details and loads the warehouse.

On a bad night the file is 4 hours late, the API returns 503 twice, and three other DAGs want the same API. Whether that night ends in a page or in a clean 07:00 load is decided by a handful of parameters.

The courier desk at an office

An office expects a courier.

The receptionist could stand at the gate for six hours (that's a sensor in poke mode — one person blocked). Smarter: check every 10 minutes and do other work between checks (reschedule). Smartest: ask the guard to ring when it arrives (deferrable).

When the parcel comes, the receptionist doesn't carry the 20 kg box to accounts — she sends a note: "it's in room 4" (XCom passes a reference, not the data).

The office has one franking machine; at most one person uses it at a time, whatever department they're from (a pool).

And if the courier hasn't come by 6 pm, the receptionist stops waiting and tells the manager (a sensor timeout).

Operators and sensors

Action operators do work: BashOperator, PythonOperator / @task, SQLExecuteQueryOperator, SparkSubmitOperator, KubernetesPodOperator. Most come from provider packages (apache-airflow-providers-amazon, …-google).

Sensors wait for a condition: S3KeySensor, SqlSensor, HttpSensor, ExternalTaskSensor, @task.sensor.

Sensor knobs:

Rule: heavy work belongs in Spark/the warehouse; operators should submit and watch.

Retries, timeouts, trigger rules

Retries (per task or in default_args): retries, retry_delay, retry_exponential_backoff, max_retry_delay. Callbacks: on_retry_callback, on_failure_callback.

Timeouts: execution_timeout caps one try of a task; for sensors, timeout caps the whole wait. Use dagrun_timeout on the DAG to cap a whole run.

Fail fast deliberately: raise AirflowFailException for errors a retry can't fix (bad credentials, schema mismatch) — it skips remaining retries. AirflowSkipException marks skipped.

Trigger rules decide when a task may start:

XCom and pools, precisely

XCom — small values stored in the metadata database (default backend), keyed by dag_id, task_id, run_id and key. A @task return value is pushed automatically; classic operators use ti.xcom_pull(task_ids='extract') or the template {{ ti.xcom_pull(task_ids='extract') }}.

Concurrency guardrails:

Pools protect external resources (an API, a database); DAG-level limits protect Airflow from one DAG.

At scale

Sensors are the silent capacity killer. 300 poke-mode sensors waiting overnight = 300 worker slots doing nothing. Reschedule mode helps; deferrable operators fix it — the triggerer runs thousands of async waits in one process.

Dynamic task mapping (extract.expand(city=[...])) creates one task instance per item at runtime — 40 cities, 40 parallel tasks, each retried independently. Combine with a pool so 40 tasks don't open 40 connections to the replica.

Metadata DB health. Large XComs and millions of task instances bloat it; scheduler loops slow down. Keep XComs tiny and prune old metadata (airflow db clean).

Retries must be classified. At 2,000 tasks a night, a retry on a schema error wastes 35 minutes and delays the alert. Retry transient errors (timeouts, 5xx, throttling); fail fast on permanent ones.

Gotchas

Wait without holding a slot. Retry only what can heal. Pass notes, not parcels. Pool shared resources.

reschedule/deferrable · execution_timeout + retries · XCom = paths · pool = cross-DAG limit.

Words to know

OperatorA reusable template for one kind of task, such as running SQL or a Python function.
SensorAn operator that waits until a condition is true, such as a file existing.
Deferrable operatorAn operator that suspends itself and hands its wait to the async triggerer, freeing the worker slot.
XComSmall values passed between tasks, stored in the metadata database by default.
PoolA named set of slots limiting how many tasks across all DAGs can use a resource at once.
Trigger ruleThe condition on upstream task states that lets a task start.
execution_timeoutMaximum duration of a single task try before Airflow kills it.

In two minutes

Test yourself

1. What does mode='reschedule' change for a sensor?

  1. It retries the sensor on failure
  2. It makes the sensor check faster
  3. It frees its worker slot between checks instead of holding it for the whole wait
  4. It reschedules the whole DAG run
Show the answer

It frees its worker slot between checks instead of holding it for the whole wait — Poke mode sleeps inside a worker slot. Reschedule mode ends the try between checks and goes up_for_reschedule, so the slot is available to other tasks.

2. If you don't set a sensor's timeout, how long will it wait by default?

  1. 7 days
  2. 1 hour
  3. Forever
  4. Until the next DAG run
Show the answer

7 days — The default timeout is 606024*7 seconds. A missing partner file can sit unnoticed for a week unless you set a realistic timeout.

3. Where does the default XCom backend store values?

  1. In the worker's local disk
  2. In Airflow's metadata database
  3. In Redis only
  4. In the DAG file
Show the answer

In Airflow's metadata database — That's why XComs must be small: big values bloat the metadata DB that the scheduler depends on.

4. Three DAGs call Razorpay's API, which allows 4 concurrent requests. What enforces the limit across all three?

  1. max_active_runs=4 on each DAG
  2. retries=4
  3. A sensor with poke_interval=4
  4. A pool with 4 slots that all three tasks use
Show the answer

A pool with 4 slots that all three tasks use — Pools are global across DAGs. DAG-level limits only constrain one DAG, so three DAGs at 4 each could make 12 concurrent calls.

5. A task fails because the warehouse password was rotated. It has retries=3 with exponential backoff. Best practice?

  1. Raise AirflowFailException for auth errors so it fails immediately and alerts
  2. Increase retries to 10
  3. Set retry_delay to 1 second
  4. Mark the task as skipped
Show the answer

Raise AirflowFailException for auth errors so it fails immediately and alerts — Retries help transient errors. A permanent error just delays the alert by the full backoff schedule.

6. After a @task.branch chooses one of two paths, a final notify task joined to both branches is always skipped. Fix?

  1. Remove the branch
  2. Set notify's trigger_rule to none_failed_min_one_success
  3. Set retries on notify
  4. Use mode='reschedule' on notify
Show the answer

Set notify's trigger_rule to none_failed_min_one_success — With the default all_success, the skipped branch propagates a skip to the join. none_failed_min_one_success runs if nothing failed and at least one upstream succeeded.

There are 10 questions, 3 puzzles and 5 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.

Learn it so it stays. Read it, test yourself, then recall it on two different days. The app schedules that for you, free.

Start this lesson →