Files
clickstream-ch-kafka-supers…/docs/specs/2026-06-09-generator-rework-hierarchical.md
T
Dmitry DementievandClaude Fable 5 bcca8f5127 docs(generator): уточнена мат-спека по итогам ревью, handoff для передачи
- Зачем:
  - повторное адверсариальное ревью нашло ошибку в формуле межсессионной
    паузы (двойной счёт кулдауна) и незакрытый контракт публикации
    device/geo; дизайн-этап завершён, работа передаётся исполнителю.
- Что:
  - исправлена формула паузы (баланс цикла, минус длительность визита);
    зафиксированы каденция device/geo «на каждое событие, как в сиде»,
    правило выбора возвращающегося, стартовое распределение страниц,
    калибровка по медиане и среднему, инвариант потолков конфигурации,
    компактное хранение профиля ссылкой на сид-сессию.
  - уточнены критерии приёмки (среднее паузы вместо медианы, счёт шага
    «товары», имена полей времени) в обеих спеках.
  - добавлен handoff .scratch/handoffs/2026-06-10-generator-spec-to-codex.md
    (передача на детальный план/реализацию), отработавший handoff от
    2026-06-09 удалён.
- Проверка:
  - цифры спеки сверены замерами по полным data/*.jsonl (стартовые страницы
    58/24/17, шаги воронки 98>=94>=56>=35>=25, device 1000 строк / 99
    уникальных); повторное ревью свежим агентом блокеров не оставило.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 18:02:57 +03:00

16 KiB
Raw Blame History

Переработка генератора: иерархическая модель сущностей для 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), где число визитов затухает от шага к шагу, а до последнего шага воронки доходит меньшинство (в сиде ~25%) — согласованно с дашбордом и сидом.
  • Сохранена модель интенсивности (жизнеподобные колебания нагрузки).
  • Встраивается в курс как урок 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).

Иерархическая генерация вместо плоской выборки. На уровне требований (без выбора конкретных распределений — это 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), где до последнего шага воронки доходит меньшинство визитов (~25%, как в сиде). (Вводить ли разнообразие 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_timestamp в источнике, event_ts в DDS — проверять можно в любой точке); мультисобытийные визиты составляют заметную долю потока (одностраничные визиты-отказы допустимы и правдоподобны).
  • Один click_id принадлежит ровно одному user_domain_id; у пользователя бывает несколько click_id.
  • Воронка по page_url_path затухает от шага к шагу; доля визитов, доходящих до /confirmation, согласована с сидом (~25%). /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-спека по математике)

Закрыто 2026-06-10: все вопросы ниже решены в спеке математической модели (распределения, ротация популяции, инвариант пауз вместо session-timeout, активные сессии как состояние, персистентность, воспроизводимость; event_type остаётся 100% pageview). Список сохранён для истории.

  • Конкретные распределения: события на сессию, сессии на пользователя за период, межсессионные интервалы.
  • Вводить ли разнообразие 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.