Зачем: два новых режима роста мира не имели уроков, а метадокументы курса не знали о новом маршруте. Что: лаба 07 «Следующий день и границы времени» — инкремент дня, сверка manifest, переходящие визиты запросом в dds.event, включение расписания на один запуск, цена роста full_refresh; лаба 08 «Живой поток и свежесть данных» — расслоение свежести слоёв, стоп/продолжение генератора, users < sessions, врезка про модельное время; LEARNING_PLAN и README курса согласованы с маршрутом 0–8; в CONTEXT.md починена ссылка «урок 7» (теперь ведёт на врезку лабы 08). Числа со стенда помечены маркером «сверить-на-стенде». Проверка: обе лабы по шаблону LESSON_STANDARD (6 секций, явный «верни как было»); внутренние ссылки разрешаются; git diff --check чистый. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
15 KiB
План обучения: курс «Кликстрим на ClickHouse»
Дата: 2026-06-03 (аудит путей выполнен; середина расщеплена — один паттерн на урок, см. §1–2). Поправка 2026-07-23: весь маршрут стал обязательным. После урока 6 добавлены лабы 07–08 про следующий день и живое продолжение; обе лабы написаны. Назначение: высокоуровневый маршрут менти по курсу — карта уроков, порядок, результаты аудита эталонных путей. Рамка курса (зачем/что/скоуп) — в
PRD.md; как устроен отдельный урок — вLESSON_STANDARD.md.Раздел 3 содержит финальные вердикты аудита и конкретный список правок — их применяем при написании соответствующих уроков, а не отдельным забегом.
1. Маршрут
Порядок линейный, все уроки и лабы обязательны. Урок 0 — разминка, а уроки 1→4 повторяют сам пайплайн (STG → ODS → DDS → оркестрация). Мониторинг (5) и Superset (6) работают как надстройки поверх готовых данных. Лабы 07–08 затем растят импортированный мир: сначала детерминированным следующим днём, потом живым продолжением.
Принцип нарезки — один прод-паттерн на урок (LESSON_STANDARD §2). Поэтому
середина пайплайна разнесена: типизация+DQ (ODS) и сборка сущностей (DDS) — разные
уроки. Витрины DM не отдельный урок: их показываем в деле там, где их потребляют —
в мониторинге (DQ-витрины) и BI (traffic/utm-витрины).
Что нужно знать заранее по Kafka: перед уроком 0 менти смотрит обзорное видео по Kafka из роадмапа — оно даёт словарь терминов. Урок 0 связывает этот словарь с тем, что менти сразу видит в живом стенде.
2. Карта уроков
| # | Урок | Эталонный путь (файлы стенда) | Статус | Режим |
|---|---|---|---|---|
| 0 | Вводный урок по Kafka | Kafka UI: топики, партиции, offset'ы, consumer-группы, отставание (lag) | обязательный | наблюдение |
| 1 | Заземление Kafka → ClickHouse | sql/ddl/stg/10_stg.sql (Kafka engine → MV → MergeTree) |
обязательный | руки |
| 2 | STG → ODS: типизация и DQ-split | sql/ods/20_stg_to_ods.sql + DDL sql/ddl/ods/20_ods.sql |
обязательный | руки |
| 3 | ODS → DDS: сборка сущностей | sql/dds/30_ods_to_dds.sql + DDL sql/ddl/dds/30_dds.sql (argMax, UNION, сироты) |
обязательный | руки |
| 4 | Оркестрация в Airflow | airflow/dags/etl_pipeline_dag.py (зависимости, гейты, остановка при нарушениях) |
обязательный | руки |
| 5 | Мониторинг | Prometheus + Grafana + экспортёры | обязательный | наблюдение |
| 6 | BI-витрина | Superset поверх ClickHouse | обязательный | руки |
| 7 | Лаба: следующий день | airflow/dags/world_next_day_dag.py |
написана, обязательная | руки |
| 8 | Лаба: живое продолжение | make generator-continue и etl_pipeline |
написана, обязательная | руки |
Урок 0 — обязательная разминка без правок кода. В уроках 1–6 и лабах 07–08 менти запускает стенд или делает управляемую правку; каждый такой эксперимент заканчивается явным возвратом к чистому состоянию.
Где «MV vs батч»: контраст из цели №2 PRD — это мост уроков 1→2. Урок 1 (STG) заканчивается вопросом «мы приземлили поток через MV — почему дальше не MV?»; урок 2 (ODS) отвечает: батч ради наблюдаемости и управляемости пересчёта. Отдельным уроком контраст не выделяем — он живёт на стыке.
Витрины DM показываем в деле в уроках 5–6; единственную концепцию DM («VIEW сейчас, материализуем если затормозит») даём короткой заметкой в финале урока 3.
3. Аудит эталонных путей
Вердикт по каждому пути — один из трёх: годно как есть / точечно править /
переписать (критерии «учебного качества» — в LESSON_STANDARD.md).
Финальная оценка (детальный аудит от 2026-06-03):
| Урок | Путь | Файлы | Вердикт |
|---|---|---|---|
| 1 | STG (Kafka→CH) | sql/ddl/stg/10_stg.sql |
точечно править (баг конвертации kafka_ts, найден на стенде) |
| 2 | STG→ODS | sql/ods/20_stg_to_ods.sql + DDL sql/ddl/ods/20_ods.sql |
точечно править |
| 3 | ODS→DDS (+ демоут DM) | sql/dds/30_ods_to_dds.sql + DDL sql/ddl/dds/30_dds.sql; DM sql/dm/40_dds_to_dm.sql + sql/ddl/dm/40_dm.sql |
точечно править |
| 4 | Airflow DAG | airflow/dags/etl_pipeline_dag.py |
точечно править |
| 5 | Мониторинг | configs/*, экспортёры |
годно как есть (для режима наблюдения) |
| 7 | Следующий день | airflow/dags/world_next_day_dag.py |
точечно править |
Карты путей раздела 2 включают и DDL целевых таблиц (
sql/ddl/{ods,dds}/*), а не только батч-трансформации: без формы целевых таблиц слой читается неполно.
3.1. Список правок (применяем при написании урока)
Правки точечные, делаем не отдельным забегом, а в составе соответствующего урока.
Урок 1 — STG (sql/ddl/stg/10_stg.sql):
- Баг конвертации
kafka_ts(найден прогоном на стенде 2026-06-03). БылоfromUnixTimestamp64Milli(toInt64(_timestamp_ms))во всех 4 MV:_timestamp_msэтоDateTime64(3),toInt64()срезает его до секунд, иfromUnixTimestamp64Milliчитает секунды как миллисекунды →kafka_ts=1970-01-21по всему стенду. Исправлено на_timestamp_ms AS kafka_ts(прямое присвоение, мс сохраняются). Прежний аудит ошибочно пометил путь «годно как есть» — потому что его не прогоняли. - Колоночные комментарии-пересказы (
-- Партиция,-- Имя топика) безвредны — не трогаем. - В финале урока — мост к уроку 2: вопрос «почему дальше не MV?».
Урок 2 — STG→ODS, типизация + DQ-split (sql/ods/20_stg_to_ods.sql + DDL sql/ddl/ods/20_ods.sql):
- Кандидат на war-story по типам: баг
kafka_tsиз урока 1 (toInt64(DateTime64)молча срезал миллисекунды → 1970). Это идеальная иллюстрация темы урока — «тихая потеря данных на неверном типе». Решено: дан как пример в уроке 2 (war-story в финале). - Добавить однострочный «поток данных» в шапку
20_stg_to_ods.sql(stg.*_raw → ods.* + ods.*_errors) — п.3 чеклиста. - Добавить строку «зачем»: основная таблица = валидный ключ (но может иметь
parse_errorsпо неключевым полям), а*_errors= любая ошибка. Без этого непонятно, почему одна строка попадает в оба места (двойной учёт) — проваливается тест одного прохода. - Убрать мусор: в
sql/ddl/ods/20_ods.sql(стр. ~15–25) — восемьDROP TABLE IF EXISTS stg.mv_*_to_ods(чистка legacy-MV). Шум прошлой архитектуры, мента читает его раньше сути. Вынести из учебного файла. - Минор: в
20_ods.sqlразнобой партиционирования (browserпоevent_date, остальные поsrc_ingest_ts) — одна строка «почему» либо унифицировать. - В «Зачем» урока — ответ на мост из урока 1: батч ради наблюдаемости/управляемости.
Урок 3 — ODS→DDS, сборка сущностей (sql/dds/30_ods_to_dds.sql + DDL sql/ddl/dds/30_dds.sql):
- Добавить однострочный «поток данных» в шапку
30_ods_to_dds.sql(ods.* → dds.click + dds.event) — п.3 чеклиста. - В финале урока — recap всей цепочки STG→ODS→DDS: защита от фрагментации после расщепления середины (менти должен собрать сквозную модель, а не три изолированных слоя).
- Демоут DM оформляем здесь: убрать мусор в
sql/dm/40_dds_to_dm.sql(стр. ~14–37, закомментированный «пример материализации витрины») и превратить его в короткую заметку «VIEW сейчас, материализуем если затормозит, см.docs/ARCHITECTURE.md». Добавить «поток данных» в шапку40_dds_to_dm.sql. - Минор:
sql/ddl/dm/40_dm.sql— магическое1919вgroupArraySample(строка «зачем» или упростить). - Сами витрины DM показываем в деле в уроках 5–6, отдельного разбора не делаем.
Урок 4 — Airflow DAG (airflow/dags/etl_pipeline_dag.py):
- Главная правка (код↔доки): сделать
check_dds_integrityчестным гейтом — добавитьassert_dds_integrity(PythonOperator), роняющий DAG приorphan_events > 0. Сейчас «проверка» только считает сирот в xcom и пишет их вdq_summary, но DAG остаётся зелёным, хотяLESSON_STANDARD§3 иPRD§2 (цель 3) обещают остановку при нарушении целостности. После правки доки и код сходятся. - Управляемая правка урока 4 строится на этом гейте: менти намеренно ломает
целостность (вставляет «осиротевшее» событие) и видит, как DAG краснеет на
assert_dds_integrity. Опирается на понятие сирот из урока 3. - Добавить ASCII-поток задач в docstring DAG
(
precheck → transform: wait → ods → dq → branch → dds → integrity → dm → validate) — для теста одного прохода. - Замечание:
check_ods_qualityтоже measure-only (метрики в xcom, без гейта) — это ок и намеренно (DQ поparse_errorsинформативен, но не блокирует). В тексте урока развести: какие проверки гейтят пайплайн, а какие только измеряют.
Урок 5 — мониторинг (configs/*):
- Конфиги (
prometheus.yml, экспортёры, provisioning Grafana) полировать под «учебное качество кода» не нужно: дашборды/JSON не читаются построчно. Вся учебная нагрузка ложится на текст урока (маршрут по дашбордам + смысл панелей). - При написании проверить, что имена дашбордов/панелей/метрик в тексте совпадают
с реальными (
configs/grafana/provisioning/dashboards/*.json). - Кандидат на мини-правку (зеркало урока 4): погасить сервис → увидеть, как панель/ алерт в Grafana краснеет. Решает открытый вопрос PRD про глубину урока (даёт «сломал-увидел» вместо чистого наблюдения) — обсудить при написании.
4. Что дальше
Все уроки 0–6 и лабы 07–08 написаны (lessons/). Правки кода §3.1 применены
в составе уроков; чекбоксы выше сверены с реальным кодом 2026-06-06. Маршрут
курса собран полностью.
Исходный план фазы написания (оставлен как контекст): пишем уроки по одному, по
шаблону из LESSON_STANDARD.md, начиная с урока 1 (Kafka→CH); урок 0 (разминка на
Kafka UI) готовим параллельно — он не зависит от полировки кода; правки кода из §3.1
применяем в составе соответствующего урока.