Troubleshooting — Apache Airflow 2
База знаний типичных ошибок курса Apache Airflow 2.
Категория
Симптомы
- scheduler.scheduler_loop_duration метрика > 5000ms
- Задачи долго переходят из scheduled в queued
- UI медленно обновляется
Причина
DAG parse блокирует scheduler loop (в 2.x) Слишком много DAG файлов на медленной FS Метаданные в БД росли — индексы фрагментированы
Решение
- В 3.0 вынести парсинг в standalone DAG Processor (mandatory)
- Использовать gitSync sidecar на local SSD
- Increase parsing_processes до 2x vCPU
- Запустить VACUUM ANALYZE на task_instance, dag_run, log, xcom
- Очистить старую историю: airflow db clean --keep-last 28
Симптомы
- TaskInstance в queued > 5 минут
- Worker idle, но задачи не подбираются
Причина
Executor heartbeat миссит (Celery worker крашится) Pool slots exhausted Priority weight tasks конкурируют worker_prefetch_multiplier > 1 + long task блокирует slot Broker (Redis/RabbitMQ) недоступен
Решение
- Проверить celery worker logs: docker compose logs worker | grep ERROR
- Проверить broker connectivity: docker exec airflow-scheduler airflow celery inspect ping
- SELECT * FROM slot_pool WHERE used_slots = occupied_slots → exhausted
- Установить worker_prefetch_multiplier = 1 для long-task workloads
- Adopt orphans: ждать adopt_or_reset_orphaned_tasks или restart scheduler
Симптомы
- В Postgres log: 'process X waiting for ShareLock on transaction'
Причина
HA scheduler конкурирует за critical section на slot_pool Это нормально до PR #19842 (Airflow 2.4+)
Решение
- Это ожидаемое поведение — игнорировать
- Если 2 scheduler-а часто спят (looks idle) — уменьшить scheduler_heartbeat_sec или max_dagruns_per_loop_to_schedule
Симптомы
- Deadlocks
- Task пропадают в queued
Причина
MariaDB < 10.6 не имеет SKIP LOCKED / NOWAIT
Решение
- Upgrade MariaDB до 10.6+
- Или мигрировать на PostgreSQL (golden path для Airflow)
Симптомы
- Task с короткой логикой (1s работы) занимает 20+ секунд от submission до completion
Причина
Pull image занимает время K8s scheduler placement Init container Python interpreter cold start
Решение
- Pre-pull image на nodes через DaemonSet
- Minimize image size — use slim base, multi-stage build
- ImagePullPolicy: IfNotPresent
- Skip init container если возможно
- Для short-task — switch на CeleryExecutor через Pluggable Executors AIP-61
Симптомы
- Pod killed reason: OOMKilled
- Task fails with retcode 137
Причина
Worker concurrency × memory per task > pod memory limit Memory leak в user code XCom через pickle с large objects
Решение
- Reduce worker_concurrency
- Set worker_max_tasks_per_child = 100 (recycle worker)
- Use custom XCom backend (S3) для больших данных
- Set lifecycle.preStop hook для graceful flush heartbeat
Симптомы
- TI в state=running, latest_heartbeat > scheduler_zombie_task_threshold назад
Причина
Worker OOMKilled / SIGKILL Node spot termination Network partition
Решение
- Ждать scheduler-зачистку (default 300s)
- Set retries >= 1 в задачах
- K8s: terminationGracePeriodSeconds: 120, pre-stop hook flush heartbeat
- Spot nodes: nodeAffinity на on-demand для критичных задач
Симптомы
- DAG parse медленный
- Vault/Secrets Backend под нагрузкой
Причина
Variable.get вызывается на уровне модуля (top-level), не внутри task
Решение
- Перенести Variable.get внутрь task callable
- [secrets] use_cache = True (в 3.x default)
- Использовать ENV variables для DAG-конфигов
Симптомы
- DAG появился — сразу 730 DagRun-ов started
Причина
catchup=True по умолчанию start_date = 2 года назад
Решение
- Всегда catchup=False для новых DAGs
- При migration: airflow dags pause <dag_id> → clear → unpause
- Set max_active_runs_per_dag для smooth catchup
Симптомы
- metadata DB > 100GB
- Slow queries
Причина
log table занимает 70% (если DB log handler enabled) task_instance row count 10M+ xcom — pickled large objects task_reschedule retention отсутствует
Решение
- Disable DB log handler — use S3/GCS/ES remote logging
- airflow db clean --keep-last 28 (cron daily)
- pg_partman для partition log/xcom/task_instance по дате
- VACUUM FULL periodic
- Custom XCom backend (S3) для блобов
Симптомы
- sqlalchemy errors
- Cursor lost between transactions
Причина
PgBouncer transaction mode + Airflow server-side cursors
Решение
- Session mode (pool_mode = session) в PgBouncer
- Или tune sql_alchemy_pool* carefully
- Или transaction mode + sql_alchemy_pool_pre_ping = True
Симптомы
- В полночь scheduler loop сотни ms
- Все DAGs queue одновременно
Причина
Все DAGs с @daily расписанием на одном моменте
Решение
- Random schedule offsets: schedule='30 1 * * *' — разнести по часам
- Custom Timetable с jitter
- max_active_runs_per_dag = N для горизонтального ограничения
Симптомы
- Worker slots все заняты sensor-ами
- Real tasks ждут
Причина
@task(mode='poke') sensor с 24h timeout
Решение
- Switch на mode='reschedule' — освобождает slot между poke
- Или deferrable=True — переходит в triggerer (asyncio)
- 1000 deferred sensors = 1 triggerer; 1000 poke sensors = 1000 worker slots
Симптомы
- start_date сдвигается на каждом parse
- Backfill результаты различаются
Причина
start_date = datetime.now() — недетерминированно
Решение
- Hardcode: start_date = datetime(2024, 1, 1)
- Никогда не вызывать datetime.now() в DAG body или decorators