docs(world): описан жизненный цикл прикладного мира

- Зачем:
  - границы владения Compose и Airflow должны быть видны менти из одного источника истины.
- Что:
  - добавлены спека и ADR жизненного цикла мира;
  - обновлены README, архитектурные справочники и прежние решения;
  - правило полной очистки записано как явное и проверяемое требование к агенту.
- Проверка:
  - документация вычитана через `ai-text-lint` и `writing-for-agents`;
  - выполнена литературная вычитка русского текста;
  - `git diff --check`.
This commit is contained in:
ddadmin committed 2026-08-21 23:02:15 +03:00
1 parent fc5244cc67
commit 7e6554507a
12 files changed
+499 -104

No files matched your search

+5 -3
View File
@@ -24,9 +24,11 @@ JSON-файл отдельного тома `airflow_auth`. Новый имен
пользователи и строки подключения. Это экономит один контейнер, но сохраняет
границу владения данными.
Airflow получает подготовленное общее подключение к `clickhouse-01`, а
Superset — подключение `clickhousedb://` к `clickhouse-02`. Провайдер
ClickHouse для Airflow не нужен: локальный образ содержит прямой клиент.
Airflow получает два подготовленных подключения к `clickhouse-01`: рабочее
`clickhouse_default` и административное `clickhouse_lifecycle` для дагов
жизненного цикла мира ([ADR 0013](0013-world-lifecycle.md)). Superset получает
подключение `clickhousedb://` к `clickhouse-02`. Провайдер ClickHouse для
Airflow не нужен: локальный образ содержит прямой клиент.
Пробник создаёт служебные таблицы `ON CLUSTER`, пишет на первой ноде и через
`remote` читает `Distributed` на второй. Разные ноды сохраняют учебную
ловушку: забытый `ON CLUSTER` проявится в Superset, даже если операция Airflow
+12 -8
View File
@@ -9,8 +9,11 @@
- `default` — с паролем, только служебный: проверки здоровья нод и работа
изнутри контейнеров. Приложения им не ходят.
- `etl` с ролью `etl_writer` — чтение и запись во всех слоях. Им ходит
Airflow и применяется DDL при подъёме стенда.
- `etl` с ролью `etl_writer` — чтение и запись во всех слоях. Им ходят рабочие
даги Airflow.
- `lifecycle` с ролью `lifecycle_owner` — создание и удаление только
прикладных баз и объектов. Им даги жизненного цикла применяют DDL и
пересоздают мир ([ADR 0013](0013-world-lifecycle.md)).
- `bi` с ролью `bi_reader` — чтение витрин DM, слоя DDS и справочников `dic`.
Им ходит Superset.
- `analyst` с ролью `analyst_reader` — чтение всех слоёв и справочников. Им
@@ -127,10 +130,11 @@ DDS под ними, потому что так чаще всего и быва
Единственное, что ему доступно сегодня, — справочники: с ними зона `dic`
пришла раньше своих потребителей.
Владелец матвью приёма определяется тем, кто применил DDL. Весь DDL стенда
идёт через `IF NOT EXISTS`, поэтому на существующих томах три матвью
сохранят нынешнего владельца, а на собранном заново стенде им станет `etl`.
Работают оба — права есть у обоих, — но два стенда в этом расходятся.
DDL для матвью приёма выполняется от имени `lifecycle`. Это видно в журнале
запросов, а при создании ClickHouse проверяет у этой учётки чтение источника и
вставку в цель. Так отдельная учётка показывает границу между редким
управлением структурой и повседневной записью `etl`, не выдавая рабочим дагам
право удалять базы.
Лаба «сломать права» вариантом с файлами не закрывается: создать через SQL
новую роль и нового пользователя ничто не мешает, и новую роль разрешено
@@ -182,8 +186,8 @@ ClickHouse через MCP Context7 (#114).
- Секрет кластера принимается внутри `remote_servers` в виде
`<secret from_env="CLICKHOUSE_CLUSTER_SECRET"/>`. Запрос от `etl` через обе
ноды выполнился на каждой под пользователем `etl`.
- `from_env` работает у элемента `password`: `etl`, `bi` и `analyst` вошли с
паролями из окружения и получили свои роли.
- `from_env` работает у элемента `password`: `etl`, `lifecycle`, `bi` и
`analyst` вошли с паролями из окружения и получили свои роли.
- При запросе от `bi` через Distributed на второй ноде `system.query_log`
показал `user = bi` и `initial_user = bi`. Значит, на соседнем шарде
применяются права инициатора, а не общей учётной записи.
+5
View File
@@ -30,6 +30,11 @@
работник берёт из переменной, а не из логической даты прогона: календарь Airflow
к оси мира отношения не имеет.
Позднее [ADR 0013](0013-world-lifecycle.md) отдал Airflow начальную позицию и
состояния обслуживания: переменной нет до первого создания мира, `-1` означает
незавершённую очистку, `0` — чистый мир до генерации, а работники принимают
только позицию `8` и больше.
Пакетная автоматика — второй выключатель, дёргающий `world_next_day`, —
**отложена, а не отвергнута**.
+67
View File
@@ -0,0 +1,67 @@
# ADR 0013. Жизненный цикл мира ведёт Airflow
Дата: 21 августа 2026 года. Статус: принято. Реализация — тикет #119.
## Решение
Compose поднимает службы стенда. Airflow владеет прикладным миром: топиками
Kafka, DDL прикладных баз ClickHouse, стартовыми данными и позицией модельного
времени. `make` поднимает инфраструктуру и через REST API запускает нужный даг,
дожидаясь его результата.
Безопасный даг `world_initialize` создаёт мир, если тот ещё не создан. На
готовом мире он ничего не меняет и успешно сообщает об этом в журнале.
Разрушительный даг `world_recreate` очищает прикладные базы, топики и позиции
их потребителей, затем запускает `world_initialize` и ждёт его завершения.
Канонический DDL применяет отдельная учётная запись Airflow с правами только на
прикладную структуру стенда.
`world_position` остаётся единственным состоянием пульта:
- переменной нет — стенд ещё не инициализировали;
- `-1` — прежний мир признан недействительным, очистка не завершена;
- `0` — мир очищен и готов к начальной генерации;
- `8` и больше — первый несыгранный день готового мира.
`world_recreate` ставит `-1` до первого удаления и `0` только после успешной
очистки. `world_initialize` принимает отсутствие позиции или `0`, а значение
`8` и больше считает готовым миром. Позиция становится `8` последней задачей,
после генерации стартовых дней и успешного приёма обоих источников. Обычные
даги движения мира работают только с готовой позицией.
## Почему
Прежняя цепочка делила один процесс между Compose, shell-скриптами и Airflow:
одноразовые службы создавали стартовые данные, отдельный сценарий ждал их
приёма, а пересборка сама исправляла внутреннюю переменную пульта. Из-за этой
границы успешная отправка в Kafka выдавалась за создание мира, повторный
`make up` смешивался с инициализацией данных, а узкая пересборка сохраняла в
Kafka сообщения и офсеты прежнего мира.
Airflow уже ведёт последующие дни и приём заказов. Отдав ему начало той же оси,
получаем один наблюдаемый процесс и одну точку успеха: зелёный
`world_initialize` означает, что схема создана, стартовый мир принят и его
следующий день известен.
## Что это решение уточняет
- В [ADR 0007](0007-clickhouse-access.md) DDL больше не принадлежит `etl`:
прикладную структуру ведёт отдельный `lifecycle`.
- В [ADR 0009](0009-world-control.md) отсутствие `world_position` больше не
означает готовый стартовый мир. Начальную позицию ставит
`world_initialize`, а работники принимают только `8` и больше.
- Одноразовые службы Compose и shell-ожидание, описанные прежним устройством
хранилища, отменены. Их последовательность стала наблюдаемым графом Airflow.
## Отклонено
- Оставить создание мира в Compose, а позицию исправлять из
`make rebuild-storage`: работает технически, но сохраняет двух
оркестраторов одного процесса.
- Один безусловно разрушительный даг для первого и повторного запуска: обычный
`make up` мог бы стереть уже прожитые дни.
- Отдельная переменная состояния мира: она дублировала бы
`world_position` и могла разойтись с ней. Значения `-1` и `0` выражают
нужные переходы в одном месте.
- Выдать обычной учётной записи `etl` право удаления баз: рабочий конвейер не
должен получать административные права из-за редкой операции восстановления.