Files
clickstream-ch-kafka-supers…/docs/specs/2026-06-09-generator-rework-hierarchical.md
T
Dmitry DementievandClaude Fable 5 6efa031023 docs(generator): добавлена мат-спека модели и исправлен профиль сида
- Зачем:
  - закрыть Open questions спеки формы доработки перед передачей на
    реализацию; адверсариальное ревью показало, что прежние ориентиры
    (популяция/паузы/интенсивность) взаимно несовместимы, а документы
    опираются на неверный факт о сиде («1..7 событий на визит»).
- Что:
  - добавлена docs/specs/2026-06-10-generator-math-model.md: марковская
    цепочка по страницам, формула связи «популяция-интенсивность-пауза»
    (λ по умолчанию 30/мин), кулдаун возврата, правило 30 минут на рестарт,
    критерии приёмки.
  - в CONTEXT.md добавлен профиль сид-датасета (полный замер: длины визитов
    1..27, медиана 10, конверсия 25%, петли и события после /confirmation)
    и исправлено ложное «разброс времени внутри click_id <= 1 мин».
  - исправлен факт «1..7 событий» в KNOWN_ISSUES.md и ADR-0004; критерии
    Validation спеки формы доработки приведены к фактам сида.
- Проверка:
  - перекрёстные ссылки между спеками/ADR/CONTEXT.md открываются; цифры
    профиля сида воспроизводятся скриптом подсчёта по полным data/*.jsonl.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:32:47 +03:00

185 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Переработка генератора: иерархическая модель сущностей для 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`), где число визитов затухает от шага к шагу,
а до последнего шага воронки доходит меньшинство (в сиде ~25%) —
согласованно с дашбордом и сидом.
- Сохранена модель интенсивности (жизнеподобные колебания нагрузки).
- Встраивается в курс как **урок 7 «Система живёт»**, сосуществует с bootstrap-сидом
и уроками 06.
## 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`), где до
последнего шага воронки доходит меньшинство визитов (~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_ts` строго **возрастает**;
мультисобытийные визиты составляют заметную долю потока (одностраничные
визиты-отказы допустимы и правдоподобны).
- Один `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:** все вопросы ниже решены в
> [спеке математической модели](./2026-06-10-generator-math-model.md)
> (распределения, ротация популяции, инвариант пауз вместо 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`.