- Зачем:
- при возврате к генератору не переоткрывать выбор «генератор 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>
15 KiB
Переработка генератора: иерархическая модель сущностей для steady-stream
Дата: 2026-06-09
Статус: Draft
Связано: ADR-0004 (решение и
почему), generator/KNOWN_ISSUES.md (диагноз
дефекта), CONTEXT.md (доменный язык),
ADR-0002
Problem
Курс на статическом сиде показывает только маленький одноразовый батч и не может
показать, как стенд живёт и движется: непрерывный поток через пайплайн,
шевелящиеся дашборды, поведение ClickHouse и витрин на живых данных. Текущий
генератор источником быть не может — у него концептуальный дефект модели
сущностей (свежий click_id на каждое событие схлопывает иерархию
пользователь → сессия → событие, см. KNOWN_ISSUES.md). Нужен переписанный
с нуля генератор, дающий правдоподобный живой поток.
Context
- Решение направления уже принято в ADR-0004: синтетический иерархический генератор, не реплей сида. Эта спека — про форму доработки, не про выбор «генератор 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-спека):
- Популяция пользователей. Поддерживать множество пользователей с
постоянными
user_domain_id. Пользователь может возвращаться несколькими сессиями во времени. (Насколько device/geo стабильны между сессиями одного пользователя — моделирующее допущение; по схеме device/geo — сессионный контекст уровняclick. Степень стабильности — в follow-up-спеке.) - Сессии пользователя. Пользователь со временем открывает 1..N сессий; каждая
сессия — новый
click_id, общий для всех её событий, с общим device/geo. Межсессионные паузы — модель «вернувшегося пользователя». - События сессии. Сессия порождает 1..M событий, разделяющих
click_id, упорядоченных по монотонно растущему времени, образующих правдоподобный путь по воронке страниц (page_url_path:/home → … → /confirmation), где до терминального шага доходит малая доля. (Вводить ли разнообразиеevent_typeсверхpageview— открытый вопрос, см. ниже.) - Интенсивность. Поверх иерархии работает сохранённая модель интенсивности — сколько событий и когда (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.