# 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` | История с видимой суточной волной | `2d` | ×60, тик 1 с | Длительность можно переопределить через `GEN_HISTORY_DURATION`, например `2d`. Команда сама считает `GEN_MODEL_T_END` от `GEN_MODEL_T0`. ## Пульт в Airflow Основной ручной путь — DAG `generator_control` в Airflow UI: 1. Поднимите стенд: `make up`. 2. Если DDL ещё не применён, запустите `ddl_init`. 3. Откройте `generator_control` и выберите `operation`. Операции: - `backfill` — создать стартовую историю. После записи в Kafka DAG сам запускает `etl_pipeline`, ждёт завершения и выполняет `check`. - `import` — прочитать артефакт из `artifact_path`. Несовместимый артефакт отклоняется до записи в Kafka. - `check` — сверить ClickHouse с manifest из Kafka. Поля формы: - `profile` берётся из профилей генератора. - `duration` можно оставить пустым, тогда берётся длительность профиля. - `seed` и `model_time_speed` — необязательные переопределения мира. - `artifact_path`: для `backfill` — куда сохранить файл; пусто — не сохранять. Для `import` — что читать; пусто — `/opt/airflow/data/startup-history-import.json`. 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 и консольных команд, но перезапись чужого файла может потребовать удалить старый файл вручную. ## Экспорт По умолчанию создаётся быстрый 6-часовой артефакт: ```bash make startup-history-export ``` Файл по умолчанию: `/tmp/clickstream-startup-history.json`. История на 2 суток с суточной волной. В live-продолжении этот профиль проживает модельные сутки примерно за 24 настенные минуты: ```bash ARTIFACT=/tmp/clickstream-startup-history-2d.json \ PROFILE=daily-wave \ make startup-history-export ``` Команда делает чистый backfill и пишет в файл один связный набор: события Kafka, state и manifest. ## Импорт на чистый стенд ```bash make clean docker compose up -d clickhouse kafka make ddl ARTIFACT=/tmp/clickstream-startup-history.json make startup-history-import sleep 10 make transform ARTIFACT=/tmp/clickstream-startup-history.json make startup-history-check make generated-history-check ``` Импорт не пишет напрямую в ClickHouse. Он воспроизводит события и служебные compact-топики в Kafka. ClickHouse читает данные через свои Kafka-таблицы и Materialized View, затем batch строит ODS, DDS и DM. Если артефакт создан не профилем `ci`, импорт запускайте с тем же профилем: ```bash PROFILE=daily-wave ARTIFACT=/tmp/clickstream-startup-history-2d.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 PROFILE=daily-wave make generator-continue ``` У `daily-wave` скорость ×60. Долгий простой стенда создаёт большую дыру в модельном времени: ночь простоя может стать десятками модельных суток без событий. Для чистой демонстрации лучше запустите `make generator-reset` или повторите импорт стартовой истории через `make startup-history-import`. Если читаемый state есть, но настройки не совпадают, генератор падает с перечнем полей. Это защита от смешения разных миров. Для намеренного нового мира используйте `make generator-reset` или `make clean`. После обновления кода старый state может оказаться в старом формате. При `GEN_STATE_RESET=false` это теперь громкий отказ, а не тихий старт с нуля поверх старой истории. Оператору нужно выбрать одно из двух: очистить стенд через `make clean` и заново создать стартовую историю, либо осознанно начать новый мир через `GEN_STATE_RESET=true` / `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`.