refactor(airflow): пробник ClickHouse разбит на четыре задачи

Зачем: пробник был одной задачей — в интерфейсе Airflow один красный
квадрат, а место отказа приходилось искать по журналу. Ручная машинерия
проброса и сведения ошибок занимала больше места, чем сама проверка, и
читатель продирался через неё раньше, чем понимал, что пробник проверяет.
Пробники — единственный образец DAG в стенде, по ним будут писать
остальные.

Что: test_clickhouse разбит на prepare_tables, write_marker,
read_from_node_2 и cleanup_tables; маркер и имя принявшей запись ноды едут
между задачами через XCom строками. Снято сведение ошибок: except
BaseException, ExceptionGroup, add_note и накопление ошибок в список;
клиент каждая задача заводит общим помощником и закрывает в finally.
Ноды описаны константой NODES парами «имя для человека — источник для
запроса», булев переключатель и параллельные списки подписей ушли.
Уборка идёт обычным правилом запуска, а не all_done: состояние запуска
Airflow считает по концам графа, и уборка, отработавшая после отказа,
покрасила бы в зелёный запуск с упавшей проверкой — решение записано
в ADR 0003. Комментарии остались в четырёх местах: чтение ноды 2 через
remote(), импорт клиента внутри функции, правило запуска уборки и
автосоздание топика в test_kafka. Малые проверки: заглушка task принимает
обе формы декоратора, проверка сведения ошибок заменена проверками
уборки. Красный путь ищет образец по журналам всех задач последнего
запуска, а не в одном самом свежем.

Проверка: make config-test, make smoke (25 проверок) и make smoke-guards
зелены. Разбитый пробник укладывается в 5 секунд из 120, отведённых
run_airflow_probe, — предел не трогаем.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 18:36:52 +03:00
co-authored by Claude Opus 5
parent 86d2214d7c
commit dc12953cac
5 changed files with 225 additions and 99 deletions
+55
View File
@@ -0,0 +1,55 @@
# ADR 0003. Уборка в DAG и состояние запуска
Дата: 31 июля 2026 года. Статус: принято.
## Решение
Задача уборки в DAG стенда идёт обычным правилом запуска: только после успеха
всех предыдущих шагов. `trigger_rule="all_done"` и задачи-teardown в хвосте
DAG не используются. Упавший DAG оставляет кластер таким, каким сломался;
чистое состояние обеспечивает первая задача следующего запуска — она сносит
остатки через `DROP ... IF EXISTS` и убеждается, что их нет.
Правило шире уборки: в конце DAG не должно стоять задачи, которая зеленеет при
отказе предыдущих.
## Почему
Состояние запуска Airflow считает по концам графа: зелёные концы — зелёный
запуск, а упавшее выше для итога немо. Уборка с `all_done` написана так, чтобы
отработать при любом исходе, и, встав в конец, красит запуск в зелёный именно
тогда, когда проверка упала. Для стенда это худший исход: `make smoke`
спрашивает у Airflow одно поле — состояние запуска, — и на сломанном кластере
докладывает, что пробник прошёл.
Обычное правило запуска переворачивает счёт по концам в нашу пользу. После
отказа выше уборка уходит в `upstream_failed`, это состояние отказа, и запуск
краснеет по ней. Видны обе половины: и отказ рабочей задачи, и отказ самой
уборки, если кластер жив, а снести таблицы не вышло. Документированный приём
Airflow для тех, кому `all_done` в хвосте всё же нужен, — отдельная
задача-сторож с правилом `one_failed`; здесь она не понадобилась.
Цена решения — между падением и следующим запуском в `default` лежат служебные
таблицы пробника. Размен принят: тот же выбор уже сделан для постоянного топика
Kafka, который пробник не удаляет.
## Что проверено
31 июля 2026 года на живом стенде с Airflow 3.3.0. Три прогона `test_clickhouse`
через API: зелёный (четыре задачи `success`, таблиц после прогона нет), красный
с удалённой на ноде 2 локальной таблицей (`prepare_tables``failed`,
остальные — `upstream_failed`, состояние запуска `failed`, следы поломки на
кластере), затем снова зелёный (остатки снесены первой задачей, таблиц 0).
Оба отвергнутых варианта проверены так же и оба дали `success` при упавшей
проверке: уборка с `trigger_rule="all_done"` и уборка
`as_teardown(on_failure_fail_dagrun=True)`.
Семантика правил запуска и задач-teardown сверена через MCP Context7 по
документации Airflow: раздел про setup/teardown (умолчание
`on_failure_fail_dagrun=False`, правило `ALL_DONE_SETUP_SUCCESS`) и раздел
лучших практик про задачу-сторож. Оттуда же взята формулировка ловушки.
Правило прочитано в исходниках
установленного Airflow — `airflow/models/dagrun.py`, `is_effective_leaf`:
концом графа считается задача, ниже которой только задачи-teardown с
`on_failure_fail_dagrun=False`, и которая сама не такова.