diff --git a/docs/course/LEARNING_PLAN.md b/docs/course/LEARNING_PLAN.md index 2d582aa..fe6f413 100644 --- a/docs/course/LEARNING_PLAN.md +++ b/docs/course/LEARNING_PLAN.md @@ -2,6 +2,8 @@ > Дата: 2026-06-03 (аудит путей выполнен; середина расщеплена — > один паттерн на урок, всего 7 уроков, см. §1–2). +> Поправка 2026-07-23: урок 6 стал обязательным; после него добавлены обязательные +> лабы 07–08 про следующий день и живое продолжение. > Назначение: высокоуровневый маршрут менти по курсу — карта уроков, порядок, > результаты аудита эталонных путей. Рамка курса (зачем/что/скоуп) — в `PRD.md`; > как устроен отдельный урок — в `LESSON_STANDARD.md`. @@ -14,8 +16,9 @@ ## 1. Маршрут Порядок линейный — уроки 1→4 повторяют сам пайплайн (STG → ODS → DDS → оркестрация), -а урок 0 — разминка перед ним. Мониторинг (5) и Superset (6) более самостоятельны и -работают как надстройки поверх готовых данных. +а урок 0 — разминка перед ним. Мониторинг (5) и Superset (6) работают как надстройки +поверх готовых данных. Лабы 07–08 затем растят импортированный мир: сначала +детерминированным следующим днём, потом живым продолжением. Принцип нарезки — **один прод-паттерн на урок** (`LESSON_STANDARD` §2). Поэтому середина пайплайна разнесена: типизация+DQ (ODS) и сборка сущностей (DDS) — разные @@ -36,7 +39,9 @@ Kafka из роадмапа — оно даёт словарь терминов. | 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 | опциональный | руки | +| 6 | BI-витрина | Superset поверх ClickHouse | обязательный | руки | +| 7 | Лаба: следующий день | `airflow/dags/world_next_day_dag.py` | в работе, обязательный | руки | +| 8 | Лаба: живое продолжение | `make generator-continue` и `etl_pipeline` | в работе, обязательный | руки | Урок 0 — обязательная разминка (без правок кода, только наблюдение); уроки 1–4 — с управляемыми правками; урок 5 ближе к наблюдению (глубину уточняем, см. @@ -64,6 +69,7 @@ Kafka из роадмапа — оно даёт словарь терминов. | 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}/*`), а не > только батч-трансформации: без формы целевых таблиц слой читается неполно. diff --git a/docs/course/LESSON_STANDARD.md b/docs/course/LESSON_STANDARD.md index 1f46624..c08c898 100644 --- a/docs/course/LESSON_STANDARD.md +++ b/docs/course/LESSON_STANDARD.md @@ -21,7 +21,8 @@ запись и увидеть `+1` в таблице ошибок `parse_errors`; сменить `kafka_group_name` и увидеть, как топик читается заново. Менти меняет — видит эффект — объясняет. **Верни как было** — каждая правка завершается явным шагом отката к чистому - состоянию (откатить изменение либо `make generated-history-analytics && make up`), чтобы + состоянию (откатить изменение либо пройти + [канонический сброс](./README.md#подготовка-и-канонический-сброс)), чтобы самостоятельный менти не застрял со сломанным стендом без ментора. (Урок 0 — без этого шага, только наблюдение.) 5. **Проверь себя** — самопроверка (раздел 4). @@ -112,7 +113,8 @@ - штатные быстрые проверки (smoke) из `docs/TEST_PLAN.md`; - встроенные проверки в `etl_pipeline_dag.py` (DAG падает на пустой витрине или нарушении целостности); -- `make generated-history-analytics && make up` для чистого сброса и повтора штатного пути. +- [канонический сброс](./README.md#подготовка-и-канонический-сброс) для чистого + возврата и повтора штатного пути. В каждом уроке — маленькая табличка самопроверки в формате **действие → где смотреть → что ожидать**, а для управляемой правки — какой diff --git a/docs/course/PRD.md b/docs/course/PRD.md index 3ce0043..b020218 100644 --- a/docs/course/PRD.md +++ b/docs/course/PRD.md @@ -3,7 +3,7 @@ > Статус: прообраз PRD. Дата: 2026-06-01. > Поправка 2026-06-03 (разморозка по делу): середина пайплайна расщеплена — ODS и DDS > теперь разные уроки (принцип «один паттерн на урок»), витрины DM демотированы в -> поверхность потребления. Обязательных уроков стало 0–5, опциональный Superset — урок 6. +> поверхность потребления. Superset вынесен в урок 6. > Затронуты §4 (скоуп) и §3/§5 (ожидаемый такт — ~день на урок). Это изменение рамки, > а не план реализации. > Поправка 2026-06-03 (терминология): режим сопровождения — еженедельный **созвон**, а не @@ -16,6 +16,10 @@ > стенда больше не является. Схема потока в §1 обновлена под генератор (источник данных > сменился, см. ADR-0006). В §7 закрыта развилка про урок о генераторе и добавлена > развилка про кластерную конфигурацию. +> Поправка 2026-07-23 (маршрут курса): зафиксированы три режима работы с миром — +> импорт эталонной базы, пакетная дозаливка следующего дня и живое продолжение. +> Урок 6 стал обязательным, после него добавлены обязательные лабы 07–08. +> Затронуты §1–4 и §7. > Назначение документа: зафиксировать для будущих сессий, что это за курс, зачем > он, что входит в скоуп работ, а что нет. Это договорная **рамка**, а не план > реализации и не стандарт уроков (см. раздел «Связанные документы»). @@ -30,7 +34,7 @@ Репозиторий — рабочий сквозной стенд кликстрим-DWH: ``` -generator (backfill/live) → Kafka → ClickHouse (Kafka engine + MV → STG) → Airflow ETL (STG→ODS→DDS→DM) → Superset +generator (импорт / живой поток) → Kafka → ClickHouse (Kafka engine + MV → STG) → Airflow ETL (STG→ODS→DDS→DM) → Superset ↘ Prometheus / Grafana (мониторинг) ``` @@ -85,7 +89,9 @@ generator (backfill/live) → Kafka → ClickHouse (Kafka engine + MV → STG) 3. Читать и объяснять **оркестрацию в Airflow**: DAG, зависимости задач, проверки качества данных, остановку пайплайна при нарушениях. 4. Понимать, **как устроен мониторинг** пайплайна (метрики, экспортёры, дашборды). -5. (Опционально) Подключать **BI-витрину** поверх ClickHouse (Superset). +5. Подключать **BI-витрину** поверх ClickHouse (Superset). +6. Различать пакетную дозаливку дня и живой поток, понимать границы времени + и свежесть данных. Сквозная цель — не «посмотреть, как работает», а **уметь пересказать паттерн своими словами и привязать его к обычной кликстрим-аналитике** (трекер событий → @@ -96,8 +102,8 @@ Kafka → ClickHouse → BI). - **Аудитория:** продвинутые менти, прошедшие базовую программу. Пишем обобщённо, но затачиваем под реальный первый прогон, а не под гипотетических будущих менти. - **Режим:** самостоятельный, асинхронный. Менти клонирует репозиторий, готовит - стенд штатным путём (`make generated-history-analytics && make up`) и идёт по - урокам из `docs/course/` рядом с кодом. + стенд по [канонической инструкции](./README.md#подготовка-и-канонический-сброс) + и идёт по урокам из `docs/course/` рядом с кодом. Уроки короткие и односоставные — ожидаемый срок прохождения одного **около дня**. - **Роль ментора:** еженедельный созвон-сверка (покрывает несколько уроков), без построчного разбора кода. @@ -108,10 +114,12 @@ Kafka → ClickHouse → BI). ## 4. Скоуп ### Входит -- **Уроки 0–5 (обязательные):** вводный урок по Kafka, заземление Kafka→CH, STG→ODS +- **Уроки 0–6 (обязательные):** вводный урок по Kafka, заземление Kafka→CH, STG→ODS (типизация + DQ), ODS→DDS (сборка сущностей), Airflow, мониторинг. Принцип нарезки — один прод-паттерн на урок; контраст «где Materialized View, а где батч» проходит - мостом уроков 1→2. + мостом уроков 1→2. Урок 6 закрывает BI-слой в Superset. +- **Лабы 07–08 (обязательные):** пакетная дозаливка следующего дня, затем живое + продолжение потока. - Витрины **DM — не отдельный урок**: их показываем в деле там, где их потребляют (мониторинг и BI). См. `LEARNING_PLAN.md` §1–2. - **Аудит и точечная полировка эталонных путей** этих уроков до учебного качества @@ -121,10 +129,6 @@ Kafka → ClickHouse → BI). Подробная карта уроков (файлы стенда, статус, режим, вердикты аудита) — в плане обучения `LEARNING_PLAN.md`. -### Опционально -- **Урок 6: Superset (BI-витрина).** Делаем, если останется ресурс; обязательные - уроки он не блокирует. - ### Не входит - Переписывание всего стенда: полируем только эталонные пути обязательных уроков, остальной код стенда остаётся под капотом. @@ -171,8 +175,7 @@ Kafka → ClickHouse → BI). (зеркало урока 4) — урок 5 даёт «сломал-увидел», а не чистое наблюдение. См. `LEARNING_PLAN.md` §3.1. - ~~Нужна ли BI-витрина (Superset) уже в первой версии.~~ **Решено:** урок 6 написан и - синхронизирован с реальным дашбордом; остаётся опциональным (обязательные уроки не - блокирует). + синхронизирован с реальным дашбордом. С 2026-07-23 он входит в обязательный маршрут. Закрыто позже: diff --git a/docs/course/README.md b/docs/course/README.md index 5b0608e..8cfbc51 100644 --- a/docs/course/README.md +++ b/docs/course/README.md @@ -27,24 +27,27 @@ поднимаются одновременно. Нужна машина, которая это потянет. - **Инструменты:** `Docker` с `docker compose`, `make`, `bash`, `curl`, `git` и `uv`. `uv` нужен для локальных Python-проверок и команд разработки. -- **Подними стенд и создай стартовую историю** (из корня репозитория) — этого хватит, - чтобы начать, и прогон быстрый: - ```bash - make generated-history-analytics - make up - ``` +## Подготовка и канонический сброс - Эта команда проводит штатный путь стенда: готовый источник данных создаёт стартовую - историю, события попадают в Kafka, затем в STG, ODS, DDS, DM и Superset. Это тот же - путь, что ручной вариант из README (Airflow UI и операция `backfill`), но одной - командой — выбери один из двух, оба ведут к одинаковому стенду. Файлы - `data/*.jsonl` пока остаются только кладовкой готовых значений для генератора - (браузеры, страны, устройства, UTM), а не источником аналитического контура. `make up` - после неё поднимает остальные UI-сервисы курса: Kafka UI, Airflow, Prometheus и Grafana. +Первый запуск и возврат к чистому эталонному миру идут одним путём. При первом запуске +пропусти `make clean`; для полного сброса выполни все три шага: - Дальше каждый урок в секции «Руки» сам напоминает, что перезапустить. - Точные шаги, параметры и troubleshooting — в [`docs/OPERATIONS.md`](../OPERATIONS.md). +1. Выполни `make clean`. Команда удалит данные стенда и метаданные Superset: сохранённые + в нём настройки и дашборды тоже придётся создать заново. +2. Выполни `make up`, открой Airflow на `http://localhost:8080` (`admin/admin`) и дождись + успешного завершения двух DAG-ов по порядку: + - `ddl_init` — запусти с пустой формой; + - `world_init` — после него запусти с пустой формой. +3. Когда `world_init` завершится успешно и витрины DM будут готовы, выполни + `make superset-init`. + +Так события из эталонного мира попадут в Kafka, затем в STG, ODS, DDS и DM, а Superset +получит готовые наборы данных и дашборд. Файлы `data/*.jsonl` остаются только кладовкой +готовых значений для генератора (браузеры, страны, устройства, UTM), а не источником +аналитического контура. + +Точные параметры и разбор ошибок — в [`docs/OPERATIONS.md`](../OPERATIONS.md). ## Уроки @@ -60,7 +63,9 @@ | 3 | [ODS → DDS: сборка сущностей](./lessons/03_ods_to_dds.md) | руки | Собираем `click` и `event` из кусочков (argMax, JOIN) и встречаем «сирот» | | 4 | [Оркестрация в Airflow](./lessons/04_airflow_orchestration.md) | руки | Всю цепочку — в один DAG с зависимостями и честным гейтом целостности | | 5 | [Мониторинг: Prometheus и Grafana](./lessons/05_monitoring.md) | наблюдение + мини-правка | Смотрим систему со стороны; гасим сервис — видим, как краснеет алерт | -| 6 | [BI-витрина в Superset](./lessons/06_superset_bi.md) | руки (опционально) | Дашборд поверх ClickHouse: KPI, динамика, воронка | +| 6 | [BI-витрина в Superset](./lessons/06_superset_bi.md) | руки | Дашборд поверх ClickHouse: KPI, динамика, воронка | +| 7 | Лаба: следующий день | руки, в работе | Пакетный инкремент дня и границы времени | +| 8 | Лаба: живое продолжение | руки, в работе | Живой поток и свежесть данных | ## Как проходить @@ -76,22 +81,18 @@ ## Проверка чистого маршрута -Перед проверкой уроков 0, 1 и 5 подними стенд с нуля: - -```bash -make generated-history-analytics -make up -``` +Перед проверкой уроков 0, 1 и 5 пройди +[канонический сброс](#подготовка-и-канонический-сброс). Что должен подтвердить человек: - урок 0: в Kafka UI видны четыре топика событий и понятны служебные топики генератора; -- урок 1: после `CLEAN_START=0 make generated-history-analytics` учебная колонка - `kafka_msg_ts` не пропадает и заполняется; +- урок 1: после запуска `world_next_day` учебная колонка `kafka_msg_ts` не пропадает + и заполняется; - урок 5: Prometheus targets `clickhouse`, `kafka`, `airflow` находятся в `UP`, - а `Kafka No Messages Produced` трактуется с учётом того, запущен live-генератор - или только стартовая история. + а `Kafka No Messages Produced` трактуется с учётом того, включён живой поток + или стенд работает на импортированной базе. ## Границы diff --git a/docs/course/lessons/00_kafka_intro.md b/docs/course/lessons/00_kafka_intro.md index 53544f6..b7fc161 100644 --- a/docs/course/lessons/00_kafka_intro.md +++ b/docs/course/lessons/00_kafka_intro.md @@ -41,12 +41,9 @@ consumer читает в своём темпе, не трогая producer'а. ## 2. Наблюдай: открой Kafka UI -Стенд уже должен быть поднят, а в топиках должны лежать события после стартовой истории: - -```bash -make generated-history-analytics -make up -``` +Подготовь стенд по +[канонической инструкции курса](../README.md#подготовка-и-канонический-сброс). +После успешного `world_init` в топиках уже лежат события эталонного мира. Открой Kafka UI: `http://localhost:8082`. Ходи по нему свободно — это режим чтения, сломать тут ничего нельзя. @@ -85,6 +82,7 @@ make up {"event_id": "8cca1c7d-...", "event_timestamp": "2026-01-01 00:01:00.000000", "event_type": "pageview", "browser_name": "Chrome", "browser_language": "sat_IN"} ``` + Загляни внутрь Value: у события есть своё `event_timestamp` (когда оно случилось, модельное время стенда), и оно отличается от Kafka-Timestamp (когда оно попало в топик). diff --git a/docs/course/lessons/01_kafka_to_clickhouse.md b/docs/course/lessons/01_kafka_to_clickhouse.md index e345d91..12bc4d0 100644 --- a/docs/course/lessons/01_kafka_to_clickhouse.md +++ b/docs/course/lessons/01_kafka_to_clickhouse.md @@ -49,15 +49,9 @@ ## 2. Руки: убедись, что данные текут -Поднимаем стенд и создаём стартовую историю. Это штатный путь курса: готовый источник -данных стенда пишет события в Kafka, ClickHouse читает их в STG, затем batch строит -ODS, DDS и DM. Файлы `data/*.jsonl` в этом пути не источник аналитики; пока это только -кладовка значений для источника данных стенда. - -```bash -make generated-history-analytics -make up -``` +Подготовь стенд по +[канонической инструкции курса](../README.md#подготовка-и-канонический-сброс). +После успешного `world_init` эталонный мир уже прошёл путь Kafka → STG → ODS → DDS → DM. Теперь смотрим, что доехало до ClickHouse. Открой SQL-консоль: `http://localhost:9123/play` (или Kafka UI на `http://localhost:8082`, чтобы тем же @@ -189,27 +183,23 @@ SELECT FROM stg.kafka_browser_raw; ``` -**Перезаливаем срез, чтобы новая колонка заполнилась.** Сначала чистим только таблицу -приёмника — иначе рядом останутся строки из секции 2, вставленные ещё *до* -`ADD COLUMN`, и в них `kafka_msg_ts` будет пустой (`1970-01-01`): +**Дозаливаем следующий день, чтобы новая колонка заполнилась.** Не очищай +`stg.browser_raw`: `world_next_day` растит уже импортированный мир, а его финальная +проверка сверяет весь накопленный результат. В старых строках, вставленных до +`ADD COLUMN`, `kafka_msg_ts` останется пустым (`1970-01-01`) — ниже мы их отфильтруем. -```sql -TRUNCATE TABLE stg.browser_raw; -``` - -Затем повторно создаём стартовую историю **без очистки volumes**. Это важно: -`CLEAN_START=0` сохраняет твою новую колонку и пересозданное MV, но добавляет свежие -сообщения в Kafka, чтобы ClickHouse прочитал их уже с новой схемой. - -```bash -CLEAN_START=0 make generated-history-analytics -``` +Открой Airflow и кнопкой **Trigger DAG** запусти `world_next_day` с пустой формой. +Он дозальёт в Kafka следующий день, а ClickHouse прочитает новые сообщения уже с +изменённой схемой. Такой прогон занимает несколько минут — дождись состояния +`success`. Здесь мы только нажимаем готовую кнопку; подробно `world_next_day` +разберём в лабе 07. **Смотрим результат:** ```sql SELECT kafka_ts, kafka_msg_ts FROM stg.browser_raw +WHERE kafka_msg_ts > toDateTime(0) ORDER BY kafka_offset LIMIT 5; ``` @@ -224,19 +214,8 @@ LIMIT 5; > теряется. Обратный случай — колонку добавил, а в MV не указал — тоже не упадёт: поле > заполнится дефолтом. Вывод: за синхронность схемы и MV отвечаешь ты, а не движок. -**Верни как было** (откат — тоже две операции, и порядок важен): - -```sql -DROP VIEW stg.mv_kafka_browser_to_stg; -- сначала MV, что ссылается на колонку -ALTER TABLE stg.browser_raw DROP COLUMN kafka_msg_ts; -``` - -```bash -make ddl # пересоздаёт эталонное MV из 10_stg.sql, схема снова как в репозитории -``` - -Если запутался в состоянии — всегда есть полный чистый прогон: -`make generated-history-analytics && make up`. +**Верни как было.** После дозаливки мир уже вырос, поэтому верни схему и данные +[каноническим сбросом](../README.md#подготовка-и-канонический-сброс). --- @@ -244,10 +223,10 @@ make ddl # пересоздаёт эталонное MV из 10_stg.sql, с | Действие | Где смотреть | Что ожидать | |----------|--------------|-------------| -| `make generated-history-analytics && make up` | `SELECT count() FROM stg.browser_raw` | счётчик > 0 | +| каноническая подготовка | `SELECT count() FROM stg.browser_raw` | счётчик > 0 | | глянуть строку | `SELECT raw FROM stg.browser_raw LIMIT 1` | валидный JSON целиком, неразобранный | | глянуть offset'ы | `SELECT kafka_offset FROM stg.browser_raw ORDER BY kafka_offset` | идут по возрастанию, без дублей | -| правка из секции 4 | `SELECT kafka_msg_ts FROM stg.browser_raw LIMIT 5` | колонка заполнена временем сообщения | +| `world_next_day` после правки из секции 4 | `SELECT kafka_msg_ts FROM stg.browser_raw WHERE kafka_msg_ts > toDateTime(0) LIMIT 5` | новые строки заполнены временем сообщения | --- diff --git a/docs/course/lessons/02_stg_to_ods.md b/docs/course/lessons/02_stg_to_ods.md index 6c75c39..b4e5f1f 100644 --- a/docs/course/lessons/02_stg_to_ods.md +++ b/docs/course/lessons/02_stg_to_ods.md @@ -84,19 +84,12 @@ ODS мы пересобираем целиком, одной задачей Airf ## 2. Руки: смотрим базовый прогон -Поднимаем стенд и создаём стартовую историю. Это штатный путь курса: готовый источник -данных стенда пишет события в Kafka, ClickHouse читает их в STG, затем batch строит -ODS, DDS и DM. Файлы `data/*.jsonl` пока остаются только кладовкой значений для этого -источника, а не источником аналитического контура. +Подготовь стенд по +[канонической инструкции курса](../README.md#подготовка-и-канонический-сброс). +После успешного `world_init` эталонный мир уже прошёл путь Kafka → STG → ODS → DDS → DM. -```bash -make generated-history-analytics -make up -``` - -Команда прогоняет всю цепочку слоёв и прямо в консоли печатает то, что нам нужно сейчас, — -блок **«Статистика ODS»**. Это просто счётчики строк по всем восьми таблицам слоя (четыре -основных и четыре с ошибками). Пример формы вывода: +Ниже — форма блока **«Статистика ODS»**, который печатает `make transform`. Это просто +счётчики строк по всем восьми таблицам слоя (четыре основных и четыре с ошибками): ``` Статистика ODS: @@ -111,12 +104,14 @@ make up │ ods.geo_by_click_errors │ 0 │ └────────────────────────────┴──────┘ ``` + Прочитаем эту табличку — в ней три вещи, которые стоит заметить. **Все четыре `*_errors` — по нулям.** Значит, стартовая история чистая: ни одна запись не дала ошибки разбора, столбец `parse_errors` у всех пустой. Это нормально — данные стенда аккуратные. Ошибки мы увидим в секции 4, когда сами их устроим. + **`browser` и `location` идут в одном зерне события.** Сколько событий пришло, столько строк и ожидаем увидеть после типизации, если ключи валидны. @@ -254,6 +249,7 @@ ORDER BY (click_id) её через `toFloat64OrNull` — «привести к дробному числу». Заменим тип на целочисленный — `toInt64OrNull`, «привести к целому». Для строки `"50.82709"` целого числа не получится (там точка, дробная часть), и функция вернёт `NULL`. То есть широта просто исчезнет. + Из секции 3 помним: разбор продублирован, поэтому правок будет **две** — в обоих `INSERT` блока `GEO EVENTS`. Открой `sql/ods/20_stg_to_ods.sql`, найди оба вхождения и в каждом замени @@ -293,6 +289,7 @@ LIMIT 4; │ 9ffd819b-... │ ᴺᵁᴸᴸ │ 85.37752 │ ['bad_geo_latitude'] │ └──────────────┴──────────────┴───────────────┴──────────────────────┘ ``` + Вот теперь видно всё разом — и DQ-split, и «двойной учёт» из секции 3 вживую. Строки с валидным `click_id` остались в основной таблице с пометкой `bad_geo_latitude`. И те же записи попали в @@ -307,6 +304,7 @@ LIMIT 4; > это молча срезало миллисекунды, и время по всему стенду уехало в `1970-01-21`. Ничто на это > не указывало — поймали только прогоном на стенде. Мораль урока: тип выбирают осознанно, даже > когда функция «не падает». + **Верни как было.** Откати обе правки — верни `toFloat64OrNull` в оба места. Если запутался, проще одной командой откатить весь файл к версии из репозитория: @@ -316,8 +314,10 @@ git checkout -- sql/ods/20_stg_to_ods.sql make transform ``` -После этого `geo_by_click_errors` снова `0`, широта на месте. А если стенд совсем «поплыл» — -всегда есть полный чистый прогон: `make generated-history-analytics && make up`. +После этого `geo_by_click_errors` снова `0`, широта на месте. А если стенд совсем +«поплыл», пройди +[канонический сброс](../README.md#подготовка-и-канонический-сброс). + --- @@ -329,6 +329,7 @@ make transform | почему `device`/`geo` меньше событий | запрос `uniqExact(click_id)` по `stg.geo_raw` | число различных `click_id` совпадает с `ods.geo_by_click` | | правка из секции 4 | блок «Статистика ODS» | `ods.geo_by_click_errors` прыгнул с `0` на ненулевое число | | правка из секции 4 | `SELECT geo_latitude, parse_errors FROM ods.geo_by_click` | широта `NULL`, в `parse_errors` — `bad_geo_latitude` | + --- @@ -339,6 +340,7 @@ make transform - скрин блока «Статистика ODS», где после правки `ods.geo_by_click_errors` ушёл с `0` на ненулевое число; - либо выборка из `ods.geo_by_click` с пустой широтой и меткой `bad_geo_latitude` рядом. + И проверь себя на словах — примерно эти вопросы всплывут на еженедельном созвоне: diff --git a/docs/course/lessons/03_ods_to_dds.md b/docs/course/lessons/03_ods_to_dds.md index 495c10e..f965f85 100644 --- a/docs/course/lessons/03_ods_to_dds.md +++ b/docs/course/lessons/03_ods_to_dds.md @@ -65,18 +65,12 @@ ## 2. Руки: смотрим базовый прогон -Поднимаем стенд и создаём стартовую историю. Это штатный путь курса: готовый источник -данных стенда пишет события в Kafka, ClickHouse читает их в STG, затем batch строит -ODS, DDS и DM. Файлы `data/*.jsonl` пока остаются только кладовкой значений для этого -источника, а не источником аналитического контура. +Подготовь стенд по +[канонической инструкции курса](../README.md#подготовка-и-канонический-сброс). +После успешного `world_init` эталонный мир уже прошёл путь Kafka → STG → ODS → DDS → DM. -```bash -make generated-history-analytics -make up -``` - -Команда прогоняет всю цепочку слоёв и по дороге печатает в консоль блок **«Статистика DDS»** — -счётчики строк по двум нашим сущностям: +Ниже — форма блока **«Статистика DDS»**, который печатает `make transform`: счётчики +строк по двум нашим сущностям. ``` Статистика DDS: @@ -104,6 +98,7 @@ make up │ 2026-06-05 │ dds │ event_without_click │ orphan_events │ 0 │ └────────────┴───────┴─────────────────────┴───────────────┴─────────────┘ ``` + В колонке `check_date` стоит `today()` из кода витрины, так что у тебя там будет сегодняшняя дата — не пугайся, если она не совпадёт с примером. @@ -111,6 +106,7 @@ make up `orphan_events = 0` — ни одной сироты. Каждое событие нашло свой клик в `dds.click`. На чистой стартовой истории так и должно быть: данные аккуратные, ничего не потерялось. В секции 4 мы сироту устроим сами — и эта строка оживёт. + Проверь нолик сам, не верь на слово. Открой SQL-консоль `http://localhost:9123/play` (пользователь `default`, пароль `123456`) и посчитай сирот напрямую: @@ -126,6 +122,7 @@ WHERE click_id IS NOT NULL `NOT IN (SELECT ...)` читается прямо по словам: «click_id события **не входит** в список всех click_id из `dds.click`». То есть событие ссылается на клик, которого в карточках кликов нет. Сейчас таких ноль — запомни этот запрос, в секции 4 он покажет другое число. + --- @@ -308,8 +305,10 @@ DDS — `make transform` чистит `dds.event` (`TRUNCATE`) и наполня make transform ``` -После этого `orphan_events` снова `0`, придуманное событие исчезло. А если стенд совсем «поплыл» — -полный чистый прогон: `make generated-history-analytics && make up`. +После этого `orphan_events` снова `0`, придуманное событие исчезло. А если стенд +совсем «поплыл», пройди +[канонический сброс](../README.md#подготовка-и-канонический-сброс). + --- @@ -322,6 +321,7 @@ make transform | почему `click` меньше `event` | запрос `count()` по `dds.click` и `dds.event` | много событий ссылаются на меньшее число кликов — норма | | правка из секции 4 (вставили сироту) | запрос `count()` сирот в play-консоли | `0 → 1` | | та же сирота через `dm.v_events_enriched` | `SELECT device_type, geo_country ...` | поля клика пустые (`NULL`) — это `LEFT JOIN` | + --- @@ -332,6 +332,7 @@ make transform - скрин запроса со счётчиком сирот: было `0`, после вставки стало `1`; - либо выборка из `dm.v_events_enriched` по событию-сироте, где `device_type` и `geo_country` пусты, — `LEFT JOIN` сохранил событие без клика. + И проверь себя на словах — примерно эти вопросы всплывут на еженедельном созвоне: diff --git a/docs/course/lessons/04_airflow_orchestration.md b/docs/course/lessons/04_airflow_orchestration.md index a62d3e8..704e5da 100644 --- a/docs/course/lessons/04_airflow_orchestration.md +++ b/docs/course/lessons/04_airflow_orchestration.md @@ -56,15 +56,19 @@ Airflow. Главная единица Airflow — **DAG** (Directed Acyclic Gra ## 2. Руки: запускаем DAG и смотрим зелёный прогон -Подними стенд и создай стартовую историю. Это штатный путь курса: готовый источник -данных стенда пишет события в Kafka, ClickHouse читает их в STG, затем batch строит -ODS, DDS и DM. Файлы `data/*.jsonl` пока остаются только кладовкой значений для этого -источника, а не источником аналитического контура. +Подготовь стенд по +[канонической инструкции курса](../README.md#подготовка-и-канонический-сброс). +После успешного `world_init` эталонный мир уже прошёл путь Kafka → STG → ODS → DDS → DM. -```bash -make generated-history-analytics -make up -``` +Перед разбором `etl_pipeline` вспомни пульт курса как лесенку: + +1. `ddl_init` создаёт схему ClickHouse; +2. `world_init` импортирует эталонный мир и запускает его обработку; +3. `world_next_day` дозаливает следующий день и снова запускает обработку. + +Первые две ступени ты уже прошёл при подготовке. Третью пока только запомни: подробно +её разберём в лабе 07. Внутри двух последних ступеней работает тот самый +`etl_pipeline`, который мы сейчас откроем отдельно. Открой Airflow: `http://localhost:8080` (логин `admin`, пароль `admin`). Найди DAG `etl_pipeline` и запусти его через **Trigger DAG with config**: @@ -97,6 +101,7 @@ WHERE layer = 'dds' Ожидаем `check_value = 0`. Это тот же смысл, что в уроке 3, только теперь число появилось внутри управляемого прогона Airflow. + --- @@ -307,15 +312,9 @@ WHERE click_id IS NOT NULL AND click_id NOT IN (SELECT click_id FROM dds.click); ``` -Снова должно быть `0`. Если стенд после экспериментов совсем запутался, сделай штатный -чистый прогон: - -```bash -make generated-history-analytics -make up -``` - -После этого при необходимости запусти `etl_pipeline` с `{"full_refresh": true}`. +Снова должно быть `0`. Если стенд после экспериментов совсем запутался, пройди +[канонический сброс](../README.md#подготовка-и-канонический-сброс). + --- @@ -328,6 +327,7 @@ make up | вставка события-сироты | прямой SQL-счётчик сирот | `0 → 1` | | `etl_pipeline` с `{"full_refresh": false}` после вставки | task `transform.assert_dds_integrity` | task красная, DAG failed | | откат через `{"full_refresh": true}` | прямой SQL-счётчик сирот | снова `0` | + --- diff --git a/docs/course/lessons/05_monitoring.md b/docs/course/lessons/05_monitoring.md index 0555a1c..64980cf 100644 --- a/docs/course/lessons/05_monitoring.md +++ b/docs/course/lessons/05_monitoring.md @@ -57,24 +57,10 @@ ## 2. Руки: открываем дашборды и targets -Подними стенд и создай стартовую историю, если он ещё не поднят. Это штатный путь курса: -готовый источник данных стенда пишет события в Kafka, ClickHouse читает их в STG, затем -batch строит ODS, DDS и DM. Файлы `data/*.jsonl` пока остаются только кладовкой значений -для этого источника, а не источником аналитического контура. - -```bash -make generated-history-analytics -make up -``` - -Учти: путь `generated-history-analytics` собирает витрины напрямую, без запуска -`etl_pipeline`, поэтому панели про задачи Airflow в `Airflow Overview` останутся -пустыми, пока ты хотя бы раз не запустишь `etl_pipeline` сам (это делалось в -уроке 4). Запустить его можно в Airflow с конфигом: - -```json -{"full_refresh": true} -``` +Подготовь стенд по +[канонической инструкции курса](../README.md#подготовка-и-канонический-сброс). +После успешного `world_init` эталонный мир уже прошёл путь Kafka → STG → ODS → DDS → DM, +а в Airflow есть завершённый прогон `etl_pipeline`. Нам нужны не идеальные объёмы, а живой стенд, в котором есть Kafka-топики, строки в ClickHouse и хотя бы один прогон Airflow. @@ -92,10 +78,10 @@ make up | `airflow` | `statsd-exporter:9102` | `statsd-exporter` отдаёт метрики Airflow в формате Prometheus | | `generator` | `generator:9109` | live-генератор отдаёт свои метрики, только когда явно запущен | -У `clickhouse`, `kafka` и `airflow` состояние должно быть `UP`. `generator` на штатном -backfill-only стенде может быть `DOWN`, потому что `make up` не запускает live-генератор. -Это нормально для курса до явного `make generator-continue`. Если один из трёх основных -target `DOWN`, Grafana дальше будет показывать `No data` или старые значения. +У `clickhouse`, `kafka` и `airflow` состояние должно быть `UP`. `generator` на базе +импортированного мира может быть `DOWN`: живой поток включается отдельно через +`make generator-continue`. Если один из трёх основных target `DOWN`, Grafana дальше +будет показывать `No data` или старые значения. То же можно проверить из терминала: @@ -125,7 +111,7 @@ curl -s http://localhost:9090/api/v1/targets | grep -o '"health":"[^"]*"' - `ClickHouse Overview`; - `Kafka Overview`; - `Airflow Overview`; -- `Generator Overview` — про live-генератор; на backfill-only стенде он пуст, +- `Generator Overview` — про живой поток; на базе импортированного мира он пуст, как и target `generator` выше, и в этом уроке не понадобится. Открой каждый и смотри не на красоту графиков, а на смысл: какой слой стенда он показывает и @@ -272,11 +258,11 @@ Grafana. | Airflow Alerts | `High Task Failure Rate` | растёт rate failed tasks | | Airflow Alerts | `High DAG Parse Time` | DAG-файлы долго парсятся | -Не все эти правила обязаны быть тихими в учебном стенде. По умолчанию `make up` и -`make generated-history-analytics` **не запускают live-генератор**, поэтому после готовой -стартовой истории новые сообщения перестают приходить. Из-за этого `Kafka No Messages Produced` -может перейти в `Alerting` на полностью здоровом backfill-only стенде. Если хочешь проверить -это правило в спокойном состоянии, явно включи live: +Не все эти правила обязаны быть тихими в учебном стенде. После импорта эталонной базы +живой поток не запущен, поэтому новые сообщения не приходят. Из-за этого +`Kafka No Messages Produced` может перейти в `Alerting` на полностью здоровом стенде: +алерт честно говорит, что потока сейчас нет, а не что импорт сломан. Если хочешь +проверить правило в спокойном состоянии, явно включи живой поток: ```bash make generator-continue diff --git a/docs/course/lessons/06_superset_bi.md b/docs/course/lessons/06_superset_bi.md index 1addf96..12c07af 100644 --- a/docs/course/lessons/06_superset_bi.md +++ b/docs/course/lessons/06_superset_bi.md @@ -63,17 +63,10 @@ DM-витрина — это SQL-объект в ClickHouse. Она задаёт ## 2. Руки: запускаем Superset и смотрим дашборд -Подними стенд и создай стартовую историю. Это штатный путь курса: готовый источник -данных стенда пишет события в Kafka, ClickHouse читает их в STG, затем batch строит -ODS, DDS, DM и обновляет Superset. Файлы `data/*.jsonl` пока остаются только кладовкой -значений для этого источника, а не источником аналитического контура. - -```bash -make generated-history-analytics -make up -``` - -Команда уже прогоняет цепочку STG → ODS → DDS → DM и создаёт metadata Superset: +Подготовь стенд по +[канонической инструкции курса](../README.md#подготовка-и-канонический-сброс). +После успешного `world_init` эталонный мир уже прошёл путь Kafka → STG → ODS → DDS → DM, +а `make superset-init` создал метаданные Superset: - подключение `clickhouse_dwh`; - 6 datasets поверх `dm.*`.