Files
ddadminandClaude Opus 4.8 1dd79a3287 docs(course): лабы 07 (next-day) и 08 (continue) + метадокументы (#22)
Зачем: два новых режима роста мира не имели уроков, а метадокументы
курса не знали о новом маршруте.

Что: лаба 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>
2026-07-23 13:36:30 +03:00

158 lines
15 KiB
Markdown
Raw Permalink 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.
# План обучения: курс «Кликстрим на 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`):**
- [x] **Баг конвертации `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`):**
- [x] **Кандидат на war-story по типам:** баг `kafka_ts` из урока 1 (`toInt64(DateTime64)`
молча срезал миллисекунды → 1970). Это идеальная иллюстрация темы урока — «тихая
потеря данных на неверном типе». Решено: дан как пример в уроке 2 (war-story в финале).
- [x] Добавить однострочный «поток данных» в шапку `20_stg_to_ods.sql`
(`stg.*_raw → ods.* + ods.*_errors`) — п.3 чеклиста.
- [x] Добавить строку «зачем»: основная таблица = валидный ключ (но может иметь
`parse_errors` по неключевым полям), а `*_errors` = любая ошибка. Без этого
непонятно, почему одна строка попадает в оба места (двойной учёт) — проваливается
тест одного прохода.
- [x] Убрать мусор: в `sql/ddl/ods/20_ods.sql` (стр. ~15–25) — восемь
`DROP TABLE IF EXISTS stg.mv_*_to_ods` (чистка legacy-MV). Шум прошлой
архитектуры, мента читает его раньше сути. Вынести из учебного файла.
- [x] Минор: в `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`):**
- [x] Добавить однострочный «поток данных» в шапку `30_ods_to_dds.sql`
(`ods.* → dds.click + dds.event`) — п.3 чеклиста.
- [x] В финале урока — **recap всей цепочки** STG→ODS→DDS: защита от фрагментации после
расщепления середины (менти должен собрать сквозную модель, а не три изолированных слоя).
- [x] **Демоут DM** оформляем здесь: убрать мусор в `sql/dm/40_dds_to_dm.sql`
(стр. ~14–37, закомментированный «пример материализации витрины») и превратить его
в короткую заметку «VIEW сейчас, материализуем если затормозит, см. `docs/ARCHITECTURE.md`».
Добавить «поток данных» в шапку `40_dds_to_dm.sql`.
- [x] Минор: `sql/ddl/dm/40_dm.sql` — магическое `1919` в `groupArraySample`
(строка «зачем» или упростить).
- Сами витрины DM показываем в деле в уроках 5–6, отдельного разбора не делаем.
**Урок 4 — Airflow DAG (`airflow/dags/etl_pipeline_dag.py`):**
- [x] **Главная правка (код↔доки):** сделать `check_dds_integrity` честным гейтом —
добавить `assert_dds_integrity` (PythonOperator), роняющий DAG при
`orphan_events > 0`. Сейчас «проверка» только считает сирот в xcom и пишет их в
`dq_summary`, но DAG остаётся зелёным, хотя `LESSON_STANDARD` §3 и `PRD` §2 (цель 3)
обещают остановку при нарушении целостности. После правки доки и код сходятся.
- [x] **Управляемая правка урока 4** строится на этом гейте: менти намеренно ломает
целостность (вставляет «осиротевшее» событие) и видит, как DAG краснеет на
`assert_dds_integrity`. Опирается на понятие сирот из урока 3.
- [x] Добавить 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 не читаются построчно. Вся учебная нагрузка
ложится на **текст урока** (маршрут по дашбордам + смысл панелей).
- [x] При написании проверить, что имена дашбордов/панелей/метрик в тексте совпадают
с реальными (`configs/grafana/provisioning/dashboards/*.json`).
- [x] Кандидат на мини-правку (зеркало урока 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
применяем в составе соответствующего урока.