- Зачем:
- три обещания спеки расходились с фактами кода и замеров HITL.
- Что:
- в путь менти возвращён шаг make superset-init; критерий «терминал
только make up» поправлен честно.
- критерий стоимости world_next_day сужен до генерации+manifest:
ETL full_refresh растёт с миром — явно вынесен в «чего не делаем».
- проверка профиля ×1 уточнена: runtime-seam уже на daily-wave,
проверять надо дефолты launch.py и автотесты.
- Проверка:
- сверка с scripts/run_generated_history_runtime_check.sh, Makefile
и замерами HITL (ETL 30 с на 110k событий).
163 lines
12 KiB
Markdown
163 lines
12 KiB
Markdown
# Редизайн пути менти: мир из артефакта и две ветки роста
|
||
|
||
Статус: принято, в работе (ветка `feature/mentee-path`).
|
||
Источник: ручной HITL-прогон пути менти 2026-07-19 — находки F1–F9
|
||
(рабочая копия — `.scratch/hitl-findings.md`; постоянный след — история git,
|
||
срез `0e312b3`). Решения приняты в обсуждении с пользователем 2026-07-19.
|
||
|
||
## Проблема
|
||
|
||
Сегодняшний первый шаг менти — `backfill`: генерация истории с нуля.
|
||
Это медленно (день модельной истории — минуты и десятки минут CPU),
|
||
а результат беден (6 часов плоского профиля `ci`). Вокруг — ворох трения:
|
||
|
||
- пульт `generator_control` подсовывает `backfill` дефолтом и носит имя,
|
||
непонятное осваивающему стенд;
|
||
- два профиля (`ci`/`daily-wave`) путают: менти достаётся плоский тестовый;
|
||
- `next-day` спрятан в общем пульте и не годится для расписания:
|
||
каждый прогон перечитывает всю историю Kafka заново — стоимость растёт
|
||
квадратично по дням, память — линейно (риск OOM);
|
||
- готового артефакта в репозитории нет: механизм `import` есть, а
|
||
импортировать нечего.
|
||
|
||
## Цели
|
||
|
||
- Менти получает богатый мир за секунды-минуты, а не генерирует его.
|
||
- Путь менти проходится в Airflow UI пустыми формами, без справочника.
|
||
- У всех менти одинаковый мир: числа в лабах воспроизводимы число-в-число.
|
||
- Мир можно растить двумя осознанными способами: пакетно по дням и живым
|
||
потоком.
|
||
- Плановый `next-day` перестаёт дорожать с возрастом мира.
|
||
|
||
## Целевая модель
|
||
|
||
**Основной режим менти — `import` эталонного мира.** От восстановленного
|
||
состояния расходятся две ветки — это три режима менти (термины — в
|
||
`CONTEXT.md`):
|
||
|
||
- **База: `import`** — загрузить эталонный мир из артефакта в git.
|
||
- **Ветка A: `next-day`** — пакетно добавить следующий модельный день.
|
||
- **Ветка B: `continue`** — живой поток от той же границы (скорость ×60).
|
||
|
||
`backfill` уходит мейнтейнеру: это инструмент сборки эталонного мира,
|
||
не первый шаг менти (аналогия из соседнего курса Airflow: по умолчанию
|
||
загружают готовый первый месяц, а не собирают его генератором).
|
||
|
||
### Путь менти (UX)
|
||
|
||
1. `make up` — поднять стенд (единственная команда терминала).
|
||
2. Airflow UI: `ddl_init` — создать схему.
|
||
3. Airflow UI: `world_init` — Trigger с пустой формой. Дефолтная операция —
|
||
`import`, путь к артефакту — дефолтом на эталонный мир. DAG сам заливает
|
||
Kafka, запускает ETL и сверяет витрины.
|
||
4. `make superset-init` — разовая инициализация дашборда (терминал).
|
||
5. Superset — готовый трёхдневный мир.
|
||
6. Дальше по выбору лабы: `world_next_day` (кнопка без параметров) или
|
||
`make generator-continue` (живой поток).
|
||
|
||
### Решения
|
||
|
||
1. **Эталонный мир**: 3 модельных дня на профиле `daily-wave` (суточная
|
||
волна видна, есть «средний» день и сравнение день-к-дню). Собирает
|
||
мейнтейнер один раз через `backfill`; хранится в git по фиксированному
|
||
пути `data/startup_history/` сжатым `xz` (~13 МБ; замер 2026-07-19:
|
||
`xz -9e` жмёт артефакт в 48 раз). Формат не меняется: `import` требует
|
||
`raw_topics`, сжатие снимает вопрос размера. Паттерн-образец —
|
||
`airflow-greenplum-solution` (сид `demo.sql.xz` в git, стрим-распаковка
|
||
при загрузке).
|
||
2. **Пульт переименовывается: `generator_control` → `world_init`.**
|
||
Дефолт формы — `import` (сейчас `backfill` — корень находки F3);
|
||
`backfill` и `check` остаются в выпадашке, но уходят из учебных
|
||
инструкций в runbook мейнтейнера. Список DAG'ов читается лесенкой:
|
||
`ddl_init` → `world_init` → `world_next_day`. Цена: история прогонов
|
||
старого dag_id теряется (на учебном стенде не жалко), доки и
|
||
контрактный тест правятся синхронно.
|
||
3. **`world_next_day` — отдельный беспараметрный DAG**: триггернул — день
|
||
добавился. `schedule` прописан, но DAG paused и `catchup=False`:
|
||
процесс тяжёлый, автозапуск включается осознанно (шаг лабы), живой
|
||
рост мира и так даёт ветка `continue`.
|
||
4. **Инкрементальные счётчики manifest — предусловие расписания.**
|
||
Накопительное состояние счётчиков (суммы, множества uid/click_id,
|
||
катящаяся контрольная сумма) переезжает в state/manifest; новый день
|
||
только добавляется. Полная перечитка Kafka уходит: стоимость дня
|
||
становится ~постоянной (сейчас день 2 — 638 с и растёт), риск OOM
|
||
снимается. Разбор по коду — hitl-findings, F9.
|
||
5. **Один учебный профиль**: `daily-wave` (волна, ×60). `ci` (×1, плоско,
|
||
6h) — служебный для автотестов, менти не предлагается. Runtime-seam-
|
||
проверка уже идёт на `daily-wave`; перед слиянием проверить остальных
|
||
потребителей `ci` — дефолты `launch.py` и автотесты, где профиль зашит.
|
||
|
||
## Чего здесь не делаем
|
||
|
||
- **Не параллелим генерацию** — ломает детерминизм (один поток ГПСЧ,
|
||
переходящие визиты); корень стоимости не в ней, а в перечитке (F9).
|
||
- **Не трогаем гео-фактуру** — отдельная спека по ADR-0006.
|
||
- **Не мигрируем лабы курса** (`docs/course/`) — они отстали от реальности
|
||
и потребуют редизайна под три режима; отдельная работа, зафиксирована
|
||
issue в трекере.
|
||
- **Не чиним здесь F5** (быстрый разлогин Airflow/Superset) **и F4**
|
||
(JS-ошибка Grid) — отдельные issues вне фичи.
|
||
- **Не включаем расписание по умолчанию** и не занимаемся retention сверх
|
||
инкрементальных счётчиков.
|
||
- **Не делаем инкрементальный ETL**: `world_next_day` по-прежнему зовёт
|
||
`full_refresh`, его стоимость растёт с миром (на текущих объёмах —
|
||
десятки секунд). Известная цена; исторический план —
|
||
`plans/incremental-etl-v2.md` (legacy).
|
||
|
||
## Форма работ
|
||
|
||
Четыре дочерних issue (GitHub, по контракту `docs/agents/issue-tracker.md`;
|
||
корневой issue ссылается сюда):
|
||
|
||
1. **Эталонный мир**: сборка 3-дневного артефакта, `xz`-хранение в git,
|
||
стрим-распаковка на `import`, дефолтный путь в форме.
|
||
2. **Поверхность DAG'ов**: переименование в `world_init`, дефолт `import`,
|
||
вынос `world_next_day` беспараметрным DAG'ом с paused-расписанием,
|
||
синхронная правка доков и контрактных тестов.
|
||
3. **Инкрементальные счётчики manifest** (без них расписание не включать).
|
||
4. **Один учебный профиль**: `daily-wave` — учебный дефолт, `ci` —
|
||
служебный; проверка независимости runtime-seam-теста от ×1.
|
||
|
||
Зависимости: (1) и (4) сцеплены (эталонный мир собирается на `daily-wave`);
|
||
(3) блокирует включение расписания из (2), но не сам DAG.
|
||
|
||
## Проверка
|
||
|
||
- Чистый стенд: путь менти из раздела UX проходится пустыми формами;
|
||
терминал — только `make up` и `make superset-init`.
|
||
- `import` эталонного мира — секунды-минуты, не десятки минут.
|
||
- После `import` у любого менти совпадают manifest-числа эталонного мира
|
||
(события, визиты, пользователи, контрольная сумма).
|
||
- Задача генерации+manifest в `world_next_day` на дне N по времени
|
||
~равна дню 1 (ETL `full_refresh` растёт с миром — это вне скоупа,
|
||
см. «Чего не делаем»); `make generated-history-chain-check` зелёный
|
||
на стыках.
|
||
- `make test` / `make lint` зелёные; профиль `ci` продолжает обслуживать
|
||
автотесты.
|
||
|
||
## Решения и отклонённые варианты
|
||
|
||
- **Без артефакта, просто ускорить backfill** — отклонено: время менти и
|
||
воспроизводимость (общий файл надёжнее «одинаковой генерации у всех»).
|
||
- **Мир «из коробки» при `make up`** — отклонено: механизм становится
|
||
невидимым, магия в учебном стенде мстит; явный `import` — учебная
|
||
ценность и общее начало обеих лаб.
|
||
- **Расписание включённым по умолчанию** — отклонено: эпизодический стенд,
|
||
тяжёлый процесс, сюрпризы catchup.
|
||
- **Три тонких DAG вместо пульта** — отклонено: плодит сущности; пульт с
|
||
правильным дефолтом + один тонкий `world_next_day` достаточно.
|
||
- **git-lfs / релизные ассеты для артефакта** — отклонено: после `xz`
|
||
артефакт помещается в обычный git.
|
||
|
||
## Влияние на документацию
|
||
|
||
- `README.md`, `docs/OPERATIONS.md`, `docs/ARCHITECTURE.md`,
|
||
`docs/REPO_MAP.md` — новый путь менти и имена DAG'ов (в тех же PR, что
|
||
и изменения).
|
||
- `docs/runbooks/startup-history.md` — `backfill`/сборка эталонного мира
|
||
переезжают сюда как процедура мейнтейнера; дополнить про размер и
|
||
хранение.
|
||
- `CONTEXT.md` — термины «мир (стенда)», «эталонный мир», «три режима
|
||
менти» (добавлены этой же спекой).
|
||
- Лабы `docs/course/` — сознательно не здесь (см. «Чего не делаем»).
|