Files
clickstream-ch-kafka-supers…/docs/runbooks/startup-history.md
T
ddadminandClaude Fable 5 ac7a973504 feat(airflow): пульт стал world_init, добавлен DAG world_next_day (#4)
- Зачем:
  - список DAG'ов должен читаться лесенкой ddl_init → world_init →
    world_next_day, а путь менти — проходиться пустыми формами
    (issue #4, спека редизайна пути менти, решения 2–3).
- Что:
  - generator_control переименован в world_init, дефолт операции —
    import; next-day ушёл из выпадашки в отдельный DAG;
  - новый беспараметрный world_next_day: расписание */30 * * * *,
    создаётся на паузе, catchup=False, max_active_runs=1; общие
    задачи вынесены в airflow/dags/utils/startup_history_tasks.py;
  - доки и контрактные тесты обновлены синхронно; быстрый старт
    README — без make ddl, схему создаёт DAG ddl_init.
- Проверка:
  - make test (210 + 31) и make lint зелёные;
  - живая приёмка на чистом стенде: world_init пустой формой
    импортировал эталонный мир за 217 с (3 дня, 280 437 событий),
    world_next_day после снятия с паузы добавляет ровно один день
    за прогон, дашборд Superset собирается.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:45:02 +03:00

214 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Runbook: стартовая история стенда
Этот runbook нужен, чтобы один раз создать стартовую историю генератора, сохранить
её в файл и быстро восстановить на чистом стенде.
За устройством генератора см. [`generator/README.md`](../../generator/README.md)
и [спеку модельного времени](../specs/2026-06-14-generator-model-time-and-startup-history.md).
## Что дёшево
- Перезапуск без очистки volumes: Kafka хранит `generator_state` и manifest.
- Live-продолжение после импорта: генератор стартует с `T_end`, если настройки
совпадают.
- Восстановление чистого стенда из готового файла: события повторно пишутся в
Kafka, ClickHouse наполняется штатным путём.
## Что требует нового артефакта
- Другая длительность истории или другой профиль запуска.
- Другой `GEN_SEED`, `GEN_MODEL_T0`, часовой пояс, скорость или настройки
генерации.
- Осознанный новый мир после несовместимого state: сначала сбросьте state через
`GEN_STATE_RESET=true` или чистые volumes.
## Профили запуска
Основной способ — глагол плюс профиль:
| Профиль | Для чего | Длительность | Live-ход |
|---------|----------|--------------|----------|
| `ci` | Быстрая проверка и CI | `6h` | ×1, тик 60 с |
| `daily-wave` | История с видимой суточной волной | `3d` | ×60, тик 1 с |
Длительность можно переопределить через `GEN_HISTORY_DURATION`, например `2d`.
Команда сама считает `GEN_MODEL_T_END` от `GEN_MODEL_T0`.
## Эталонный мир
В репозитории хранится готовый мир за три модельных дня:
`data/startup_history/reference-world.json.xz`. Это обычный JSON-артефакт,
сжатый xz. Его добавляют прямо в Git, без Git LFS.
Сопровождающий проекта собирает файл одним запуском из корня репозитория:
```bash
ARTIFACT="$PWD/data/startup_history/reference-world.json.xz" \
make startup-history-export
```
Команда выполняет backfill с профилем `daily-wave` и сразу пишет xz-файл.
Несжатый JSON занимает около 850 МБ, сжатый файл — около 32 МБ. После проверки
файл можно добавить обычной командой:
```bash
git add data/startup_history/reference-world.json.xz
```
При импорте DAG дважды читает и распаковывает артефакт: во время предпроверки и
перед записью в Kafka. Это увеличивает время импорта, но не меняет результат.
Менти запускает `world_init` с пустой формой. Тогда читается эталонный мир из
репозитория. Следующий модельный день добавляет отдельный беспараметрный DAG
`world_next_day`. Из консоли тот же импорт запускается без указания пути:
```bash
make startup-history-import
```
## Пульт в Airflow
Основной учебный путь в Airflow UI:
1. Поднимите стенд: `make up`.
2. Если DDL ещё не применён, запустите `ddl_init`.
3. Снимите паузу с `etl_pipeline`, если он ещё paused:
`docker compose exec -T airflow-webserver airflow dags unpause etl_pipeline`.
4. Запустите `world_init` с пустой формой. По умолчанию он импортирует эталонный мир.
5. Когда нужен ещё один модельный день, запустите `world_next_day` с пустой формой.
`world_next_day` имеет расписание каждые 30 минут, но по умолчанию стоит на паузе.
Не включайте расписание до внедрения накопительных счётчиков manifest.
## Операции сопровождающего
В форме `world_init` сопровождающему дополнительно доступны операции:
- `backfill` — создать стартовую историю. После записи в Kafka DAG сам запускает
`etl_pipeline`, ждёт завершения и выполняет `check`.
- `check` — сверить ClickHouse с manifest из Kafka.
Поля формы:
- `profile` берётся из профилей генератора.
- `duration` можно оставить пустым, тогда берётся длительность профиля.
- `seed` и `model_time_speed` — необязательные переопределения мира.
- `artifact_path`: для `backfill` — куда сохранить файл; пусто — не сохранять.
Для `import` — что читать; пусто — эталонный мир из репозитория:
`/opt/airflow/data/startup_history/reference-world.json.xz`.
Backfill и import работают только на чистом стенде. Если Kafka-топики данных или
STG уже непустые, DAG упадёт до записи и подскажет `make clean`. Консольные
команды `make generator-backfill` и `make startup-history-import` делают такую же
предпроверку с хоста. Это защита от смешивания разных миров.
Границы пульта:
- `make up`, `make clean` и live-продолжение остаются в консоли.
- Операции `continue` в DAG нет намеренно: live — долгоживущий сервис, а пульт
управляет разовыми пакетными операциями.
- Airflow не получает доступ к жизненному циклу контейнеров; таски выполняют обычный
Python-код генератора.
Если backfill сохраняет файл в `./data`, он создаётся пользователем Airflow
внутри контейнера. Чтение работает из Airflow и консольных команд, но перезапись
чужого файла может потребовать удалить старый файл вручную.
## Экспорт
По умолчанию создаётся трёхсуточный артефакт `daily-wave` с суточной волной:
```bash
make startup-history-export
```
Файл по умолчанию: `/tmp/clickstream-startup-history.json`. В live-продолжении
профиль `daily-wave` проживает модельные сутки примерно за 24 настенные минуты.
Для быстрой автоматической проверки явно задайте служебный профиль `ci`:
```bash
PROFILE=ci ARTIFACT=/tmp/clickstream-startup-history-ci.json \
make startup-history-export
```
Команда делает чистый backfill и пишет в файл один связный набор: события Kafka,
state и manifest.
## Импорт на чистый стенд
```bash
make clean
docker compose up -d clickhouse kafka
make ddl
make startup-history-import
sleep 10
make transform
ARTIFACT=data/startup_history/reference-world.json.xz make startup-history-check
CHECK_LIVE_SEAM=0 make generated-history-check
```
Это путь только импорта: live-строк после `T_end` ещё нет. Стык backfill/live
проверяйте через `make generated-history-runtime-check` или после
`make generator-continue` и повторного `make transform`.
Импорт не пишет напрямую в ClickHouse. Он воспроизводит события и служебные
compact-топики в Kafka. ClickHouse читает данные через свои Kafka-таблицы и
Materialized View, затем batch строит ODS, DDS и DM.
Импорт запускайте с тем же профилем, на котором создан артефакт. Для служебного
артефакта `ci` профиль нужно задать явно:
```bash
PROFILE=ci ARTIFACT=/tmp/clickstream-startup-history-ci.json \
make startup-history-import
```
`make startup-history-check` сверяет контрольные числа DM-витрины с manifest
артефакта: события, визиты, пользователей и диапазон `event_timestamp`. Если
data-топики Kafka уже непустые, импорт остановится до публикации событий.
В текущем стеке `kafka-python` не даёт транзакционный producer для нескольких
топиков. Поэтому импорт остаётся clean-stand операцией: при ошибке записи он
удаляет import-топики Kafka, чтобы повторный импорт не дописал дубли. Если
ClickHouse уже успел прочитать частичные сообщения, очистите стенд через
`make clean` и повторите импорт.
## Live-продолжение
После импорта запускайте live с тем же профилем, что был в артефакте. Для
обычного артефакта `daily-wave` достаточно команды:
```bash
make generator-continue
```
У `daily-wave` скорость ×60. Долгий простой стенда создаёт большую дыру в
модельном времени: ночь простоя может стать десятками модельных суток без
событий. Для чистой демонстрации лучше очистите стенд, затем запустите
`make generator-reset` или повторите импорт стартовой истории через
`make startup-history-import`.
`make generator-reset` начнёт новый live-мир только на чистом стенде; если в
Kafka data-топиках или STG уже есть строки, команда попросит `make clean`.
Если читаемый state есть, но настройки не совпадают, генератор падает с перечнем
полей. Это защита от смешения разных миров. Для намеренного нового мира
сначала очистите стенд через `make clean`, затем запускайте нужный сценарий.
После обновления кода старый state может оказаться в старом формате. При
`GEN_STATE_RESET=false` это теперь громкий отказ, а не тихий старт с нуля поверх
старой истории. Оператору нужно выбрать одно из двух: очистить стенд через
`make clean` и заново создать стартовую историю, либо осознанно начать новый
live-мир через `make generator-reset` на чистом стенде.
После нестандартного мира `make generator-continue` нужно запускать с теми же
настройками, что были у backfill/import. При расхождении генератор громко
покажет поля, которые не совпали.
Старые артефакты `daily-wave`, созданные до перехода на ×60 и тик 1 с, с новым
профилем несовместимы. Это ожидаемо: защита от смешения миров должна остановить
такой запуск.
Старые переменные `GEN_RUN_MODE`, `GEN_STATE_RESET` и `GEN_MODEL_T_END` остаются
низкоуровневым способом для отладки и прямого `docker compose run`.