docs(specs): спека генератора (этап 2) из решений карты #26 #35

Merged
ddmitry merged 6 commits from feat/32-generator-spec into main 2026-08-01 21:05:49 +03:00
4 changed files with 38 additions and 20 deletions
Showing only changes of commit 7df5e482b9 - Show all commits
+5 -1
View File
@@ -9,7 +9,7 @@
Аналитическая база стенда — кластер ClickHouse со слоями STG/ODS/DDS/DM. Аналитическая база стенда — кластер ClickHouse со слоями STG/ODS/DDS/DM.
Принимающая сторона границы «трекер | хранилище»: нормализует имена и стили Принимающая сторона границы «трекер | хранилище»: нормализует имена и стили
источников, строится по их документации. источников, строится по их документации.
_Избегать_: склад, склад данных _Избегать_: склад, склад данных, DWH
**Состав мира**: **Состав мира**:
Постоянная часть мира генератора — популяция посетителей с календарём их Постоянная часть мира генератора — популяция посетителей с календарём их
@@ -30,6 +30,10 @@ _Избегать_: состояние мира
Проигрывание текущего модельного дня в реальном времени с ускорением; Проигрывание текущего модельного дня в реальном времени с ускорением;
включается по требованию, не постоянный фон. включается по требованию, не постоянный фон.
**Пакетный режим**:
Проигрывание готового дня пачкой, без темпа: заливка снимка при старте
стенда, пересборки и проверки.
**Граница суток**: **Граница суток**:
Единственный структурный шов модели: сессии режутся по ней, слепок заказов Единственный структурный шов модели: сессии режутся по ней, слепок заказов
снимается на ней, день проживается только целиком. снимается на ней, день проживается только целиком.
@@ -19,7 +19,7 @@
| Случайные значения numpy (векторно) | ~10^8 значений/с | таблица numpy | | Случайные значения numpy (векторно) | ~10^8 значений/с | таблица numpy |
| Векторная сборка колонок события | ~10^6–10^7 строк/с | локальная проверка | | Векторная сборка колонок события | ~10^6–10^7 строк/с | локальная проверка |
| Посточная генерация (random + dict) | ~10^410^5 строк/с | локальная проверка | | Посточная генерация (random + dict) | ~10^410^5 строк/с | локальная проверка |
| Посточная генерация через Faker | ~10^310^4 строк/с | бенчмарк mimesis | | Посточная генерация через Faker | ~10^310^4 значений/с | бенчмарк mimesis |
| Сериализация orjson (запись ~50 КБ) | ~10^5 док/с | README orjson | | Сериализация orjson (запись ~50 КБ) | ~10^5 док/с | README orjson |
| Сериализация stdlib json (та же запись) | ~10^4 док/с | README orjson | | Сериализация stdlib json (та же запись) | ~10^4 док/с | README orjson |
+1 -1
View File
@@ -56,7 +56,7 @@ JSON-поле `ecommerce`. Сессий в потоке нет — их мент
- **Имена колонок — как в облачной выгрузке Метрики** (`ClientID`, - **Имена колонок — как в облачной выгрузке Метрики** (`ClientID`,
`UTCEventTime`, `purchaseID`…). Сырой слой хранит имена источника; свои `UTCEventTime`, `purchaseID`…). Сырой слой хранит имена источника; свои
snake_case-имена появляются в DDS/DM. Это учебный пункт: у каждого snake_case-имена появляются в DDS/DM. Это учебный пункт: у каждого
источника — свой стиль, нормализует его склад, а не трекер. источника — свой стиль, нормализует его хранилище, а не трекер.
- **Идентификаторы — числовые UInt64** (`WatchID`, `VisitID`, `ClientID`), - **Идентификаторы — числовые UInt64** (`WatchID`, `VisitID`, `ClientID`),
UUID уходят. Исследование советовало UUID не трогать, но тот совет исходил UUID уходят. Исследование советовало UUID не трогать, но тот совет исходил
из цены переделки текущего генератора; v2 пишет генератор заново, цена из цены переделки текущего генератора; v2 пишет генератор заново, цена
+31 -17
View File
@@ -20,7 +20,10 @@
Здесь не переоткрывается решённое мастер-спекой: модель данных события, Здесь не переоткрывается решённое мастер-спекой: модель данных события,
таксономия, анонимность, механика заказов (границы мандата #14). Генератор таксономия, анонимность, механика заказов (границы мандата #14). Генератор
слепков заказов — этап 3: эта спека лишь не должна ему мешать. слепков заказов — этап 3: эта спека лишь не должна ему мешать. Один
осознанный выход за границы: решение о хранении снимка (раздел 5) формально
касается этапа 7 — расширение подтверждено владельцем в резолюции
«Производительности».
## Целевая картина одним взглядом ## Целевая картина одним взглядом
@@ -48,15 +51,18 @@
плана состава, а не копятся в состоянии. Глобальные инварианты («каждый плана состава, а не копятся в состоянии. Глобальные инварианты («каждый
двухкуковый покупатель заказал с обеих кук») гарантируются планом — двухкуковый покупатель заказал с обеих кук») гарантируются планом —
счётчики манифеста известны до генерации событий. счётчики манифеста известны до генерации событий.
- **Состав не замкнут: посетители появляются и уходят.** План состава задаёт - **Состав не замкнут: посетители появляются и затухают.** План состава
календарь появления — у каждого посетителя есть дата первого визита и задаёт календарь появления — у каждого посетителя есть дата первого
профиль возвратов, заметная доля кук одноразовая, как в живом трафике. визита и профиль возвратов, включая затухание: заметная доля кук
Новые посетители появляются на всём протяжении оси: uniq(ClientID) растёт одноразовая, как в живом трафике. Новые посетители появляются на всём
с горизонтом, DAU и MAU не сходятся в одно число. Приток — часть плана, а протяжении оси: uniq(ClientID) растёт с горизонтом, дневная и накопленная
не мутация: счётчики манифеста по-прежнему известны до генерации аудитории не сходятся в одно число. Приток — часть плана, а не мутация:
(уточнение по вычитке владельца, 2026-08-01). счётчики манифеста по-прежнему известны до генерации (уточнение по
вычитке владельца, 2026-08-01); его числа — раздел 9.
- **Своя ось модельного времени.** Мир рождается в фиксированный день D0 - **Своя ось модельного времени.** Мир рождается в фиксированный день D0
(понедельник — см. раздел 5); реальный календарь в модели не участвует. (понедельник — см. раздел 5); реальный календарь в модели не участвует.
В `EventDate`/`UTCEventTime` дни оси ложатся конкретными датами, но это
константа мира, от даты запуска не зависящая (значение — раздел 9).
Между прогонами живут только зерно и позиция на оси: выключенный ноутбук — Между прогонами живут только зерно и позиция на оси: выключенный ноутбук —
мир замер, потом продолжил. мир замер, потом продолжил.
- **Два режима движения по одной оси.** Пошаговый — базовый для лаб: старт - **Два режима движения по одной оси.** Пошаговый — базовый для лаб: старт
@@ -89,13 +95,14 @@
- **Обещание — содержимое до байта.** Два прогона с одним зерном дают тот же - **Обещание — содержимое до байта.** Два прогона с одним зерном дают тот же
набор событий: те же `WatchID`/`VisitID`, поля, метки модельного времени. набор событий: те же `WatchID`/`VisitID`, поля, метки модельного времени.
Артефакт снимка при пересборке побайтово совпадает: канонический порядок Снимок при пересборке побайтово совпадает: канонический порядок ключей и
ключей и строк, пустой diff означает «ничего не изменилось». Вне строк; сверка — по хешам манифеста, а два локально пересобранных снимка
сравнимы обычным diff — пустой означает «ничего не изменилось». Вне
обещания — транспорт: офсеты и партиции Kafka, какая нода прочитала, обещания — транспорт: офсеты и партиции Kafka, какая нода прочитала,
`_ingested_at`, темп живого дня. `_ingested_at`, темп живого дня.
- **Условия обещания.** Детерминизм держится при зафиксированном `uv.lock` - **Условия обещания.** Детерминизм держится при зафиксированном `uv.lock`
и внутри канонического контейнера (везде Linux; переменная — только и внутри канонического контейнера — то есть везде Linux, на маке и в WSL
архитектура CPU). Истина — CI на Linux; сходимость любой машины проверяет тоже; единственная переменная — архитектура CPU. Истина — CI на Linux; сходимость любой машины проверяет
скрипт «пересгенерируй день N — сравни хеш с манифестом». Расхождение на скрипт «пересгенерируй день N — сравни хеш с манифестом». Расхождение на
любой платформе — баг генератора, а не допуск. любой платформе — баг генератора, а не допуск.
- **Раздача зерна — иерархией подпотоков.** Корневое зерно → состав мира; - **Раздача зерна — иерархией подпотоков.** Корневое зерно → состав мира;
@@ -103,7 +110,8 @@
торговые события, расхождения, опоздания — в фиксированном порядке. торговые события, расхождения, опоздания — в фиксированном порядке.
По построению: параллельный прогон равен последовательному; продление По построению: параллельный прогон равен последовательному; продление
истории днём N+1 не трогает дни 1…N; правка одного компонента меняет истории днём N+1 не трогает дни 1…N; правка одного компонента меняет
только его часть снимка — diff читаем. только его часть снимка — в манифесте меняются хеши только затронутых
дней, дифф двух локальных пересборок читаем.
- **Механизм подпотоков — `numpy.random.SeedSequence`.** Сверено через - **Механизм подпотоков — `numpy.random.SeedSequence`.** Сверено через
Context7 по документации numpy (2026-08-01): `spawn(n)` порождает детей Context7 по документации numpy (2026-08-01): `spawn(n)` порождает детей
расширением `spawn_key`, потомок полностью определяется парой расширением `spawn_key`, потомок полностью определяется парой
@@ -226,10 +234,12 @@
хранят. Манифест несёт паспорт мира, счётчики и хеши по дням; проверки хранят. Манифест несёт паспорт мира, счётчики и хеши по дням; проверки
«пустой git diff» и «пересгенерируй день N — сравни хеш» живут на нём. «пустой git diff» и «пересгенерируй день N — сравни хеш» живут на нём.
Каждый `make up` — живая демонстрация детерминизма. Честная потеря — Каждый `make up` — живая демонстрация детерминизма. Честная потеря —
страховка от платформенного бага; смягчение — CI гоняет генерацию на страховка на случай платформенного бага: раньше менти с расходящимися
amd64 и arm64. байтами мог взять готовый снимок из git, теперь он упрётся в красный чек
манифеста; смягчение — CI гоняет генерацию на amd64 и arm64.
- **Живой день — ×60 по умолчанию**: модельные сутки за 24 реальные минуты, - **Живой день — ×60 по умолчанию**: модельные сутки за 24 реальные минуты,
суточная волна разворачивается на глазах, темп ~35100 событий/с. Число — суточная волна разворачивается на глазах; темп в среднем ~35 событий/с,
в пиковые часы сильных дней — до ~100. Число —
значение по умолчанию, переопределяется флагом проигрывателя: ускорение — значение по умолчанию, переопределяется флагом проигрывателя: ускорение —
свойство транспорта, вне обещания воспроизводимости, константой мира не свойство транспорта, вне обещания воспроизводимости, константой мира не
делается. делается.
@@ -249,7 +259,7 @@
| Лаг живого дня | секунды | | Лаг живого дня | секунды |
**Автоматический порог один: полный день (50 тыс. событий) генерируется **Автоматический порог один: полный день (50 тыс. событий) генерируется
≤ 30 с.** Расчёт по планке исследования (~2–5×10⁵ событий/с на ядро) — около ≤ 30 с.** Расчёт по планке исследования (~2–5×10⁵ событий/с на ядро) — доли
секунды; порог держит машинный разброс ×2–5 и ловит деградацию на 1–2 секунды; порог держит машинный разброс ×2–5 и ловит деградацию на 1–2
порядка: Faker в горячем цикле, случайная квадратичность. Реализация — порядка: Faker в горячем цикле, случайная квадратичность. Реализация —
pytest-тест с маркером `perf` и таймаутом-обрубанием: обязателен в CI, pytest-тест с маркером `perf` и таймаутом-обрубанием: обязателен в CI,
@@ -327,6 +337,10 @@ pytest-тест с маркером `perf` и таймаутом-обрубан
- интерфейс запуска генератора (CLI / цели make) и как он делит режимы - интерфейс запуска генератора (CLI / цели make) и как он делит режимы
проигрывателя; кто его зовёт в стенде — даги `world_init`/`next_day` из проигрывателя; кто его зовёт в стенде — даги `world_init`/`next_day` из
оценки мастер-спеки (раздел 9) — и в каком контейнере он живёт; оценки мастер-спеки (раздел 9) — и в каком контейнере он живёт;
- числа притока посетителей: доля одноразовых кук и темп появления новых
(раздел 1);
- календарная дата-константа D0: каким числом дни оси ложатся в
`EventDate`/`UTCEventTime`;
- формат описания мира и конфигурации (что константа кода, что параметр); - формат описания мира и конфигурации (что константа кода, что параметр);
- как фиксируется «зерновой» мир конца этапа 2 (раздел 9 мастер-спеки): - как фиксируется «зерновой» мир конца этапа 2 (раздел 9 мастер-спеки):
с манифестным решением напрашивается мини-манифест зернового мира — форму с манифестным решением напрашивается мини-манифест зернового мира — форму