docs(generator): зафиксированы источник аналитики и стартовая история

- Зачем:
  - генератор даёт здоровую пирамиду, и стенду нужен единый источник аналитики вместо вырожденного статического сида.
- Что:
  - добавлен ADR-0006: генерация — единственный источник аналитики, статический сид становится архивным (кладовка значений до синтеза фактуры).
  - добавлена спека модельного времени: точка отсчёта, заливка прошлого, стартовая история, сохранение состояния, воспроизводимость и проверка в два шага.
  - в ADR-0004 и ADR-0005 добавлены указатели вперёд на ADR-0006 и спеку.
- Проверка:
  - чтение документов; перекрёстные ссылки между ADR-0004/0005/0006 и спекой согласованы.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 14:47:20 +03:00
co-authored by Claude Opus 4.8
parent 899a3f07a1
commit 49512b190a
4 changed files with 322 additions and 0 deletions
@@ -0,0 +1,199 @@
# Модельное время на практике: сохранение состояния, заливка прошлого и стартовая история
Дата: 2026-06-14
Статус: Draft
Связано: [ADR-0005](../adr/0005-generator-model-clock.md) (решение про модельные
часы — эта спека его дорабатывает), [ADR-0006](../adr/0006-generation-as-sole-analytics-source.md)
(стартовая история как источник аналитики), мат-спека
[`2026-06-10-generator-math-model.md`](./2026-06-10-generator-math-model.md)
(её разделы «Персистентность через рестарты» и «Воспроизводимость» здесь
**приводятся в соответствие** с модельным временем — не повторяются, а
переописываются ссылкой), [`CONTEXT.md`](../../CONTEXT.md), задача
[`06-state-v2-and-restart`](../../.scratch/feature-data-generator/issues/06-state-v2-and-restart.md)
(сохранение состояния построено на настенных часах).
Уровень документа — **модель и правила**, как в мат-спеке. Код, имена настроек,
формат конфигурации и сам способ управления ходом часов — за исполнителем, в
рамках правил этой спеки (как условились в ADR-0005).
## Проблема
ADR-0005 решил отвязать время генератора от реальных часов и ввёл три скорости
его хода: обычную (×1), ускоренную (×K) и мгновенную «промотку» прошлого. Но два
правила старой модели — как генератор сохраняет состояние между перезапусками и
как добивается повторяемости — описаны от реальных часов. А ADR-0006 сделал
стартовую историю (готовое сгенерированное прошлое) единственным источником
данных стенда.
Осталось довести модель до конца: как работает промотка прошлого, его заморозка и
непрерывный запуск стенда с этого момента; как при этом меняются правила
сохранения состояния и повторяемости; и как всё это проверить. Всё это — **один
механизм**: промотать прошлое → заморозить → продолжить живьём. Это один и тот же
путь сохранения и восстановления, просто с разных сторон.
## Цели
- **Дать генератору собственные часы и сделать поток повторяемым.** Генератор
должен отсчитывать время от своей точки отсчёта (ниже — «стартовая модельная
точка `T0`»), а не от реальных часов компьютера. Тогда при одних и тех же
настройках он каждый раз порождает один и тот же поток — это нужно для тестов и
для повторяемых уроков.
- **Переписать правила сохранения и восстановления состояния по этим часам.**
Сейчас они завязаны на реальное время. Главное правило — «если посетитель молчал
дольше 30 минут, считаем, что он ушёл» — должно мерить эти 30 минут по часам
генератора. И поток больше не должен зависеть от того, в котором часу реального
дня запущен генератор.
- **Научиться быстро «проматывать» прошлое и замораживать его как стартовый набор
данных.** Генератор прокручивает время без пауз от точки отсчёта до нужного
момента, порождает события прошлого и сохраняет «слепок» своего состояния. Этот
замороженный набор — то, с чего свежий стенд начинает жить, уже имея историю:
здоровую пирамиду «пользователей меньше, чем визитов, а визитов меньше, чем
событий» с первой минуты.
- **Описать, как всё это проверить — в два шага.** Сначала числами: агент
поднимает стенд и сверяет данные в ClickHouse. Потом глазами: человек смотрит на
дашборды и видит, что распределение похоже на задуманное, а стенд «дышит» во
времени.
## Чего здесь не делаем
- **Не учим генератор придумывать «фактуру» сам** (браузеры, гео, устройства,
метки кампаний). Это отдельная спека — следствие ADR-0006. Пока её нет,
стартовая история берёт фактуру из статического сида; это осознанно временно.
- **Не описываем здесь перестройку процесса** на новый сид (загрузка
`kafka_load_dag`, витрины/Superset, уроки) — это решено в ADR-0006 и
проектируется отдельно. Эта спека — только про сам генератор.
- **Не фиксируем** имена настроек, формат конфигурации и сам алгоритм — это за
исполнителем.
- **Не добавляем** новые типы событий и инкрементальную загрузку ETL.
## Модель
### Точка отсчёта и скорость хода часов
У генератора своя точка отсчёта времени — **стартовая модельная точка `T0`**
(задаётся в настройках). В метку события (`event_timestamp`) пишется это
внутреннее время. Скорость, с которой оно идёт относительно реальных часов,
переключается (в ADR-0005 — «драйвер часов»):
- **×1** — как реальное время;
- **×K** — в `K` раз быстрее;
- **заливка прошлого** — время гонится без пауз от `T0` до нужного момента
(особый случай очень большого `K`).
### Повторяемость
При одном и том же зерне `GEN_SEED`, одной и той же `T0` и одной скорости
генератор каждый раз даёт **один и тот же поток**. Это убирает прежнюю оговорку
мат-спеки «запуск в другой час даёт другой поток»: дневной коэффициент
(день/ночь) теперь считается по внутреннему времени от `T0`, а не по реальным
часам. Так раздел «Воспроизводимость» мат-спеки приводится в соответствие с
модельным временем.
### Заливка прошлого и стартовая история
«Промотать» прошлое — значит прогнать время без пауз от `T0` до момента `T_end` и
сгенерировать события этого отрезка. На выходе — события за `[T0, T_end]` и
**слепок состояния** генератора на момент `T_end`.
События плюс слепок и есть **стартовая история** («новый сид» — синоним, новое
значение слова «сид» не заводим). При создании стенда это прошлое заливается, и
генератор готов продолжить ровно с `T_end`. Стенд сразу живёт с готовой историей —
с настоящей пирамидой «пользователей меньше, чем визитов, визитов меньше, чем
событий», без вырождения статического сида, где пользователей ровно столько же,
сколько визитов.
### Сохранение и восстановление состояния
Правило «молчал дольше 30 минут — посетитель ушёл» остаётся; меняется только **от
какого момента отсчитывать эти 30 минут**:
- **после сбоя** — от реального «сейчас» (время и правда прошло) → как сегодня;
- **при запуске со стартовой истории** — от метки слепка (`T_end`) → разрыва
почти нет, активные визиты не обрываются, мир продолжается без шва.
Паузы между визитами одного пользователя и обязательная пауза перед возвратом
тоже считаются по внутренним меткам времени, а не по числу тактов. Если
сохранённое состояние повреждено или его нет — генератор стартует с чистого листа
(свежая популяция от `GEN_SEED`) и пишет предупреждение в лог. Так раздел
«Персистентность» мат-спеки приводится в соответствие с модельным временем.
### С какой скоростью стенд живёт дальше
После старта стенд продолжает жить на выбранной скорости. Скорость — это **ручка
под задачу**: чтобы увидеть медленные вещи (возвраты, сдвиг воронки после правки
таблицы переходов, «дыхание» суточной нагрузки) за учебное время, нужно ускорение
(×K) — иначе суточная волна разворачивается реальные сутки. Какой скорость будет
по умолчанию (×1 «как настоящий сайт» или ускоренная «учебная») — решает
урок/исполнитель; спека лишь фиксирует, что это ручка и что именно на ней держится
наблюдаемость медленных явлений.
### Временная опора на сид (фактура)
Пока генератор не умеет придумывать фактуру сам (ADR-0006), стартовая история
одевает события в данные из статического сида (браузер, гео, устройство, метки
кампаний). Это временно и не мешает: механизм времени и стартовой истории не ждёт
синтеза фактуры, а сид до его появления остаётся кладовкой готовых значений — но
уже не источником аналитики.
## Проверка
Два шага — это разделение труда: первый объективный и повторяемый (его делает
агент или CI), второй — человеческий взгляд.
### Шаг 1 — агент сверяет числа в ClickHouse
На чистом стенде с фиксированными `GEN_SEED` и `T0` залить стартовую историю,
прогнать STG→DM и проверить:
- **пирамида:** уникальных пользователей меньше, чем визитов, а визитов меньше,
чем событий;
- **воронка** убывает по шагам `/home → товары → /cart → /payment →
/confirmation`; доля дошедших до `/confirmation` — в нужном коридоре;
- **возвраты:** у части пользователей больше одного визита;
- **длина визита** и доля коротких визитов — в коридорах мат-спеки.
Главное: проверки **повторяемы** (тот же `GEN_SEED` и `T0` → те же числа в
пределах допуска) и записаны как команды и запросы, чтобы их мог прогнать агент
или CI без человека. Сами запросы и числовые коридоры — при реализации.
### Шаг 2 — человек смотрит на дашборды
Глазами убедиться, что:
- **распределение похоже на задуманное** — на наши спроектированные распределения
(мат-спека), а не на профиль чужого сида;
- **стенд «дышит»** — видна суточная волна нагрузки, копятся возвраты и воронка
(заметно на ×K).
Здесь всплывает нехватка: дашборда с распределением сгенерированных данных пока
нет. Superset построен на сиде, а Grafana показывает только скорость работы
сервиса, не форму данных. Закрыть можно двумя способами: переключить Superset на
генерацию (часть большой миграции — позже) или добавить простую панель
«распределение из ClickHouse» в Grafana. Пошаговые действия шага 2 — в будущий
runbook «проверка генератора на стенде».
## Решения и отклонённые варианты
- **Приняли:** одна спека на время, сохранение состояния и стартовую историю —
это один механизм, а не три задачи.
- **Приняли:** своя точка отсчёта `T0` как опора повторяемости; дневной
коэффициент считается по внутреннему времени.
- **Приняли:** проверка в два шага (агент — числа, человек — глаза).
- **Отклонили:** жёстко подгонять генерацию под профиль сида (см. ADR-0006).
- **Отклонили:** приводить сохранение состояния в соответствие с модельным
временем отдельно от стартовой истории — это разрезало бы один механизм надвое.
## Влияние на документацию
- **Мат-спека:** разделы «Персистентность через рестарты» и «Воспроизводимость»
пометить как переописанные в модельном времени этой спекой (ссылкой, без
повтора).
- **ADR-0005:** направление «стартовая история» — отмечено как запущенное.
- **CONTEXT.md:** «новый сид» = «стартовая история стенда» (синоним, не новое
значение); роль статического сида — по ADR-0006.
- **Будущий runbook** «проверка генератора на стенде» — шаги шага 2.
- **`generator/README.md`, `docs/OPERATIONS.md`** — при реализации: скорость хода
часов, ×K, стартовая история, проверки шага 1.