- Зачем:
- три обещания спеки расходились с фактами кода и замеров 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 событий).
12 KiB
Редизайн пути менти: мир из артефакта и две ветки роста
Статус: принято, в работе (ветка 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)
make up— поднять стенд (единственная команда терминала).- Airflow UI:
ddl_init— создать схему. - Airflow UI:
world_init— Trigger с пустой формой. Дефолтная операция —import, путь к артефакту — дефолтом на эталонный мир. DAG сам заливает Kafka, запускает ETL и сверяет витрины. make superset-init— разовая инициализация дашборда (терминал).- Superset — готовый трёхдневный мир.
- Дальше по выбору лабы:
world_next_day(кнопка без параметров) илиmake generator-continue(живой поток).
Решения
- Эталонный мир: 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, стрим-распаковка при загрузке). - Пульт переименовывается:
generator_control→world_init. Дефолт формы —import(сейчасbackfill— корень находки F3);backfillиcheckостаются в выпадашке, но уходят из учебных инструкций в runbook мейнтейнера. Список DAG'ов читается лесенкой:ddl_init→world_init→world_next_day. Цена: история прогонов старого dag_id теряется (на учебном стенде не жалко), доки и контрактный тест правятся синхронно. world_next_day— отдельный беспараметрный DAG: триггернул — день добавился.scheduleпрописан, но DAG paused иcatchup=False: процесс тяжёлый, автозапуск включается осознанно (шаг лабы), живой рост мира и так даёт веткаcontinue.- Инкрементальные счётчики manifest — предусловие расписания. Накопительное состояние счётчиков (суммы, множества uid/click_id, катящаяся контрольная сумма) переезжает в state/manifest; новый день только добавляется. Полная перечитка Kafka уходит: стоимость дня становится ~постоянной (сейчас день 2 — 638 с и растёт), риск OOM снимается. Разбор по коду — hitl-findings, F9.
- Один учебный профиль:
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 ссылается сюда):
- Эталонный мир: сборка 3-дневного артефакта,
xz-хранение в git, стрим-распаковка наimport, дефолтный путь в форме. - Поверхность DAG'ов: переименование в
world_init, дефолтimport, выносworld_next_dayбеспараметрным DAG'ом с paused-расписанием, синхронная правка доков и контрактных тестов. - Инкрементальные счётчики manifest (без них расписание не включать).
- Один учебный профиль:
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 (ETLfull_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/— сознательно не здесь (см. «Чего не делаем»).