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

+37 -26
View File
@@ -67,8 +67,9 @@ make check-services
стенд и ждёт здорового состояния долгоживущих контейнеров. В образ Airflow
добавлены закреплённые клиенты ClickHouse и Kafka. Одноразовые `airflow-init` и
`superset-init` завершаются с кодом 0. Первый обновляет схему Airflow,
подготавливает администратора и подключение к `clickhouse-01`. Второй обновляет
Superset, создаёт администратора и импортирует подключение к `clickhouse-02`.
подготавливает администратора и два подключения к `clickhouse-01`. Второй
обновляет Superset, создаёт администратора и импортирует подключение к
`clickhouse-02`.
С нуля подъём занимает около трёх минут, на живом стенде — около минуты.
`make smoke` за секунды спрашивает, собран ли стенд: зависимости машины,
@@ -118,29 +119,33 @@ Kafka по той же причине спрашивают снаружи. Её
поднятый стенд, сколько стоит прогон и куда класть новую — в
[карте проверок](docs/architecture/testing.md).
Остановить контейнеры без удаления данных можно командой `make down`. Для
полного сброса с удалением всех именованных томов используйте `make clean`.
Повторный `make up` безопасен: одноразовая подготовка приложений идемпотентна.
Остановить контейнеры без удаления данных можно командой `make down`. Если
нужно вернуть прикладной мир к началу, `make rebuild-storage` запустит в
Airflow его явную пересборку: удалит прикладные базы ClickHouse, топики и
позиции потребителей Kafka, затем создаст схему и данные заново. Службы и их
тома останутся на месте. `make clean` удаляет весь стенд вместе с именованными
томами и для обычной разработки не нужен. Перед пересборкой поставьте
`world_live` на паузу и дайте уже начатому работнику закончить день.
## Стартовый мир
Стенд поднимается не пустым: разовая служба `world-init` играет в топик `hits`
первые восемь дней модельного времени — понедельник по понедельник, 401 185
событий. Следом второй запуск того же генератора отправляет в топик `orders`
семь слепков — за дни 0…6. Дальше оба источника разбирает хранилище, и к концу
`make up` события лежат в `ods.event`, а заказы — в `ods.order_v`. Так у всякой
лабы есть данные, и всегда одни и те же.
Стенд поднимается не пустым: `make up` через REST API запускает даг Airflow
`world_initialize`. Тот создаёт прикладные топики и схему, собирает образ
генератора и играет в `hits` первые восемь дней модельного времени — понедельник
по понедельник, 401 185 событий. Следом второй запуск генератора отправляет в
`orders` семь слепков — за дни 0…6. Даг ждёт приёма обоих источников и только
тогда ставит позицию мира в `8`. Так у всякой лабы есть данные, и всегда одни и
те же.
Последний день намеренно неполон: события дня 7 уже приехали, а его слепок
уедет только с первым ходом мира. Поэтому на свежем стенде правый край графика
выручки отстаёт от кликстрима и дозаполняется по мере движения мира — так
виден разный темп потокового трекера и ночной выгрузки магазина.
Ждать приходится дольше, чем работает заливка. `make up` сначала опрашивает
ClickHouse, пока события не доедут, затем разово запускает даг `orders_ingest`
и ждёт его конца. Повторный `make up` заливает мир заново; это не ошибка, а
свойство: номера событий и версии заказов те же, и повторы схлопнут
`ReplacingMergeTree`.
Повторный `make up` безопасен: `world_initialize` видит готовую позицию,
сообщает в журнале, что мир уже существует, и не применяет DDL, не собирает
образ и не заливает данные повторно. Изменённую схему применяют адресно либо
явной командой `make rebuild-storage`.
Сам мир в git не хранится — он чистая функция зерна, и держать его в
репозитории значило бы держать там кэш. Вместо него лежит **опись мира**,
@@ -202,9 +207,9 @@ make generate-live GENERATOR_DAY=3 GENERATOR_SPEED=1000
docker compose --profile generator build generator
```
Тот же образ несёт заливка стартового мира, поэтому свежим его держит и
обычный `make up`: он собирает образы всего стенда, и генератор теперь среди
них.
Тот же образ несёт заливка стартового мира. Перед такой заливкой
`world_initialize` сам собирает его из текущего дерева; на готовом мире
повторный `make up` до сборки не доходит.
Разведено это нарочно: собрать образ и запустить контейнер — разные действия, и
цель запуска, молча пересобирающая образ, стирает между ними границу. Что образ
@@ -231,11 +236,13 @@ uv run --project generator python -m clickstream_generator batch \
### Пульт мира
Считать дни самому необязательно. В Airflow живёт **пульт мира** — три дага,
которые зовут тот же образ генератора и ведут позицию на оси за вас.
Считать дни самому необязательно. В Airflow живёт **пульт мира**: два дага
создают его начальное состояние, а три двигают ось дальше.
| Даг | Что делает |
| --- | --- |
| `world_initialize` | создаёт стартовый мир, если его ещё нет; повторный запуск ничего не меняет |
| `world_recreate` | удаляет только прикладной мир и запускает его создание заново |
| `world_next_day` | играет следующие дни пачкой; сколько — параметр запуска, по умолчанию один |
| `world_live_day` | играет следующий день в темпе модельного времени, около двадцати четырёх минут |
| `world_live` | выключатель: пока снят с паузы, дёргает `world_live_day` день за днём |
@@ -248,8 +255,10 @@ uv run --project generator python -m clickstream_generator batch \
Позицию хранит переменная Airflow `world_position` — номер первого несыгранного
дня. Ставит её работник, сыгравший день, и только по успеху: оборванный прогон
позицию не двигает, и следующий запуск играет тот же день заново. Нет
переменной — мир в стартовом состоянии.
позицию не двигает, и следующий запуск играет тот же день заново. Отсутствующая
переменная означает, что мир ещё не создавали; `-1` — очистка не закончена,
`0` — очистка закончена, а начальная генерация ещё не завершена. Работники
начинают играть только с позиции `8` и больше.
Выключатель создаётся на паузе. Снимите — мир поедет сам; поставите обратно —
встанет на границе модельных суток, доиграв начатый день. Форма пульта и доводы
@@ -286,10 +295,12 @@ uv run --project generator python -m clickstream_generator batch \
- Prometheus — `http://127.0.0.1:29090`;
- Grafana — `http://127.0.0.1:23000`, пользователь `admin`, пароль `admin`.
В ClickHouse шесть пользователей и пять ролей:
В ClickHouse семь пользователей и шесть ролей:
- `etl` применяет DDL и подключает Airflow; роль `etl_writer` читает и пишет
слои хранилища;
- `etl` подключает рабочие даги Airflow; роль `etl_writer` читает и пишет слои
хранилища;
- `lifecycle` применяет канонический DDL и пересоздаёт прикладные базы; роль
`lifecycle_owner` не даёт эти административные права рабочим дагам;
- `bi` подключает Superset; роль `bi_reader` читает справочники `dic` и будущие
слои DDS и DM;
- `analyst` предназначен для подключения человека и читает все слои и