Зачем: после редизайна пути менти курс ссылался на старый путь generated-history-analytics/backfill и не проходился по новому стенду. Что: в README курса — единый блок подготовки и канонического сброса (make clean -> make up + ddl_init/world_init -> make superset-init), таблица уроков дополнена лабами 07–08 («в работе»); LESSON_STANDARD и уроки 0–6 ссылаются на канонический блок; урок 1 переведён на дозаливку через world_next_day (кнопкой-анонсом, цена в минутах названа); урок 4 — лесенка DAG-ов; урок 5 — словарь «база import / живой поток»; урок 6 обязателен; цифры старого мира помечены маркером «сверить-на-стенде». Проверка: grep по generated-history-analytics/backfill в docs/course/ пуст; правки только в docs/course/; git diff --check чистый. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
139 lines
12 KiB
Markdown
139 lines
12 KiB
Markdown
# Стандарт уроков курса «Кликстрим на ClickHouse»
|
||
|
||
> Дата: 2026-06-01. Поправка: 2026-06-05 (по итогам уроков 0–2 — добавлены §2 «Регистр
|
||
> и голос», описание шапки урока и §5 «Грабли, на которые мы уже наступили»).
|
||
> Назначение: рабочий чеклист, по которому пишется **каждый** урок. Открывается при
|
||
> создании урока. Рамка курса (зачем/что/скоуп) — в `PRD.md`; карта уроков и
|
||
> маршрут — в `LEARNING_PLAN.md`.
|
||
|
||
---
|
||
|
||
## 1. Шаблон урока
|
||
|
||
Каждый урок строится по единому шаблону:
|
||
|
||
1. **Зачем и где в проде** — что за паттерн и где он встречается в обычной
|
||
кликстрим-аналитике (без привязки к конкретной компании).
|
||
2. **Руки** — что запустить и что понаблюдать (Kafka UI / ClickHouse / Grafana).
|
||
3. **Загляни внутрь** — чтение отполированного эталонного кода с пояснениями.
|
||
4. **Управляемая правка** — одно-два маленьких изменения с **видимым результатом**.
|
||
Например: добавить поле в Materialized View и увидеть его в `*_raw`; «сломать»
|
||
запись и увидеть `+1` в таблице ошибок `parse_errors`; сменить `kafka_group_name`
|
||
и увидеть, как топик читается заново. Менти меняет — видит эффект — объясняет.
|
||
**Верни как было** — каждая правка завершается явным шагом отката к чистому
|
||
состоянию (откатить изменение либо пройти
|
||
[канонический сброс](./README.md#подготовка-и-канонический-сброс)), чтобы
|
||
самостоятельный менти не застрял со сломанным стендом без ментора.
|
||
(Урок 0 — без этого шага, только наблюдение.)
|
||
5. **Проверь себя** — самопроверка (раздел 4).
|
||
6. **Что должно получиться** — **конкретный видимый результат**, который менти проверяет
|
||
сам: скрин эффекта правки (например, красный DAG или `+1` в таблице ошибок) плюс один
|
||
абзац «своими словами» про паттерн урока или запрос, который пришлось написать. Это и
|
||
есть самопроверка (раздел 4); те же вопросы «своими словами» менти разбирает с ментором
|
||
на еженедельном созвоне. (Урок 0 — без правки, поэтому результат здесь — что менти
|
||
увидел при наблюдении.)
|
||
|
||
### Шапка урока
|
||
|
||
Кроме шести секций, каждый урок открывается единым блоком-цитатой (`>`) сверху — он задаёт
|
||
рамку до того, как менти дойдёт до первой секции:
|
||
|
||
- **Формат:** одной фразой, что менти делает, — `**практика** — будешь сам запускать…`
|
||
или `**наблюдение** — ничего не запускаем и не меняем…`. Старые пометки «Статус: черновик»
|
||
и «Режим: руки» не возвращаем: менти читал их как «материал не готов» и «непонятно, как
|
||
понимать»;
|
||
- **Пререквизит:** какой урок пройден и что менти уже умеет;
|
||
- **Эталонный путь:** ссылка(и) на разбираемый файл;
|
||
- **Поток данных одной строкой:** та же стрелочная схема, что стоит в начале эталонного файла;
|
||
- **О чём урок простыми словами:** 1–2 фразы совсем простым языком, без терминов, — чтобы
|
||
менти с первой строки понял, о чём речь.
|
||
|
||
В конце урока — короткий **мост к следующему**: какой честный вопрос остаётся открытым и
|
||
как его закроет следующий урок.
|
||
|
||
## 2. Регистр и голос
|
||
|
||
Этот раздел — про то, *как* написан урок. Он появился по итогам уроков 0–2: мы начали со
|
||
сжатого регистра и переписывали — дешевле сразу писать мягко.
|
||
|
||
**Кто читатель.** Менти прошёл курсовую, уровень — «обзорно» (понять идею и потрогать, а не
|
||
стать экспертом). Поддержка — еженедельный созвон с ментором, а не «сессия»; на нём менти
|
||
разбирает вопросы «своими словами».
|
||
|
||
**Правила регистра:**
|
||
- **термин расшифровываем на первом употреблении** — коротко, своими словами (как `ODS`,
|
||
`DQ-split`, `Materialized View`, `*OrNull` в уроках 1–2). Не оставляем слово голым;
|
||
- **человеческий тон, без жаргона ради жаргона** — короткие предложения, прямое обращение
|
||
к менти («открой», «посмотри», «помнишь из урока 0»);
|
||
- **`###`-подзаголовки внутри длинных секций** — особенно в «Загляни внутрь»: каждый
|
||
смысловой кусок под своим заголовком, а не сплошной стеной;
|
||
- **плотные абзацы-«стены» бьём на пункты** с конкретикой;
|
||
- **разжёвывать, а не сжимать.** Урок может выйти заметно длиннее первого черновика — для
|
||
«обзорно» это норма, а не повод резать.
|
||
|
||
**House style (оставляем осознанно, `ai-text-lint` на это не правим):** тире «—», буква «ё»,
|
||
жирные зачины абзацев, двоеточие перед списком.
|
||
|
||
**Перед финалом** — прогнать текст урока через `/ai-text-lint` (контекст `article`) и убрать
|
||
настоящие AI-маркеры (например, слова вроде «ключевой», «является»), но house style выше
|
||
не снимать.
|
||
|
||
## 3. Стандарт качества эталонного кода
|
||
|
||
Эталонные пути полируем до **учебного качества**; остальной код стенда остаётся под
|
||
капотом — его не трогаем.
|
||
|
||
**Чеклист (минимум).** Файл доведён, если:
|
||
1. комментарии на русском объясняют **зачем**, а не пересказывают код;
|
||
2. шаги явные и названы своими именами, без неочевидных трюков;
|
||
3. в начале файла — поток данных одной строкой (как в STG:
|
||
`Kafka → kafka_*_raw → MV → *_raw`);
|
||
4. ошибки и проверки качества данных видны — понятно, куда смотреть;
|
||
5. нет мусора: мёртвого кода, закомментированных экспериментов, разнобоя в именах.
|
||
|
||
**Тест одного прохода (главный критерий и защита от громоздкости).**
|
||
> Менти читает файл один раз сверху вниз и может пересказать своими словами,
|
||
> *что* происходит и *зачем* — не прыгая по файлу, не гугля, не расшифровывая трюки.
|
||
|
||
Если тест не проходит — правим строго одним из трёх способов: (1) упростить код;
|
||
(2) добавить ровно одну строку «зачем» там, где код честно неочевиден; (3) убрать
|
||
лишнее под капот или в короткую пометку. **Добавить ещё комментариев — не способ.**
|
||
|
||
**Две защиты от раздувания:**
|
||
- комментируем только неочевидное «зачем» (очевидную строку комментировать нельзя —
|
||
лишний текст сам по себе мешает понимать);
|
||
- «близко к проду» ≠ «вся прод-сложность прямо в коде урока»: один прод-паттерн на
|
||
урок показываем чисто, остальные прод-заботы — короткой пометкой «в проде иначе: …».
|
||
|
||
Вердикты аудита по конкретным путям — в `LEARNING_PLAN.md`.
|
||
|
||
## 4. Самопроверка (по-простому)
|
||
|
||
Отдельный тест-фреймворк не строим. Опираемся на то, что в стенде уже есть:
|
||
- штатные быстрые проверки (smoke) из `docs/TEST_PLAN.md`;
|
||
- встроенные проверки в `etl_pipeline_dag.py` (DAG падает на пустой витрине или
|
||
нарушении целостности);
|
||
- [канонический сброс](./README.md#подготовка-и-канонический-сброс) для чистого
|
||
возврата и повтора штатного пути.
|
||
|
||
В каждом уроке — маленькая табличка самопроверки в формате
|
||
**действие → где смотреть → что ожидать**, а для управляемой правки — какой
|
||
видимый результат должен появиться.
|
||
|
||
## 5. Грабли, на которые мы уже наступили
|
||
|
||
Короткий список ошибок из уроков 0–2 — чтобы следующий урок их не повторил.
|
||
|
||
- **Проверяй на стенде, а не «на глаз».** Любой категоричный claim про числа или целостность
|
||
подтверждай командой на стенде. Так нашёлся баг с `kafka_ts` в уроке 1 (значение типа
|
||
`DateTime64` молча резалось через `toInt64`, и время по всему стенду уехало в `1970`) и
|
||
подтвердилось, что меньший размер таблиц контекста в уроке 2 — это схлопывание повторов
|
||
по `click_id` (проверяется через `uniqExact`), а не потеря данных.
|
||
- **Имена таблиц и колонок в тексте сверяй с реальным DDL.** То, что написано в уроке, должно
|
||
совпадать с `sql/ddl/...`, иначе менти запутается, когда выполнит запрос на стенде.
|
||
- **Один паттерн на урок; будущие темы только анонсируй.** Не раскрывай то, что относится к
|
||
следующим урокам (пример: жёсткий гейт на «сирот» — это урок 4, в уроке 3 мы их только
|
||
вводим как понятие).
|
||
- **Спорные API ClickHouse сверяй через MCP Context7** (зачем и в каком порядке — в
|
||
`AGENTS.md`), не полагайся на память: именно типы и поведение функций уже один раз подвели.
|