From 49512b190a34eb7a5953419c35dadf301d9f41a8 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 14 Jun 2026 14:47:01 +0300 Subject: [PATCH] =?UTF-8?q?docs(generator):=20=D0=B7=D0=B0=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D1=81=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=20=D0=B0=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D1=82=D0=B8=D0=BA=D0=B8=20=D0=B8=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D1=82=D0=BE=D0=B2=D0=B0=D1=8F=20=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - генератор даёт здоровую пирамиду, и стенду нужен единый источник аналитики вместо вырожденного статического сида. - Что: - добавлен ADR-0006: генерация — единственный источник аналитики, статический сид становится архивным (кладовка значений до синтеза фактуры). - добавлена спека модельного времени: точка отсчёта, заливка прошлого, стартовая история, сохранение состояния, воспроизводимость и проверка в два шага. - в ADR-0004 и ADR-0005 добавлены указатели вперёд на ADR-0006 и спеку. - Проверка: - чтение документов; перекрёстные ссылки между ADR-0004/0005/0006 и спекой согласованы. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../0004-steady-stream-synthetic-generator.md | 6 + docs/adr/0005-generator-model-clock.md | 5 + ...006-generation-as-sole-analytics-source.md | 112 ++++++++++ ...enerator-model-time-and-startup-history.md | 199 ++++++++++++++++++ 4 files changed, 322 insertions(+) create mode 100644 docs/adr/0006-generation-as-sole-analytics-source.md create mode 100644 docs/specs/2026-06-14-generator-model-time-and-startup-history.md diff --git a/docs/adr/0004-steady-stream-synthetic-generator.md b/docs/adr/0004-steady-stream-synthetic-generator.md index 7f496b3..ca0ccbf 100644 --- a/docs/adr/0004-steady-stream-synthetic-generator.md +++ b/docs/adr/0004-steady-stream-synthetic-generator.md @@ -8,6 +8,12 @@ спека [`docs/specs/2026-06-09-generator-rework-hierarchical.md`](../specs/2026-06-09-generator-rework-hierarchical.md) (форма доработки). +> Обновление (2026-06-14): тезис «генератор сосуществует с сидом, не заменяет +> его» частично пересмотрен в [ADR-0006](./0006-generation-as-sole-analytics-source.md) +> — для аналитического контура стенда генерация заменяет статический сид; сид +> выводится из оборота (целевым образом — полностью). Сосуществование остаётся +> верным лишь для переходной роли сида (палитра атрибутов / dev-фикстура). + ## Решение Режим `steady-stream` (живой поток на стенде) питается **синтетическим diff --git a/docs/adr/0005-generator-model-clock.md b/docs/adr/0005-generator-model-clock.md index 21d736d..c945261 100644 --- a/docs/adr/0005-generator-model-clock.md +++ b/docs/adr/0005-generator-model-clock.md @@ -11,6 +11,11 @@ steady-stream как синтетический генератор), [`CONTEXT.m [`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()`). diff --git a/docs/adr/0006-generation-as-sole-analytics-source.md b/docs/adr/0006-generation-as-sole-analytics-source.md new file mode 100644 index 0000000..374e935 --- /dev/null +++ b/docs/adr/0006-generation-as-sole-analytics-source.md @@ -0,0 +1,112 @@ +# ADR-0006: Единственный источник аналитики — генерация; статический сид становится архивным + +Принято: 2026-06-14 +Статус: accepted +Связано: [ADR-0004](./0004-steady-stream-synthetic-generator.md) (**частично +пересматривает** — тезис о сосуществовании сида и потока), +[ADR-0005](./0005-generator-model-clock.md) (**запускает** направление +«стартовая история стенда»), мат-спека +[`docs/specs/2026-06-10-generator-math-model.md`](../specs/2026-06-10-generator-math-model.md) +(модель распределений), спека механизма +[`docs/specs/2026-06-14-generator-model-time-and-startup-history.md`](../specs/2026-06-14-generator-model-time-and-startup-history.md) +(как это устроено), [`CONTEXT.md`](../../CONTEXT.md) (три значения слова «сид»), +[`generator/KNOWN_ISSUES.md`](../../generator/KNOWN_ISSUES.md). + +## Решение + +Аналитический контур стенда (Kafka → STG→ODS→DDS→DM → Superset) питается **только +генерацией**: стартовой историей (готовое сгенерированное прошлое) при создании +стенда и живым потоком далее. Статический сид `data/*.jsonl` **выводится из этого +процесса и становится архивным** — он больше не грузится в Kafka и не строит +витрины. Перевод процесса на новый, сгенерированный сид — **цель этой работы**, а +не отложенный шаг. + +У старого сида остаётся одна временная роль — **кладовка готовых значений** для +генератора (браузеры, страны, устройства, метки кампаний): генератор берёт оттуда +«фактуру», чтобы одевать ею события, и так же делает сам новый сид. Поэтому файл +пока остаётся в репозитории, хотя из рабочего процесса уже вышел. + +- **Что делаем в этой работе:** новый сид становится источником аналитики; процесс + (загрузка, витрины, дашборды, уроки) перестраивается на него; старый сид выходит + из процесса в архив. +- **Что остаётся на отдельный шаг:** научить генератор придумывать фактуру + самостоятельно. После этого старый файл можно удалить совсем — последняя + зависимость от него исчезнет. +- **Целевое состояние:** самодостаточный генератор, старого сида в репозитории нет. + +Это решение перекрывает тезис ADR-0004 «генератор сосуществует с сидом, не +заменяет его»: для аналитики генерация сид заменяет. + +## Контекст + +ADR-0004 (2026-06-09) зафиксировал: «`bootstrap`-сид и уроки 0–6 не трогаем, +генератор сосуществует с сидом». На старте переработки это было верно. Две вещи +изменили посылку: генератор теперь даёт здоровую пирамиду `users < sessions < +events`, а ADR-0005 ввёл модельное время — с ним стартовая история (готовое +сгенерированное прошлое) становится несущей: именно она даёт стенду историческую +глубину с первой минуты. + +Почему старый сид перестаёт быть источником: + +- **Вырождение `users == sessions`** — у каждого пользователя ровно один визит; + это ровно то, от чего уходим (см. `CONTEXT.md`). +- **Неизвестное происхождение и качество** — это срез чужого учебного задания + (видно, что данные сделаны библиотекой-заполнителем: `dummywebsite.com`, + выдуманные почты, случайные локали). Считать его авторитетным образцом оснований + нет. +- **Одноразовость** — заливается одной пачкой, не показывает живой стенд во + времени. + +Скрытая зависимость, найденная при разборе кода: генератор **одевает события +значениями из сида**. Профиль пользователя хранится ссылкой на сид-сессию +(мат-спека, §«Профиль пользователя»), а `generate_batch` копирует поля сид-строк, +переопределяя лишь идентификаторы, время и путь страниц. Поэтому старый сид нельзя +убрать одним движением: сначала генератору нужна своя «фактура». Отсюда разделение +на два шага. + +Побочный факт: путь для «грязных» записей (`ods.*_errors`) сид **не наполняет** — +проверка показала, что все 1000 записей во всех четырёх файлах чисты, ни одна не +попадает в ошибки. Значит вывод сида из процесса не вредит уроку про грязные +данные; наоборот, генератор, умеющий **намеренно** подсыпать брак, научит этому +лучше идеально чистого среза. + +## Рассмотренные варианты + +- **A — сид остаётся источником, генерация сосуществует (текущее положение по + ADR-0004).** Отклонено: тащит вырождение `users == sessions` в аналитику и + держит два источника вместо одного. +- **B — генерация заменяет источник, но сид остаётся жёстким образцом для сверки** + (генератор обязан число-в-число повторять профиль сида). Отклонено как + направление: образцовость сида сомнительна, а подгонять генерацию под + вырожденный профиль — шаг назад от того, ради чего всё затевалось. +- **C — генерация единственный источник; старый сид становится архивным + (принято).** Свои намеренно спроектированные распределения вместо + унаследованных; своя «фактура» генератора — отдельным шагом, до него сид + временно остаётся кладовкой значений. Цена — переход в два шага и временно + сохранённая зависимость от сида. + +## Последствия + +- Аналитический контур переключается на генерацию; **стартовая история становится + несущей** — без неё свежий стенд стартует с пустым прошлым. Это связывает + решение со спекой механизма (модельное время + стартовая история). +- **Порядок шагов важен.** Старый сид выходит из процесса не раньше, чем + (1) готова стартовая история и (2) процесс — загрузка, витрины, дашборды, + уроки — переведён на новый сид. Удалить файл совсем можно только третьим шагом — + после того как генератор научится своей фактуре. Вынуть сид раньше — оставить + стенд без данных. +- **ADR-0004 частично пересмотрен**: «генератор сосуществует с сидом, не заменяет» + остаётся верным лишь для временной роли сида как кладовки значений; для аналитики + генерация сид заменяет. Ссылка вперёд добавлена в ADR-0004. +- **Глоссарий `CONTEXT.md`** (раздел «три значения слова сид») надо выровнять: + статический сид → архивная кладовка значений с целью полного вывода. Обновляется + отдельным шагом. +- **Перестройка процесса** — перевод загрузки (`kafka_load_dag`), витрин/Superset + и уроков на новый сид — входит в эту работу как её цель; детальный план этой + перестройки — отдельная спека на этапе реализации. +- **Полное удаление `data/*.jsonl`** — только после того, как генератор научится + своей фактуре (отдельная спека). +- **Учебная ценность растёт**: со своей фактурой проектируем интересные + распределения (перекос по странам, набор устройств, привязка кампаний, намеренный + брак) вместо случайного набора из чужого сида — но это уже шаг про фактуру, не + этот. diff --git a/docs/specs/2026-06-14-generator-model-time-and-startup-history.md b/docs/specs/2026-06-14-generator-model-time-and-startup-history.md new file mode 100644 index 0000000..852b8c7 --- /dev/null +++ b/docs/specs/2026-06-14-generator-model-time-and-startup-history.md @@ -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.