docs(generator): зафиксировано направление переработки генератора
- Зачем:
- при возврате к генератору не переоткрывать выбор «генератор vs реплей»
и иметь готовую рамку требований под реализацию steady-stream.
- Что:
- ADR-0004: steady-stream питается синтетическим иерархическим генератором,
не реплеем сида (обоснование + отклонённые варианты C/B).
- спека docs/specs/2026-06-09: требования к иерархической модели сущностей
и критерии приёмки; математика делегирована follow-up-спеке.
- CONTEXT.md: термины «популяция пользователей», «возвращающийся пользователь».
- Проверка:
- прочитать ADR-0004 и спеку; сверить термины в CONTEXT.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
dd2c3cdaf9
commit
6ddc7a90d4
+19
@@ -44,6 +44,25 @@ user_domain_id (пользователь, постоянный)
|
|||||||
|
|
||||||
Здоровые данные дают пирамиду `users ≤ sessions ≤ events`.
|
Здоровые данные дают пирамиду `users ≤ sessions ≤ events`.
|
||||||
|
|
||||||
|
## Термины потоковой генерации (steady-stream)
|
||||||
|
|
||||||
|
Относятся к синтетическому потоку из генератора (режим `steady-stream`), не к
|
||||||
|
статическому сиду. Решение и контекст — [ADR-0004](./docs/adr/0004-steady-stream-synthetic-generator.md),
|
||||||
|
форма доработки — [спека генератора](./docs/specs/2026-06-09-generator-rework-hierarchical.md).
|
||||||
|
|
||||||
|
### Популяция пользователей (user population)
|
||||||
|
|
||||||
|
Множество пользователей с **постоянными** `user_domain_id`, которые генератор
|
||||||
|
держит во времени и из которых разыгрывает активность. В отличие от сида (где
|
||||||
|
пул `user_domain_id` плоский и 1:1 с визитами), популяция — это источник
|
||||||
|
возвратов: один и тот же пользователь порождает несколько визитов.
|
||||||
|
|
||||||
|
### Возвращающийся пользователь (returning user)
|
||||||
|
|
||||||
|
Пользователь, открывающий **более одного** визита (`click_id`) во времени, с
|
||||||
|
межсессионными паузами. Именно возвраты дают расхождение `users < sessions` —
|
||||||
|
то, чего нет на сиде (`users == sessions`) и что отличает поток от статики.
|
||||||
|
|
||||||
## Почему на демо `Unique Users == Unique Sessions`
|
## Почему на демо `Unique Users == Unique Sessions`
|
||||||
|
|
||||||
В демо-датасете (`data/*.jsonl`) каждый пользователь имеет **ровно один**
|
В демо-датасете (`data/*.jsonl`) каждый пользователь имеет **ровно один**
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# ADR-0004: Steady-stream источник — синтетический иерархический генератор, не реплей
|
||||||
|
|
||||||
|
Принято: 2026-06-09
|
||||||
|
Статус: accepted
|
||||||
|
Связано: [`generator/KNOWN_ISSUES.md`](../../generator/KNOWN_ISSUES.md) (диагноз
|
||||||
|
дефекта), [`CONTEXT.md`](../../CONTEXT.md),
|
||||||
|
[ADR-0002](./0002-specs-as-durable-design-docs.md) (спеки как durable design-доки),
|
||||||
|
спека [`docs/specs/2026-06-09-generator-rework-hierarchical.md`](../specs/2026-06-09-generator-rework-hierarchical.md)
|
||||||
|
(форма доработки).
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
Режим `steady-stream` (живой поток на стенде) питается **синтетическим
|
||||||
|
генератором, переписанным с нуля по иерархической модели** «популяция
|
||||||
|
пользователей → сессии → события», а **не реплеем статического сида**. Модель
|
||||||
|
интенсивности текущего генератора (Poisson по тикам + дневной коэффициент +
|
||||||
|
jitter) сохраняется. Режим `bootstrap` (статический сид `data/*.jsonl`) и уроки
|
||||||
|
0–6 остаются прежними — генератор **сосуществует** с сидом, не заменяет его.
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Проект сменил назначение на учебный стенд с курсом. Базовый курс стоит на
|
||||||
|
статическом сиде, и это осознанно: сид **честен внутри визита** (`click_id`
|
||||||
|
группирует 1..7 событий — настоящая воронка). Но у сида два потолка, которые
|
||||||
|
сид принципиально не закрывает:
|
||||||
|
|
||||||
|
- **`users == sessions`** — каждый пользователь имеет ровно один `click_id` (1:1),
|
||||||
|
возвращающихся пользователей нет (см. `CONTEXT.md`).
|
||||||
|
- **одноразовость** — сид заливается батчем; не видно, как ClickHouse и витрины
|
||||||
|
ведут себя на непрерывном живом потоке.
|
||||||
|
|
||||||
|
Нужен режим, в котором стенд **живёт и движется** на правдоподобных данных
|
||||||
|
(воронка + жизнеподобные колебания интенсивности), при **скромном объёме** (у
|
||||||
|
менти может не быть мощного железа — на объём/стресс не закладываемся).
|
||||||
|
|
||||||
|
Двойная учебная ценность — ключевой критерий выбора: ценны не только *данные*
|
||||||
|
(живой стенд + честная пирамида `users < sessions < events`), но и **сам
|
||||||
|
генератор как объект изучения** — его генеративная модель достойна того, чтобы её
|
||||||
|
разбирать в курсе.
|
||||||
|
|
||||||
|
Текущая реализация генератора не дорабатывается инкрементально: у неё
|
||||||
|
концептуальный дефект модели сущностей (свежий `click_id` на каждое событие
|
||||||
|
схлопывает иерархию — см. `generator/KNOWN_ISSUES.md`), переписываем с нуля.
|
||||||
|
|
||||||
|
## Рассмотренные варианты
|
||||||
|
|
||||||
|
- **A — синтетический иерархический генератор (принято).** Популяция юзеров с
|
||||||
|
постоянным `user_domain_id` → 1..N сессий (`click_id` на сессию) → 1..M
|
||||||
|
упорядоченных по времени событий с правдоподобной воронкой. Единственный
|
||||||
|
вариант, дающий возвращающихся пользователей (`users < sessions < events`) и
|
||||||
|
учебную ценность самого моделирования. Цена — самый большой объём работы и
|
||||||
|
риск ошибиться в статистической модели.
|
||||||
|
- **C — реплей честного сида на часах.** Лить реальные записи сида в Kafka во
|
||||||
|
времени, переписывая `event_timestamp` в «сейчас», зацикливая пул и модулируя
|
||||||
|
rate. **Отклонено.** Дёшев и даёт гарантированно честную воронку почти без
|
||||||
|
риска, **но**: (1) обходит ровно ту генеративно-модельную часть, ради учебной
|
||||||
|
ценности которой всё и затевается; (2) наследует вырождение `users == sessions`
|
||||||
|
из сида — полную пирамиду не даёт никогда; (3) «бесконечность» = зацикленный
|
||||||
|
конечный пул.
|
||||||
|
- **B — минимальный «живой» генератор с грубой воронкой.** Тот же rewrite, но
|
||||||
|
воронка на фиксированных вероятностях, без глубины. Отклонено как
|
||||||
|
половинчатое: числа воронки менее убедительны, а вопрос возвратов всё равно
|
||||||
|
надо решать — то есть основной сложности не избегает.
|
||||||
|
|
||||||
|
## Последствия
|
||||||
|
|
||||||
|
- Режим `steady-stream` фиксируется как синтетическая генерация; направление
|
||||||
|
переоткрывать не нужно (типовой вопрос «почему не реплей?» закрыт здесь).
|
||||||
|
- `bootstrap`-сид и уроки 0–6 не трогаем; живой поток подаётся отдельным уроком 7.
|
||||||
|
- Контракт данных потока: здоровая пирамида `users < sessions < events`,
|
||||||
|
монотонное время внутри сессии, `click_id` переиспользуется внутри сессии.
|
||||||
|
Этот контракт наследуют будущие артефакты (спека доработки, урок 7, возможные
|
||||||
|
правки витрин).
|
||||||
|
- Детальная архитектура/требования — в спеке `2026-06-09-generator-rework-hierarchical.md`.
|
||||||
|
- **Статистическая модель** (распределения событий/сессия, сессий/пользователь,
|
||||||
|
межсессионные паузы; нужен ли настоящий session-timeout; персистентность
|
||||||
|
популяции через рестарты) **выносится в отдельную follow-up-спеку** и здесь
|
||||||
|
намеренно не фиксируется.
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
# Переработка генератора: иерархическая модель сущностей для steady-stream
|
||||||
|
|
||||||
|
Дата: 2026-06-09
|
||||||
|
Статус: Draft
|
||||||
|
Связано: [ADR-0004](../adr/0004-steady-stream-synthetic-generator.md) (решение и
|
||||||
|
почему), [`generator/KNOWN_ISSUES.md`](../../generator/KNOWN_ISSUES.md) (диагноз
|
||||||
|
дефекта), [`CONTEXT.md`](../../CONTEXT.md) (доменный язык),
|
||||||
|
[ADR-0002](../adr/0002-specs-as-durable-design-docs.md)
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Курс на статическом сиде показывает только маленький одноразовый батч и не может
|
||||||
|
показать, как стенд **живёт и движется**: непрерывный поток через пайплайн,
|
||||||
|
шевелящиеся дашборды, поведение ClickHouse и витрин на живых данных. Текущий
|
||||||
|
генератор источником быть не может — у него концептуальный дефект модели
|
||||||
|
сущностей (свежий `click_id` на каждое событие схлопывает иерархию
|
||||||
|
`пользователь → сессия → событие`, см. `KNOWN_ISSUES.md`). Нужен переписанный
|
||||||
|
с нуля генератор, дающий правдоподобный живой поток.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- **Решение направления уже принято** в [ADR-0004](../adr/0004-steady-stream-synthetic-generator.md):
|
||||||
|
синтетический иерархический генератор, не реплей сида. Эта спека — про *форму*
|
||||||
|
доработки, не про выбор «генератор vs реплей».
|
||||||
|
- **Двойная учебная ценность** (критерий ADR-0004): ценны и данные (живой стенд +
|
||||||
|
честная пирамида), и сам генератор как объект изучения. Дизайн должен быть
|
||||||
|
пригоден для разбора в курсе, а не только «работать».
|
||||||
|
- **Доменный язык** зафиксирован в `CONTEXT.md`: пользователь = `user_domain_id`,
|
||||||
|
визит/сессия = `click_id`, событие = `event_id`; здоровые данные дают пирамиду
|
||||||
|
`users ≤ sessions ≤ events`. Спека добавляет термины «популяция пользователей»,
|
||||||
|
«возвращающийся пользователь».
|
||||||
|
- **Воронка в данных живёт в `page_url_path`, не в `event_type`.** В сиде
|
||||||
|
`event_type` = 100% `pageview` (других типов нет); воронка реализована
|
||||||
|
страницами: `/home → /product_a + /product_b → /cart → /payment →
|
||||||
|
/confirmation` (терминальный шаг — `/confirmation`). Дашборд так и устроен
|
||||||
|
(Page Funnel, конверсия `/confirmation ÷ /home`). Схема `event_type`
|
||||||
|
*допускает* и другие значения (click/purchase/add_to_cart), но в данных их
|
||||||
|
нет. `user_domain_id` — на уровне `click` (device_events), device/geo —
|
||||||
|
сессионный контекст. См. `sql/ddl/dds/30_dds.sql`, `CONTEXT.md`.
|
||||||
|
- **Что сохраняем без изменений:** модель интенсивности
|
||||||
|
(`_calculate_events_count()` — Poisson + `_hour_factor()` + jitter) —
|
||||||
|
`KNOWN_ISSUES.md` подтверждает её корректность.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Поток порождает **здоровую пирамиду** `users < sessions < events`: популяция
|
||||||
|
возвращающихся пользователей, у пользователя 1..N сессий, у сессии 1..M событий.
|
||||||
|
- `click_id` **переиспользуется внутри сессии** (envelope из нескольких событий с
|
||||||
|
общим device/geo), а не штампуется на каждое событие.
|
||||||
|
- **Время событий внутри сессии монотонно растёт** (правдоподобный путь по
|
||||||
|
страницам), а не равно `now()` для всего батча.
|
||||||
|
- **Правдоподобная воронка** по `page_url_path`: затухающая последовательность
|
||||||
|
страниц (`/home → … → /confirmation`), где до терминального шага доходит
|
||||||
|
правдоподобно малая доля — согласованно с дашбордом и сидом.
|
||||||
|
- Сохранена модель интенсивности (жизнеподобные колебания нагрузки).
|
||||||
|
- Встраивается в курс как **урок 7 «Система живёт»**, сосуществует с bootstrap-сидом
|
||||||
|
и уроками 0–6.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- **Не закладываемся на объём/стресс.** Скромный rate, рассчитанный на слабое
|
||||||
|
железо менти; цель — «видно жизнь и движение», а не нагрузочное тестирование.
|
||||||
|
- **Не выбираем здесь распределения и прочую математику** — конкретные модели
|
||||||
|
(события/сессия, сессии/пользователь, межсессионные паузы, нужен ли настоящий
|
||||||
|
session-timeout, персистентность популяции через рестарты) выносятся в
|
||||||
|
**отдельную follow-up-спеку**.
|
||||||
|
- **Не трогаем** статический сид как bootstrap, базовые уроки 0–6, витрины и
|
||||||
|
дашборд (строятся на сиде).
|
||||||
|
- **Не дорабатываем текущую реализацию инкрементально** — переписываем с нуля
|
||||||
|
(см. ADR-0004 и `KNOWN_ISSUES.md`).
|
||||||
|
- **Не пишем сам урок 7** в рамках этой спеки — это материал по `LESSON_STANDARD`
|
||||||
|
на этапе реализации.
|
||||||
|
|
||||||
|
## Current state
|
||||||
|
|
||||||
|
`generator/generator.py`, `generate_batch()` — плоская выборка на каждое событие:
|
||||||
|
свежий `click_id`, атрибуты копируются из случайной сид-строки, время ≈ `now()`
|
||||||
|
для всего батча. Итог на потоке: `Sessions == Events` (сессия = одно событие),
|
||||||
|
`user_domain_id` переиспользуется из сид-пула (~99) без привязки к сессиям.
|
||||||
|
Полный разбор и таблица «сид vs поток» — в `generator/KNOWN_ISSUES.md`.
|
||||||
|
|
||||||
|
Корректная часть, которую сохраняем: `_calculate_events_count()`
|
||||||
|
(Poisson + дневной коэффициент + jitter).
|
||||||
|
|
||||||
|
## Recommended approach
|
||||||
|
|
||||||
|
Иерархическая генерация вместо плоской выборки. На уровне требований (без выбора
|
||||||
|
конкретных распределений — это follow-up-спека):
|
||||||
|
|
||||||
|
1. **Популяция пользователей.** Поддерживать множество пользователей с
|
||||||
|
*постоянными* `user_domain_id`. Пользователь может **возвращаться**
|
||||||
|
несколькими сессиями во времени. (Насколько device/geo стабильны между
|
||||||
|
сессиями одного пользователя — моделирующее допущение; по схеме device/geo —
|
||||||
|
сессионный контекст уровня `click`. Степень стабильности — в follow-up-спеке.)
|
||||||
|
2. **Сессии пользователя.** Пользователь со временем открывает 1..N сессий; каждая
|
||||||
|
сессия — новый `click_id`, общий для всех её событий, с общим device/geo.
|
||||||
|
Межсессионные паузы — модель «вернувшегося пользователя».
|
||||||
|
3. **События сессии.** Сессия порождает 1..M событий, разделяющих `click_id`,
|
||||||
|
**упорядоченных по монотонно растущему времени**, образующих правдоподобный
|
||||||
|
путь по воронке страниц (`page_url_path`: `/home → … → /confirmation`), где до
|
||||||
|
терминального шага доходит малая доля. (Вводить ли разнообразие `event_type`
|
||||||
|
сверх `pageview` — открытый вопрос, см. ниже.)
|
||||||
|
4. **Интенсивность.** Поверх иерархии работает сохранённая модель интенсивности —
|
||||||
|
сколько событий и когда (Poisson + дневной коэффициент + jitter).
|
||||||
|
|
||||||
|
Связь с тиковой моделью генератора (как «развернуть» иерархию во времени по тикам,
|
||||||
|
а не одним батчем) — открытый вопрос дизайна, см. ниже.
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
|
||||||
|
- Иерархия — единственный способ получить возвраты и честную пирамиду
|
||||||
|
(обоснование выбора против реплея — в ADR-0004).
|
||||||
|
- Монотонное время и `click_id`-на-сессию делают воронку и сессии настоящими
|
||||||
|
учебными объектами, а не артефактами.
|
||||||
|
- Сохранение модели интенсивности минимизирует объём rewrite и риск: переписываем
|
||||||
|
только модель сущностей, корректную математику интенсивности не трогаем.
|
||||||
|
|
||||||
|
## Risks and mitigations
|
||||||
|
|
||||||
|
- **Стыковка иерархии с тиковой/потоковой моделью.** Иерархия естественно
|
||||||
|
«батчевая» (сессия = последовательность), а генератор тиковый и стремится к
|
||||||
|
`now()`. Риск снова получить время ≈ `now()` или порвать сессии между тиками.
|
||||||
|
Митигейшн: в follow-up-спеке явно спроектировать, как сессия раскладывается во
|
||||||
|
времени по тикам (например, активные сессии как состояние генератора).
|
||||||
|
- **Правдоподобие против воспроизводимости.** `GEN_SEED` должен давать
|
||||||
|
воспроизводимый поток и при иерархической модели.
|
||||||
|
- **Персистентность популяции через рестарты.** Текущий state-в-Kafka хранит tick
|
||||||
|
и RNG; популяция пользователей/активные сессии — новый вид состояния. Решается
|
||||||
|
в follow-up-спеке (возможно, расширением `generator_state`).
|
||||||
|
- **Скромное железо.** Держать rate низким по умолчанию; иерархия не должна
|
||||||
|
раздувать память (ограничивать размер активной популяции/сессий).
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Критерии приёмки переработанного генератора (на потоке, не на сиде):
|
||||||
|
|
||||||
|
- `uniqExact(user_domain_id) < uniqExact(click_id) < count(event_id)` —
|
||||||
|
пирамида не вырождена (есть возвраты и мультисобытийные сессии).
|
||||||
|
- Внутри одного `click_id` — несколько событий с **возрастающим** `event_ts`.
|
||||||
|
- Один `click_id` принадлежит ровно одному `user_domain_id`; у пользователя
|
||||||
|
бывает несколько `click_id`.
|
||||||
|
- Воронка по `page_url_path` затухает (доля доходящих до `/confirmation`
|
||||||
|
правдоподобно мала), терминальный шаг — `/confirmation`.
|
||||||
|
- Поток крутится на слабом железе при дефолтном rate без деградации стенда.
|
||||||
|
- `GEN_SEED` даёт воспроизводимый поток.
|
||||||
|
|
||||||
|
## Documentation impact
|
||||||
|
|
||||||
|
- `generator/README.md` и `generator/KNOWN_ISSUES.md` — обновить **в момент
|
||||||
|
реализации**: снять предупреждение о дефекте, описать новую модель.
|
||||||
|
- `CONTEXT.md` — добавить «популяция пользователей» / «возвращающийся
|
||||||
|
пользователь» (в этой итерации брейнсторма уже вносится).
|
||||||
|
- `docs/course/` — добавить урок 7 «Система живёт» по `LESSON_STANDARD` (на этапе
|
||||||
|
реализации); README курса — строку в таблицу уроков.
|
||||||
|
- `README.md` (корень) — режим `steady-stream` уже описан; синхронизировать при
|
||||||
|
реализации.
|
||||||
|
|
||||||
|
## Open questions (→ follow-up-спека по математике)
|
||||||
|
|
||||||
|
- Конкретные распределения: события на сессию, сессии на пользователя за период,
|
||||||
|
межсессионные интервалы.
|
||||||
|
- **Вводить ли разнообразие `event_type`** (purchase/add_to_cart/click) сверх
|
||||||
|
`pageview`. Сейчас и сид, и дашборд исходят из 100% `pageview` (воронка —
|
||||||
|
страничная); добавление типов оживило бы выкинутые из дашборда колонки
|
||||||
|
purchases/add_to_cart и требует согласованной правки урока 6/дашборда. То есть
|
||||||
|
это не локальное решение генератора — взвесить в follow-up-спеке.
|
||||||
|
- Вводить ли настоящий **session-timeout** (окно неактивности как граница сессии)
|
||||||
|
— сейчас `CONTEXT.md` фиксирует `сессия ≡ визит ≡ click_id` без тайм-аута.
|
||||||
|
- Как иерархия раскладывается во времени по тикам генератора (модель активных
|
||||||
|
сессий как состояние).
|
||||||
|
- Персистентность популяции пользователей и активных сессий через рестарты
|
||||||
|
(расширение `generator_state`).
|
||||||
|
- Воспроизводимость потока при иерархической модели и `GEN_SEED`.
|
||||||
Reference in New Issue
Block a user