- Зачем: - генератор даёт здоровую пирамиду, и стенду нужен единый источник аналитики вместо вырожденного статического сида. - Что: - добавлен ADR-0006: генерация — единственный источник аналитики, статический сид становится архивным (кладовка значений до синтеза фактуры). - добавлена спека модельного времени: точка отсчёта, заливка прошлого, стартовая история, сохранение состояния, воспроизводимость и проверка в два шага. - в ADR-0004 и ADR-0005 добавлены указатели вперёд на ADR-0006 и спеку. - Проверка: - чтение документов; перекрёстные ссылки между ADR-0004/0005/0006 и спекой согласованы. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
112 lines
9.9 KiB
Markdown
112 lines
9.9 KiB
Markdown
# ADR-0005: Модельные часы генератора, отвязанные от настенного времени
|
||
|
||
Принято: 2026-06-11
|
||
Статус: accepted
|
||
Связано: [ADR-0004](./0004-steady-stream-synthetic-generator.md) (расширяет —
|
||
steady-stream как синтетический генератор), [`CONTEXT.md`](../../CONTEXT.md)
|
||
(термины времени и «сида»), мат-спека
|
||
[`docs/specs/2026-06-10-generator-math-model.md`](../specs/2026-06-10-generator-math-model.md)
|
||
(разделы «Персистентность» и «Воспроизводимость» написаны от настенных часов —
|
||
будут реконсилированы отдельным заходом), задача
|
||
[`06-state-v2-and-restart`](../../.scratch/feature-data-generator/issues/06-state-v2-and-restart.md)
|
||
(state v2 и правило рестарта построены на настенных часах).
|
||
|
||
> Обновление (2026-06-14): направление «стартовая история стенда» запущено в
|
||
> [ADR-0006](./0006-generation-as-sole-analytics-source.md); реконсиляция
|
||
> разделов «Персистентность» и «Воспроизводимость» мат-спеки выполнена в
|
||
> [спеке модельного времени](../specs/2026-06-14-generator-model-time-and-startup-history.md).
|
||
|
||
## Решение
|
||
|
||
Модельное время генератора **расцеплено** от настенных часов (`now()`).
|
||
Генератор потребляет метку такта **извне**; «драйвер часов» задаёт отображение
|
||
модельного времени на реальное. Шов под это уже существует в коде (такт
|
||
принимает метку времени параметром).
|
||
|
||
Три режима — это **драйверы поверх одного шва**, а не отдельные архитектуры:
|
||
|
||
- **живой ×1** — модельное время идёт со скоростью настенных часов (текущее
|
||
поведение);
|
||
- **живой ускоренный ×K** — модельное время идёт в `K` раз быстрее реального
|
||
(`K` — конфигурация, дефолт ×1);
|
||
- **заливка ×∞** — модельное время гонится без сна от стартовой точки до
|
||
целевой; частный случай `K → ∞`.
|
||
|
||
Живой стенд **масштабируется ×K, дефолт ×1**. Ускорение — учебная «ручка»
|
||
урока 7: разогнать стенд и вживую увидеть медленные явления (возвраты,
|
||
межсессионные паузы, сдвиг воронки после правки марковской таблицы переходов).
|
||
|
||
Метки событий пишутся по **модельному** времени. При ускорении/заливке «сейчас»
|
||
стенда расходится с настенными часами — для аналитического стенда (ключ —
|
||
`event_timestamp`, внеочередная вставка в ClickHouse нормальна) это **безвредно**;
|
||
фиксируем как явное свойство, чтобы «время стенда ≠ твои настенные часы» не
|
||
читалось как баг.
|
||
|
||
Имена параметров, формат конфигурации и алгоритм драйвера — за исполнителем; здесь
|
||
не фиксируются.
|
||
|
||
## Контекст
|
||
|
||
Текущая модель неявно держит инвариант **«время генератора ≡ реальное время ×1»**:
|
||
главный цикл спит до настенного такта, метки событий = `now()` + смещения, а
|
||
паузы внутри визита «проживаются» в реальном времени. Для учебного плана это
|
||
неудобно по двум причинам:
|
||
|
||
- **Медленные явления не успеть показать.** Возвраты (~2 ч межсессионной паузы),
|
||
накопление воронки, удержание разворачиваются часами и днями — в рамках урока
|
||
на ×1 их не увидеть.
|
||
- **Историческую глубину живой генератор не создаёт вовсе.** За реальную минуту
|
||
рождается ~λ событий реального времени; глубину сейчас даёт только статический
|
||
сид.
|
||
|
||
Дополнительно: воспроизводимость на ×1 завязана на настенный час (дневной
|
||
коэффициент, метки) — запуск в другой час даёт другой поток. Расцепление с
|
||
**фиксированной стартовой модельной точкой** эту зависимость снимает.
|
||
|
||
Связь с задачей 06: персистентность (state v2 + рестарт) уже построена на
|
||
настенных часах. Расцепление меняет смысл «правила 30 минут» — см. «Последствия».
|
||
Эта ADR — **отдельный слой поверх 06**; она не правит то, что задачи 06/07 уже
|
||
зафиксировали (их доводила автономная петля субагентов на реал-тайм-модели),
|
||
реконсиляция идёт отдельным заходом.
|
||
|
||
## Рассмотренные варианты
|
||
|
||
- **Оставить ×1 (статус-кво).** Отклонено: не решает учебное неудобство;
|
||
историческую глубину даёт только сид, медленные явления вживую не показать.
|
||
- **Живой всегда ×1, «быстро» только как разовая заливка прошлого.**
|
||
Жизнеспособно, но слабее: урок 7 не может вживую ускорить наблюдение, и это два
|
||
разных механизма (живой стенд и батч-заливка) вместо одного.
|
||
- **Живой масштабируется ×K, дефолт ×1; заливка = `K → ∞` (принято).** Один
|
||
механизм поглощает все режимы; ускорение прямо служит уроку 7; дефолт ×1
|
||
сохраняет «дыхание» настоящего сайта; пре-заливка прошлого тоже доступна
|
||
(бакаешь на огромном `K`, живёшь на умеренном). Цена — метки уходят вперёд
|
||
настенных часов при разгоне (безвредно для аналитики).
|
||
|
||
## Последствия
|
||
|
||
- Режимы живой / ускоренный / заливка — конфигурация одного драйвера часов;
|
||
реализация и имена параметров — за исполнителем.
|
||
- **Правило 30 минут (задача 06) переопределяется в модельном времени.** Формула
|
||
не меняется: разрыв между модельной меткой следующего события визита и
|
||
**модельным временем возобновления** больше 30 минут → визит закрывается;
|
||
меньше → продолжается. Меняется **origin возобновления** — он становится
|
||
параметром:
|
||
- **восстановление после краха** — origin = настенное `now()` (время реально
|
||
прошло) → поведение как сегодня;
|
||
- **продолжение из заморозки/сида** — origin = метка снимка → разрыв ≈ 0, мир
|
||
продолжается непрерывно, активные визиты не закрываются.
|
||
|
||
Текущий код 06 берёт `now()` жёстко: корректно для краха, требует
|
||
параметризации для продолжения.
|
||
- **Будущее направление (ещё не строим): «стартовая история стенда».** Заливка
|
||
`K → ∞` + заморозка state v2 даёт сгенерированное прошлое, с которого живой
|
||
стенд стартует **непрерывно** (те же люди возвращаются — `users < sessions`
|
||
рождается вживую, без костыля статического сида `users == sessions`). При этом
|
||
роль статического сида со временем смещается с «учебный датасет» на
|
||
«dev-фикстура и калибровочный эталон» (термины — в `CONTEXT.md`). **На время
|
||
разработки** сид и поток **сосуществуют** (как зафиксировано в ADR-0004);
|
||
продолжение-из-сида — отдельная будущая задача после готовности генератора.
|
||
- Воспроизводимость улучшается при фиксированной стартовой модельной точке;
|
||
реконсиляция разделов «Персистентность» и «Воспроизводимость» мат-спеки —
|
||
отдельный заход после ревью эксперимента с автономной петлёй.
|