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

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