Files
clickstream-ch-kafka-supers…/docs/specs/2026-06-09-generator-rework-hierarchical.md
T
Dmitry DementievandClaude Opus 4.8 6ddc7a90d4 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>
2026-06-09 18:04:00 +03:00

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