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