Airflow Sensors & Deferrable Operators
A sensor is a special kind of operator that waits for a condition to become true before letting downstream tasks proceed โ a file to appear, another DAGโs task to finish, a database row to exist, an HTTP endpoint to return a specific status. Sensors are how Airflow pipelines react to external events rather than only running on a fixed clock schedule.
Common Built-In Sensors
from airflow.sensors.filesystem import FileSensorfrom airflow.providers.amazon.aws.sensors.s3 import S3KeySensorfrom airflow.sensors.external_task import ExternalTaskSensor
wait_for_file = FileSensor( task_id="wait_for_upload", filepath="/data/incoming/orders.csv", poke_interval=60, timeout=60 * 60,)
wait_for_s3_object = S3KeySensor( task_id="wait_for_s3_export", bucket_key="s3://exports/orders/{{ ds }}/complete.flag", poke_interval=120,)
wait_for_upstream_dag = ExternalTaskSensor( task_id="wait_for_upstream", external_dag_id="orders_extract", external_task_id="load_complete",)Every sensor shares the same two key parameters: poke_interval (how often to check the condition) and timeout (how long to keep checking before failing the task entirely) โ without a timeout, a sensor waiting on a condition that never arrives will run forever, silently occupying resources.
The Problem with Classic โPoke Modeโ Sensors
By default, a sensor runs in poke mode: it occupies a worker slot for its entire waiting period, checking the condition on each poke_interval, sleeping in between. A sensor waiting up to an hour for a file, checking every 60 seconds, ties up a worker slot for that full hour even though itโs actively โdoing workโ for a fraction of a second per check. At scale โ dozens of sensors waiting on slow upstream systems โ this can exhaust worker capacity entirely, a well-known failure mode called sensor starvation.
Deferrable Operators: The Fix
Airflow 2.2+ introduced deferrable operators (and deferrable-capable sensors), which solve this by releasing the worker slot entirely while waiting, handing the โkeep checkingโ responsibility to the lightweight triggerer process instead โ an asyncio event loop that can efficiently manage thousands of concurrent waits without needing thousands of worker slots.
wait_for_file = FileSensor( task_id="wait_for_upload", filepath="/data/incoming/orders.csv", poke_interval=60, timeout=60 * 60, mode="reschedule", # releases the worker slot between pokes, instead of blocking it)mode="reschedule" is a middle ground available on all sensors โ it frees the worker slot between individual pokes (rather than the whole waiting period), at the cost of slightly more scheduling overhead per check. True deferrable operators (built on BaseSensorOperatorโs async support, or written explicitly as @task.sensor / deferrable custom operators) go further, using the triggererโs event loop instead of polling on a schedule at all for sensors that support event-based waiting.
Choosing a Mode
| Mode | Worker slot usage | Best for |
|---|---|---|
poke (default) | Held for the entire wait | Short waits (seconds to a few minutes) where the overhead of releasing/reacquiring a slot isnโt worth it |
reschedule | Released between individual pokes | Longer waits (many minutes to hours) where poke would tie up capacity needlessly |
| Deferrable (triggerer-based) | Not held at all while waiting | Very long or very numerous concurrent waits โ the most resource-efficient option where supported |
Common Mistakes
Leaving dozens of long-running sensors in default poke mode, exhausting the worker pool and causing unrelated DAGs to queue behind them โ a classic Airflow scaling incident.
Omitting timeout, leaving a sensor able to wait indefinitely for a condition that, due to an upstream failure, will simply never occur โ always set a timeout matched to a realistic worst-case wait.
Using a sensor at all when a simpler dependency (e.g., ExternalTaskSensor replaced by a proper cross-DAG trigger, or a direct upstream task dependency) would do โ sensors add polling overhead and latency; only reach for one when genuinely waiting on an external, non-Airflow-controlled condition.
Frequently Asked Questions
Do all sensors support deferrable/async mode? No โ it depends on the specific sensorโs implementation; check the providerโs documentation for a deferrable=True parameter or an async-capable version of the sensor you need.
Does reschedule mode delay how quickly the sensor reacts once the condition is true? Marginally โ thereโs a small scheduling overhead reacquiring a slot versus already holding one in poke mode, but for waits measured in minutes or longer this is negligible.
Is the triggerer required for reschedule mode? No โ reschedule works via the standard scheduler/executor mechanism; the triggerer is specifically needed for true async deferrable operators, not for reschedule mode.
Summary
Sensors let pipelines react to external conditions rather than only fixed schedules, but the default poke-mode implementation has a real resource cost worth understanding before deploying dozens of them. mode="reschedule" is a low-effort improvement for longer waits, and true deferrable operators โ powered by the triggerer covered in Airflow Architecture โ are the fully resource-efficient answer where the specific sensor supports them.