Skip to main content

Airflow's core model

โฑ 30 min

Hookโ€‹

You have read that Airflow "runs your DAGs," and you have seen a screenshot of a green grid. But when a task is stuck yellow at 03:00 and the on-call rotation is you, "it runs your DAGs" is not enough. You need to know which component decided to start that task, where it recorded the attempt, what it is waiting on, and how the next task will ever learn this one produced a filename. Airflow is not magic; it is five or six named parts with clear jobs. Learn the parts and the green grid stops being a mystery.

Conceptโ€‹

Apache Airflow is an open-source orchestrator that lets you define pipelines as Python code and then schedules, runs, and monitors them. Everything in this lesson is a name for one of its parts. You are not learning to configure Airflow yet โ€” you are learning its vocabulary so the machinery is legible.

Start with what you write. A DAG in Airflow is a Python file that declares tasks and their dependencies โ€” the same directed acyclic graph from the previous lesson, now expressed in code. A task is one node in that graph: a single unit of work. You rarely write a task's guts from scratch; instead you configure an operator, a reusable template for a kind of work. A PythonOperator runs a Python function; a BashOperator runs a shell command; a SparkSubmitOperator submits a Spark job. An operator is the class; a task is one configured instance of it sitting in your graph.

That is the authoring side. The running side is a set of long-lived processes.

The scheduler is the heart. It continuously reads your DAG files, decides which task instances are due โ€” every upstream succeeded, the interval has arrived โ€” and hands them off to run. It is the real-world version of the "run a task only when its dependencies are in the succeeded set" loop you saw last lesson. The executor is the scheduler's arm: it decides where a ready task actually runs โ€” in a local subprocess, on a pool of workers, or in a container. The metadata database is Airflow's memory: every DAG run, every task attempt, every state transition (queued, running, success, failed) is recorded there. When the UI shows a green square, it is reading the metadata DB. When the scheduler asks "did extract succeed for July 16?", it asks the metadata DB. Nothing about a run lives only in a worker's head.

Two more parts handle the cases a plain graph cannot express.

A sensor is a special task that waits for a condition before it succeeds: a file to land in a bucket, a partition to appear, an external job to finish. Instead of guessing that the upstream file "should be there by 2 a.m.," a sensor task polls until it genuinely is, then lets its downstream tasks proceed. It turns "wait for something outside this DAG" into a first-class node in the graph.

An XCom (short for cross-communication) is how one task passes a small piece of data to another โ€” a filename, a row count, a computed date. Tasks run in separate processes and cannot share variables, so when the extract task needs to tell the load task which file it wrote, it pushes that string as an XCom and the load task pulls it. XComs are for small control values, not for moving your dataset; the data itself belongs in storage, and the XCom carries the pointer.

PartIts one job
DAGDeclares the tasks and their dependencies, in Python
OperatorReusable template for a kind of work; a task configures one
SchedulerDecides which task instances are due and dispatches them
ExecutorDecides where a ready task runs (local, workers, containers)
Metadata DBRecords every run and state; the single source of truth
SensorA task that waits for an external condition before succeeding
XComPasses a small control value from one task to another
๐Ÿง  Knowledge check
1. What is the difference between an operator and a task in Airflow?
2. Where does Airflow record that a task succeeded, so the UI and scheduler agree on state?

Worked exampleโ€‹

Let me read a small Airflow DAG the way the scheduler does, naming each part as it appears. This is a daily sales pipeline: wait for the raw file, transform it, load it. Read it as illustration โ€” you are not running Airflow yet.

from datetime import datetime

from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.sensors.filesystem import FileSensor


def transform(**context):
# Pull the path the upstream sensor confirmed, via XCom.
raw_path = context["ti"].xcom_pull(task_ids="wait_for_raw", key="path")
print(f"transforming {raw_path}")
return "warehouse://sales/2026-07-16" # returned value becomes an XCom


def load(**context):
target = context["ti"].xcom_pull(task_ids="transform")
print(f"loading into {target}")


with DAG(
dag_id="daily_sales",
schedule="@daily",
start_date=datetime(2026, 7, 1),
catchup=False,
) as dag:
wait_for_raw = FileSensor(
task_id="wait_for_raw",
filepath="/data/raw/sales_{{ ds }}.csv",
)
do_transform = PythonOperator(task_id="transform", python_callable=transform)
do_load = PythonOperator(task_id="load", python_callable=load)

wait_for_raw >> do_transform >> do_load

Walk it as the scheduler would. The DAG(...) block declares the graph and its schedule โ€” @daily, so one run per day. The last line, wait_for_raw >> do_transform >> do_load, sets the dependencies: >> means "then," so it declares the same arrows you drew by hand last lesson. wait_for_raw is a sensor: the scheduler will not consider transform due until that sensor succeeds, which it does only once the file for that day's date ({{ ds }}) actually exists. When transform runs, it pulls an XCom to learn the path the sensor found, does its work, and returns a value โ€” that return becomes an XCom automatically, which load then pulls to learn where to write. The scheduler dispatched each task the moment its upstream reached success in the metadata DB; the executor chose the process each one ran in. Every part from the table is on the page.

Notice what you did not have to write: no clock arithmetic, no "sleep until the file shows up," no manual passing of the filename through a global. The model supplies all of it.

Hands-onโ€‹

Your turn. The exercise below gives you a short DAG definition with the parts scrambled and mislabeled โ€” a sensor called an operator, an XCom push with no matching pull, a dependency arrow pointing the wrong way โ€” and asks you to identify each part correctly and fix the one broken data handoff. It runs in Python Arena, so there is nothing to install.

โ–ถ Python Arenade204-label-the-dag-parts
Open in Python Arena โ†—

Success criteria: every part is labeled with the correct role, and the corrected XCom push/pull pair uses matching task ids and keys. Python Arena checks your answers against the reference automatically.

Recapโ€‹

  • You can name Airflow's core parts and state the one job of each: DAG, operator, scheduler, executor, metadata DB, sensor, and XCom.
  • You can distinguish an operator (the template) from a task (one configured instance in the graph).
  • You can explain the scheduler-plus-metadata-DB loop as the real-world version of "run a task once its dependencies have succeeded."
  • You can describe a sensor as a waiting task and an XCom as a small control value passed between tasks, with the dataset itself living in storage.

Next up: what changes when this pipeline runs unattended in production โ€” SLAs, failure handling, alerting, and reruns that do not corrupt data.

๐Ÿง  Knowledge check
1. A task needs to tell a downstream task which file it just wrote. What is the right Airflow mechanism?
2. Why use a sensor instead of scheduling the transform for a fixed time you assume the file has arrived?
๐Ÿ“Œ Key takeaways
  • You author DAGs, tasks, and operators; Airflow runs them through the scheduler, executor, and workers. - The metadata database is the single source of truth for every run and state. - A sensor is a task that waits for an external condition before it succeeds. - An XCom passes a small control value between tasks; the dataset itself stays in storage.