Apache Airflow: How to prevent airflow from backfilling dag runs?
Ты поставил новый даг на почасовое выполнение, а он вдруг запускается двадцать раз подряд, забивая очередь и съедая ресурсы. Знакомо? Это backfilling, и он може
Ты поставил новый даг на почасовое выполнение, а он вдруг запускается двадцать раз подряд, забивая очередь и съедая ресурсы. Знакомо? Это backfilling, и он может быть совершенно бесполезным, а то и вредным.
Например, твой даг забирает данные из API, который обновляется раз в час. Каждая последующая попытка за прошлый час будет тянуть одно и то же. А Airflow по умолчанию честно попытается нагнать все пропущенные интервалы с момента start_date.
Почему это происходит
Всё дело в дефолтном поведении Airflow и в параметре catchup. Если даг создан с start_date в прошлом и расписанием (schedule), scheduler увидит пропущенные интервалы и создаст на каждый из них дагран.
Вот типичный сценарий:
from datetime import datetime
from airflow import DAG
default_args = {
'start_date': datetime(2024, 1, 1, 0, 0), # Давно в прошлом
}
dag = DAG(
'my_hourly_dag',
default_args=default_args,
schedule_interval='@hourly',
# catchup не указан - используется глобальная настройка
)
Если такой даг развернуть 1 февраля 2024 года, scheduler попытается выполнить его за все 744 пропущенных часа января. И сделает это быстро, один за другим.
Как остановить безумие
Решение прямое — явно отключить догоняние для дага. Для этого используется параметр catchup=False.
dag = DAG(
'my_smart_dag',
default_args=default_args,
schedule_interval='@hourly',
catchup=False, # Ключевая строка
)
С этой настройкой scheduler создаст только один дагран — для следующего интервала после момента развёртывания. Всё прошлое останется в покое.
Настройка для всех дагов
Менять каждый даг вручную неудобно. Можно задать поведение по умолчанию для всего Airflow. Начиная с версии 1.8, в конфигурации есть параметр catchup_by_default.
# airflow.cfg
[scheduler]
catchup_by_default = False
Или через переменную окружения: AIRFLOW__SCHEDULER__CATCHUP_BY_DEFAULT=False. После этого все новые даги по умолчанию не будут догонять прошлое. Для старых дагов, где catchup явно не задан, применится новое значение.
А что с динамическим start_date?
В FAQ советуют не использовать datetime.now() как start_date. Это мудрый совет. Если задать start_date=datetime.now() и catchup=False, то первый запуск может произойти только через целый интервал (час, день), что неочевидно.
Лучше всего — фиксированная дата в прошлом плюс явное catchup=False. Или использовать последнее выполнение как точку отсчёта для логики внутри тасок, если данные инкрементальные.
Итог
Backfilling — это фича, а не баг, но она нужна не всегда. Чтобы её отключить, выставляй catchup=False в аргументах DAG. Для глобального изменения переключи catchup_by_default в конфиге. И не используй динамический start_date, чтобы не запутать ни scheduler, ни себя.