feat(generator): глагол next-day — следующий модельный день от слепка

- Зачем:
  - режим кормления стенда порциями: менти триггерит «следующий день»,
    видит полный цикл DWH за один шаг (задача 13, вариант 2 — генерация
    от слепка T_end).
- Что:
  - новый ограниченный режим next-day: восстановление мира из state,
    генерация ровно [T_end, T_end+24h), публикация данные -> state ->
    манифест (манифест — точка фиксации, автоотката нет).
  - операция next-day в DAG generator_control: своя предпроверка границы
    вместо clean-guard, идемпотентность через параметр expected_t_end.
  - цепочка границ — накопительное поле boundaries в манифесте, старый
    формат читается как [T0, T_end]; импорт не изменён.
  - новая проверка цепочки (make generated-history-chain-check): непарные
    счётчики и однородность по каждой границе, явный статус нулевого
    стыка, хвост за границей по всем четырём топикам, литералы в UTC
    с микросекундами.
  - документация OPERATIONS.md: глагол, предпроверка, восстановление
    после сбоя, ограничение retention; в задаче 13 — решения двух слепых
    ревью постановки и кода с аргументами отклонений.
- Проверка:
  - make test: 204 теста генератора + 31 контракт корня, зелёные.
  - make generated-history-chain-check: зелёный, 2 внутренние границы,
    непарные счётчики нулевые; учебный цикл: DM 322 -> 10026 -> 19196
    за два next-day подряд.
  - make generated-history-runtime-check (регрессия задачи 20): зелёный.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-12 23:53:48 +03:00
co-authored by Claude Fable 5
parent 540989d358
commit f5dee26eda
17 changed files with 1501 additions and 31 deletions
+45 -3
View File
@@ -14,6 +14,8 @@
- `CHECK_LIVE_SEAM=0 make generated-history-check` (повторяемая проверка
ClickHouse и Superset после прогона только стартовой истории)
- `make generated-history-runtime-check` (короткая проверка стыка backfill/live)
- `make generated-history-chain-check` (проверка завершённых стыков между
порциями истории)
- `make ddl` (применяет SQL из `sql/ddl/00_databases.sql` и `sql/ddl/*/*.sql` в ClickHouse)
- `make data` (архивный путь: заливает `data/*.jsonl` в Kafka; не основной источник аналитики)
- `make transform` (запускает batch-процесс ODS -> DDS -> DM)
@@ -59,19 +61,55 @@ volumes или live-генератором. Для стыка backfill/live от
дождаться `success`;
- `import` — импортировать портативный артефакт, затем запустить
`etl_pipeline` и дождаться `success`;
- `next-day` — восстановить мир из state, добавить 24 модельных часа,
затем запустить `etl_pipeline` и дождаться `success`;
- `check` — сверить ClickHouse с manifest из Kafka.
- Параметры:
- `operation` (`backfill` / `import` / `check`);
- `operation` (`backfill` / `import` / `next-day` / `check`);
- `profile` — список берётся из `PROFILES` генератора;
- `duration``6h`, `2d` и т.п.; пусто означает длительность профиля;
- `seed`, `model_time_speed` — необязательные переопределения мира;
- `artifact_path` — для `backfill` путь сохранения, для `import` путь чтения.
- `artifact_path` — для `backfill` путь сохранения, для `import` путь чтения;
- `expected_t_end` — необязательная ожидаемая граница перед `next-day`.
При расхождении запуск показывает ожидаемое и фактическое значения.
Backfill/import требуют чистый стенд: пустые data-топики Kafka и пустые
`stg.*_raw`. При отказе очистите стенд через `make clean`. Операции `continue`
в DAG нет: live-генератор — долгоживущий сервис, его запускают с консоли через
`make generator-continue`.
`next-day` работает на непустом стенде и не использует проверку чистоты.
Перед записью пульт требует manifest, state ровно на его `T_end` и остановленный
live-генератор. Настройки мира берутся из manifest; поля `profile`, `duration`,
`seed` и `model_time_speed` формы для этой операции не применяются. Один запуск
добавляет полуоткрытый диапазон `[T_end, T_end + 24h)` в UTC. Новая граница
появляется в `boundaries`; старый manifest без поля читается как `[T0, T_end]`.
Расписание остаётся выключенным (`schedule=None`), а `max_active_runs=1` не даёт
двум доливкам выполняться параллельно.
После двух доливок проверьте завершённые стыки:
```bash
make generated-history-chain-check
```
Проверка проходит по внутренним границам `boundaries`, ищет непарные строки и
смену browser/referer/utm внутри переходящих визитов. Она не меняет
`make generated-history-runtime-check` для стыка backfill/live.
Точка фиксации `next-day` — новый manifest. Порядок записи: data-топики, state,
manifest. Автоматического отката нет. Если запуск упал до публикации manifest,
не повторяйте доливку поверх возможного хвоста. Очистите стенд и переимпортируйте
последний исправный портативный артефакт, затем повторите `next-day`.
Текущая версия пересчитывает накопительные счётчики и контрольные суммы по всей
доступной истории data-топиков Kafka. Поэтому время выполнения и расход памяти
каждой доливки растут вместе с историей. Стандартный срок хранения Kafka тоже
ограничивает долгую работу стенда. Для ручного учебного цикла запускайте
соседние дни без долгих пауз. Перед включением расписания отдельная задача
должна выбрать одно из решений: бессрочное хранение data-топиков или новый
формат накопительного состояния manifest.
Перед запуском `etl_pipeline` пульт проверяет, что DAG не стоит на паузе. Если
стоит, задача падает сразу с подсказкой снять паузу в UI или командой:
@@ -165,7 +203,7 @@ make generator-logs
| `GEN_MODEL_T_END` | Правая граница стартовой истории для `backfill` | пусто |
| `GEN_MODEL_TIMEZONE` | Часовой пояс модельных часов для дневного коэффициента | `UTC` |
| `GEN_MODEL_TIME_SPEED` | Сколько модельных секунд проходит за одну настенную секунду | `1` |
| `GEN_RUN_MODE` | Режим генератора | `live` |
| `GEN_RUN_MODE` | Режим: `live`, `backfill` или `next-day` | `live` |
| `GEN_LAUNCH_PROFILE` | Имя профиля запуска для логов | `ci` |
| `GEN_STARTUP_HISTORY_ARTIFACT` | JSON-файл для экспорта стартовой истории в режиме `backfill` | пусто |
| `GEN_STATE_ENABLED` | Сохранять state v3 между рестартами | `true` |
@@ -254,6 +292,10 @@ GEN_HISTORY_DURATION=2d make generated-history-analytics
контрольными числами. При live-запуске с теми же настройками генератор видит,
что state совпадает с manifest, и стартует ровно с `T_end` без настенной дельты.
Новый backfill записывает `boundaries=[T0, T_end]`. Каждый успешный `next-day`
добавляет одну границу, сдвигает `model_t_end` и пересчитывает накопительные
счётчики и контрольные суммы по всей истории.
Если историю нужно сохранить в файл и восстановить на чистом стенде без новой
генерации, используйте [runbook стартовой истории](./runbooks/startup-history.md).