Отдать жизненный цикл мира дагам Airflow #119

Closed
opened 2026-08-21 17:47:59 +03:00 by ddmitry · 0 comments
Owner

Источник истины — спека
«Жизненный цикл мира: создание и пересоздание ведёт Airflow»
и ADR 0013.
Задача одна и делится только на коммиты: несколько связных коммитов внутри неё
— нормально, дробить её на дочерние тикеты не надо.

Цель

Прикладная схема, данные стартового мира и позиция модельного времени
переезжают к Airflow. Compose остаётся с долгоживущими службами и
инфраструктурой, make — с подъёмом стенда и вызовом нужного дага. Менти видит
один наблюдаемый граф вместо процесса, размазанного между Compose, shell и
Airflow.

Сначала прочитать

  • docs/specs/2026-08-21-world-lifecycle.md — контракты дагов, состояния
    позиции, поведение при отказах, границы;
  • docs/adr/0013-world-lifecycle.md — решение и доводы;
  • docs/adr/0007-clickhouse-access.md — модель доступа, куда добавляется новая
    учётная запись;
  • docs/adr/0008-order-ingestion.md и docs/adr/0009-world-control.md —
    устройство приёма заказов и пульта мира;
  • docs/architecture/storage.md, раздел «Раскладка SQL» — как сегодня
    применяется DDL и почему матвью приёма создаётся последней;
  • docs/architecture/testing.md — карта целей и правило о make clean;
  • compose.yaml, Makefile, dags/world_control.py, dags/orders_ingest.py,
    infra/airflow/init.sh, infra/clickhouse/users.d/access.xml,
    scripts/wait-for-world.sh, scripts/ingest-starting-orders.sh.

Критерии приёмки

Compose и make

  • Служб kafka-init, clickhouse-init, world-init и world-snapshots в
    compose.yaml нет — ни под профилем, ни как цели compose run.
  • Зависимости оставшихся служб перестроены: успешный подъём не ждёт
    исчезнувших одноразовых служб, docker compose up --wait возвращает
    ноль.
  • make up поднимает инфраструктуру, зовёт world_initialize через REST API
    Airflow и ждёт результата.
  • make rebuild-storage зовёт world_recreate и ждёт результата.
  • Один сценарий-помощник (например, scripts/run-dag.sh) запускает
    названный даг и ждёт конца; печатает идентификатор дага, идентификатор
    прогона, итоговое состояние и — при отказе — что делать дальше. Потолок
    ожидания назван пределом, а не бюджетом, и взят с кратным запасом от
    замера.
  • scripts/wait-for-world.sh и scripts/ingest-starting-orders.sh удалены:
    их работа переехала в даг и в помощника.
  • make generate-batch и make generate-live работают; унаследованные
    зависимости от исчезнувших служб сняты.
  • make clean не тронут.

Доступ

  • В access.xml появилась учётная запись lifecycle с ролью
    lifecycle_owner: администрирование прикладных баз stg, ods, dds,
    dm, dic и кластерный DDL; чтение system.settings; административных
    прав на системные базы нет.
  • Права etl не расширены.
  • Пароль живёт в .env, образец добавлен в .env.example, подключение
    заводит infra/airflow/init.sh.

world_initialize

  • Публичный даг без расписания, не на паузе, max_active_runs = 1.
  • Разбирает позицию по таблице состояний спеки: нет переменной или 0 —
    создаёт мир; -1 — краснеет и зовёт повторить make rebuild-storage;
    значения от 1 до WORLD_STARTING_DAYS - 1 — краснеют как недопустимые;
    WORLD_STARTING_DAYS и больше — пишет в журнал, что мир уже есть, и
    зеленеет, не трогая DDL, данные и позицию.
  • Топики создаются до DDL: hits — две партиции, orders — одна;
    повторный запуск подтверждает эти числа или падает с понятной ошибкой.
  • Канонический DDL применяется из sql/ddl/ по порядку имён файлов, с ноды
    1, учётной записью lifecycle.
  • Канонический образ генератора собирается дагом до проигрыша дней.
  • Ожидание приезда событий повторяет прежнее условие точно: сумма строк
    ods.event_dist за даты описи и ods.event_errors_dist не меньше суммы
    событий из data/world-inventory.json. Опись смонтирована в Airflow только
    для чтения.
  • Слепки отправляются вторым прогоном образа, orders_ingest дёргается
    ждущим триггером.
  • Позиция становится WORLD_STARTING_DAYS последней успешной задачей.

world_recreate

  • Публичный даг: позиция -1 до первого удаления; прикладные базы
    удаляются первыми; затем удаляются позиции групп clickstream_hits,
    clickstream_orders, после них — топики hits, orders; позиция 0
    только после успешной очистки; дальше ждущий триггер world_initialize.
  • Общей вынесенной реализации с world_initialize нет — переиспользование
    идёт делегированием.

Работники мира

  • world_next_day и world_live_day краснеют, когда позиции нет либо она
    меньше WORLD_STARTING_DAYS; отказ называет прочитанное значение и
    следующий шаг.
  • orders_ingest сторожа не получил.

Документация тем же PR

  • Обновлены: AGENTS.md, README.md, обе спеки
    (2026-07-30-stand-v2-realism.md, 2026-08-01-generator.md),
    docs/adr/0009-world-control.md, docs/architecture/storage.md,
    docs/architecture/testing.md,
    docs/architecture/orders/start-world.md, комментарии compose.yaml,
    комментарий про world-init в generator/src/clickstream_generator/inventory.py
    и комментарий про kafka-init в sql/ddl/10-stg-tables.sql.
  • ADR 0013 называет отменённые утверждения: отсутствие позиции больше не
    значит «стартовый мир уже есть» (спека генератора, раздел 8; ADR 0009;
    README); Compose больше не владеет созданием мира (storage.md, «Раскладка
    SQL»); DDL применяет не etl, а lifecycle (ADR 0007).
  • Замеры холодного и повторного make up и пересборки записаны в
    docs/architecture/testing.md, «Что проверено», и названы в теле PR.

Границы

  • Мигратор схемы не заводим: на готовом мире изменённый DDL не применяется.
  • Блокировок, статуса обслуживания и автоматической паузы world_live в этой
    задаче нет — предусловие обслуживания живёт словами в спеке и README.
  • Содержание DDL, код генератора, устройство слоёв и приём заказов не трогаем.
  • Новых проверок в make не добавляем: вопрос «данные правильные» — к дагам DQ
    (docs/architecture/testing.md).
  • make clean в реализацию и обычную проверку не входит.

Подводные камни

  • Многозапросные файлы DDL. Нынешний clickhouse-client --multiquery
    исполняет файл целиком. Новый путь обязан сохранить это свойство; API клиента
    и допустимый способ разбора сначала сверить через MCP Context7, решение
    коротко записать в коде или документации.
  • Контекст сборки образа. Airflow нужны только для чтения generator/,
    data/ и корневой .dockerignore. Локальные .venv и кеши в контекст ехать
    не должны. Задачу сборки замерить отдельно и назвать её цену в теле PR.
  • Удаление группы потребителей. Пока в группе есть живой участник, удаление
    недопустимо. Отсюда порядок «сначала базы, затем группы и топики»; на живом
    стенде проверить, что после удаления баз группы освободились.
  • WORLD_STARTING_DAYS остаётся в двух местах — в compose.yaml и в
    inventory.py генератора. После переезда у числа в YAML один потребитель;
    комментарий в генераторе должен указывать на него, а не на исчезнувшую службу.
  • Спорные API — Airflow 3.3 (REST, TriggerDagRunOperator, DockerOperator
    и сборка образа) и DDL ClickHouse — сверять через MCP Context7 и коротко
    фиксировать, что проверили.

Проверка

Команды:

make config-test
make lint
make smoke
make check-clickhouse
make check-services

Сценарии на живом стенде:

  1. Повторный make up на готовом мире — быстро, зелёно, позиция не
    изменилась, счётчики check-clickhouse те же, в журнале дага сказано, что мир
    уже есть.
  2. make rebuild-storage — позиция проходит -1 → 0 →
    WORLD_STARTING_DAYS; в ODS ровно даты описи; топики созданы заново, офсеты
    групп идут с нуля; службы и их тома не пересоздавались.
  3. Красные пути краснеют нарочно: работник при отсутствующей, нулевой и
    отрицательной позиции; world_initialize при позиции -1; оборванная
    очистка оставляет -1, и следующий make up об этом говорит, а не
    заглаживает.
  4. Отказ в правах читается: весь DDL применяется учётной записью lifecycle
    без ошибок прав, административный запрос к системной базе ей отклоняют.
  5. make generate-batch поднимает генератор без исчезнувших зависимостей.
  6. Один холодный make clean && make up — на итоговой приёмке и только
    потому, что эта работа меняет самый первый подъём стенда. Правилом это не
    становится: обычная проверка обходится узкой областью очистки.
Источник истины — спека [«Жизненный цикл мира: создание и пересоздание ведёт Airflow»](https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/specs/2026-08-21-world-lifecycle.md) и [ADR 0013](https://git.dementev.space/ddmitry/clickstream-data-platform/src/branch/main/docs/adr/0013-world-lifecycle.md). Задача одна и делится только на коммиты: несколько связных коммитов внутри неё — нормально, дробить её на дочерние тикеты не надо. ## Цель Прикладная схема, данные стартового мира и позиция модельного времени переезжают к Airflow. Compose остаётся с долгоживущими службами и инфраструктурой, `make` — с подъёмом стенда и вызовом нужного дага. Менти видит один наблюдаемый граф вместо процесса, размазанного между Compose, shell и Airflow. ## Сначала прочитать - `docs/specs/2026-08-21-world-lifecycle.md` — контракты дагов, состояния позиции, поведение при отказах, границы; - `docs/adr/0013-world-lifecycle.md` — решение и доводы; - `docs/adr/0007-clickhouse-access.md` — модель доступа, куда добавляется новая учётная запись; - `docs/adr/0008-order-ingestion.md` и `docs/adr/0009-world-control.md` — устройство приёма заказов и пульта мира; - `docs/architecture/storage.md`, раздел «Раскладка SQL» — как сегодня применяется DDL и почему матвью приёма создаётся последней; - `docs/architecture/testing.md` — карта целей и правило о `make clean`; - `compose.yaml`, `Makefile`, `dags/world_control.py`, `dags/orders_ingest.py`, `infra/airflow/init.sh`, `infra/clickhouse/users.d/access.xml`, `scripts/wait-for-world.sh`, `scripts/ingest-starting-orders.sh`. ## Критерии приёмки **Compose и `make`** - [ ] Служб `kafka-init`, `clickhouse-init`, `world-init` и `world-snapshots` в `compose.yaml` нет — ни под профилем, ни как цели `compose run`. - [ ] Зависимости оставшихся служб перестроены: успешный подъём не ждёт исчезнувших одноразовых служб, `docker compose up --wait` возвращает ноль. - [ ] `make up` поднимает инфраструктуру, зовёт `world_initialize` через REST API Airflow и ждёт результата. - [ ] `make rebuild-storage` зовёт `world_recreate` и ждёт результата. - [ ] Один сценарий-помощник (например, `scripts/run-dag.sh`) запускает названный даг и ждёт конца; печатает идентификатор дага, идентификатор прогона, итоговое состояние и — при отказе — что делать дальше. Потолок ожидания назван пределом, а не бюджетом, и взят с кратным запасом от замера. - [ ] `scripts/wait-for-world.sh` и `scripts/ingest-starting-orders.sh` удалены: их работа переехала в даг и в помощника. - [ ] `make generate-batch` и `make generate-live` работают; унаследованные зависимости от исчезнувших служб сняты. - [ ] `make clean` не тронут. **Доступ** - [ ] В `access.xml` появилась учётная запись `lifecycle` с ролью `lifecycle_owner`: администрирование прикладных баз `stg`, `ods`, `dds`, `dm`, `dic` и кластерный DDL; чтение `system.settings`; административных прав на системные базы нет. - [ ] Права `etl` не расширены. - [ ] Пароль живёт в `.env`, образец добавлен в `.env.example`, подключение заводит `infra/airflow/init.sh`. **`world_initialize`** - [ ] Публичный даг без расписания, не на паузе, `max_active_runs = 1`. - [ ] Разбирает позицию по таблице состояний спеки: нет переменной или `0` — создаёт мир; `-1` — краснеет и зовёт повторить `make rebuild-storage`; значения от `1` до `WORLD_STARTING_DAYS - 1` — краснеют как недопустимые; `WORLD_STARTING_DAYS` и больше — пишет в журнал, что мир уже есть, и зеленеет, не трогая DDL, данные и позицию. - [ ] Топики создаются до DDL: `hits` — две партиции, `orders` — одна; повторный запуск подтверждает эти числа или падает с понятной ошибкой. - [ ] Канонический DDL применяется из `sql/ddl/` по порядку имён файлов, с ноды 1, учётной записью `lifecycle`. - [ ] Канонический образ генератора собирается дагом до проигрыша дней. - [ ] Ожидание приезда событий повторяет прежнее условие точно: сумма строк `ods.event_dist` за даты описи и `ods.event_errors_dist` не меньше суммы событий из `data/world-inventory.json`. Опись смонтирована в Airflow только для чтения. - [ ] Слепки отправляются вторым прогоном образа, `orders_ingest` дёргается ждущим триггером. - [ ] Позиция становится `WORLD_STARTING_DAYS` последней успешной задачей. **`world_recreate`** - [ ] Публичный даг: позиция `-1` до первого удаления; прикладные базы удаляются первыми; затем удаляются позиции групп `clickstream_hits`, `clickstream_orders`, после них — топики `hits`, `orders`; позиция `0` только после успешной очистки; дальше ждущий триггер `world_initialize`. - [ ] Общей вынесенной реализации с `world_initialize` нет — переиспользование идёт делегированием. **Работники мира** - [ ] `world_next_day` и `world_live_day` краснеют, когда позиции нет либо она меньше `WORLD_STARTING_DAYS`; отказ называет прочитанное значение и следующий шаг. - [ ] `orders_ingest` сторожа не получил. **Документация тем же PR** - [ ] Обновлены: `AGENTS.md`, `README.md`, обе спеки (`2026-07-30-stand-v2-realism.md`, `2026-08-01-generator.md`), `docs/adr/0009-world-control.md`, `docs/architecture/storage.md`, `docs/architecture/testing.md`, `docs/architecture/orders/start-world.md`, комментарии `compose.yaml`, комментарий про `world-init` в `generator/src/clickstream_generator/inventory.py` и комментарий про `kafka-init` в `sql/ddl/10-stg-tables.sql`. - [ ] ADR 0013 называет отменённые утверждения: отсутствие позиции больше не значит «стартовый мир уже есть» (спека генератора, раздел 8; ADR 0009; README); Compose больше не владеет созданием мира (storage.md, «Раскладка SQL»); DDL применяет не `etl`, а `lifecycle` (ADR 0007). - [ ] Замеры холодного и повторного `make up` и пересборки записаны в `docs/architecture/testing.md`, «Что проверено», и названы в теле PR. ## Границы - Мигратор схемы не заводим: на готовом мире изменённый DDL не применяется. - Блокировок, статуса обслуживания и автоматической паузы `world_live` в этой задаче нет — предусловие обслуживания живёт словами в спеке и README. - Содержание DDL, код генератора, устройство слоёв и приём заказов не трогаем. - Новых проверок в `make` не добавляем: вопрос «данные правильные» — к дагам DQ (`docs/architecture/testing.md`). - `make clean` в реализацию и обычную проверку не входит. ## Подводные камни - **Многозапросные файлы DDL.** Нынешний `clickhouse-client --multiquery` исполняет файл целиком. Новый путь обязан сохранить это свойство; API клиента и допустимый способ разбора сначала сверить через MCP Context7, решение коротко записать в коде или документации. - **Контекст сборки образа.** Airflow нужны только для чтения `generator/`, `data/` и корневой `.dockerignore`. Локальные `.venv` и кеши в контекст ехать не должны. Задачу сборки замерить отдельно и назвать её цену в теле PR. - **Удаление группы потребителей.** Пока в группе есть живой участник, удаление недопустимо. Отсюда порядок «сначала базы, затем группы и топики»; на живом стенде проверить, что после удаления баз группы освободились. - **`WORLD_STARTING_DAYS` остаётся в двух местах** — в `compose.yaml` и в `inventory.py` генератора. После переезда у числа в YAML один потребитель; комментарий в генераторе должен указывать на него, а не на исчезнувшую службу. - **Спорные API** — Airflow 3.3 (REST, `TriggerDagRunOperator`, `DockerOperator` и сборка образа) и DDL ClickHouse — сверять через MCP Context7 и коротко фиксировать, что проверили. ## Проверка Команды: ``` make config-test make lint make smoke make check-clickhouse make check-services ``` Сценарии на живом стенде: 1. **Повторный `make up` на готовом мире** — быстро, зелёно, позиция не изменилась, счётчики `check-clickhouse` те же, в журнале дага сказано, что мир уже есть. 2. **`make rebuild-storage`** — позиция проходит `-1` → `0` → `WORLD_STARTING_DAYS`; в ODS ровно даты описи; топики созданы заново, офсеты групп идут с нуля; службы и их тома не пересоздавались. 3. **Красные пути краснеют нарочно**: работник при отсутствующей, нулевой и отрицательной позиции; `world_initialize` при позиции `-1`; оборванная очистка оставляет `-1`, и следующий `make up` об этом говорит, а не заглаживает. 4. **Отказ в правах читается**: весь DDL применяется учётной записью `lifecycle` без ошибок прав, административный запрос к системной базе ей отклоняют. 5. **`make generate-batch`** поднимает генератор без исчезнувших зависимостей. 6. **Один холодный `make clean && make up`** — на итоговой приёмке и только потому, что эта работа меняет самый первый подъём стенда. Правилом это не становится: обычная проверка обходится узкой областью очистки.
ddmitry added the ready-for-agent label 2026-08-21 17:47:59 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: ddmitry/clickstream-data-platform#119