From b15ee9093924bd52a92feb97890209e767e5013a Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Wed, 3 Jun 2026 23:25:23 +0300 Subject: [PATCH] =?UTF-8?q?docs(course):=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=83=D1=80=D0=BE=D0=BA=200=20(=D0=B2?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=D0=BD=D1=8B=D0=B9=20=D0=BF=D0=BE=20Kafka,=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B1=D0=BB=D1=8E=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - курсу нужна разминка перед уроком 1: связать словарь из обзорного видео по Kafka (топик, партиция, offset, группа, lag) с живым стендом. - Что: - добавлен lessons/00_kafka_intro.md по LESSON_STANDARD в режиме наблюдения (без управляемой правки и отката), эталон голоса — урок 1. - честная врезка про lag: у групп ch_stg_* колонки offset/lag в Kafka UI пустые, прогресс чтения смотреть в ClickHouse (system.kafka_consumers). - README курса: в строке lessons теперь указаны уроки 0 и 1. - Проверка: - факты сверены на живом кластере: 4 топика *_events по 1 партиции, 4 группы ch_stg_* (STABLE, 1 участник); прогоном подтверждено, что новая группа читает топик с начала (auto.offset.reset=earliest). --- docs/course/README.md | 2 +- docs/course/lessons/00_kafka_intro.md | 143 ++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 docs/course/lessons/00_kafka_intro.md diff --git a/docs/course/README.md b/docs/course/README.md index 012c506..e416c77 100644 --- a/docs/course/README.md +++ b/docs/course/README.md @@ -18,7 +18,7 @@ | [`PRD.md`](./PRD.md) | Рамка: зачем курс, цели, аудитория, скоуп, критерии успеха | Чтобы понять «что и зачем». Замороженный документ | | [`LEARNING_PLAN.md`](./LEARNING_PLAN.md) | План обучения: карта уроков, маршрут, аудит эталонных путей | Чтобы понять «в каком порядке и из чего» | | [`LESSON_STANDARD.md`](./LESSON_STANDARD.md) | Стандарт уроков: шаблон урока, качество кода, самопроверка | Рабочий чеклист при написании каждого урока | -| [`lessons/`](./lessons/) | Сами уроки, по одному файлу (есть: урок 1) | Прохождение курса менти | +| [`lessons/`](./lessons/) | Сами уроки, по одному файлу (есть: уроки 0 и 1) | Прохождение курса менти | ## Порядок чтения diff --git a/docs/course/lessons/00_kafka_intro.md b/docs/course/lessons/00_kafka_intro.md new file mode 100644 index 0000000..aff8b1e --- /dev/null +++ b/docs/course/lessons/00_kafka_intro.md @@ -0,0 +1,143 @@ +# Урок 0. Вводный по Kafka (Kafka UI, наблюдение) + +> Статус: черновик. Режим: **наблюдение** (ничего не меняем, только смотрим). +> Пререквизит: обзорное видео по Kafka из роадмапа — оттуда ты уже знаешь слова +> «топик», «партиция», «offset», «consumer-группа», «lag». Этот урок связывает их +> с живым стендом, чтобы они перестали быть просто словами. +> Эталонный путь: сам Kafka UI на `http://localhost:8082`. +> +> Поток данных одной строкой: +> `make data → топики Kafka (партиции, offset'ы) → consumer-группа ClickHouse вычитывает` + +--- + +## 1. Зачем и где в проде + +В обычной кликстрим-аналитике между тем, кто **порождает** события (трекер на сайте, в +приложении), и тем, кто их **считает** (хранилище, BI), почти всегда стоит Kafka. Она — +буфер и общий журнал: producer пишет события, не зная и не дожидаясь, кто их прочитает; +consumer читает в своём темпе, не трогая producer'а. Если читатель отстал или прилёг — +события не теряются, они лежат в топике и ждут. + +На нашем стенде роли уже расставлены: `make data` играет роль трекера и заливает события +в топики, а ClickHouse — это consumer, который их вычитывает. В этом уроке мы не запускаем +пайплайн и ничего не меняем — мы открываем Kafka UI и **узнаём в живом кластере** те самые +понятия из видео. Это разминка: в уроке 1 ты уже руками увидишь, как эти же сообщения +доезжают до таблиц ClickHouse. + +> **В проде так же, только крупнее.** Здесь один брокер и крошечный срез данных. В бою +> брокеров несколько, топик разбит на много партиций, читателей в группе — тоже несколько, +> и Kafka сама делит партиции между ними. Понятия при этом ровно те же, что мы сейчас +> разглядим на маленьком кластере. + +--- + +## 2. Наблюдай: открой Kafka UI + +Стенд уже должен быть поднят (`make up`) и в топиках должны лежать события +(`LIMIT=50 make data` из урока 1 — или любой прошлый прогон). Открой Kafka UI: +`http://localhost:8082`. Ходи по нему свободно — это режим чтения, сломать тут ничего нельзя. + +Пройди по трём экранам и просто посмотри. + +**Топики** (раздел *Topics*). Увидишь четыре топика событий — по одному на тип: + +- `browser_events` — события браузера (pageview и т.п.); +- `device_events` — про устройство; +- `geo_events` — гео; +- `location_events` — местоположение. + +Рядом может быть служебный топик `__consumer_offsets` (если включён показ внутренних +топиков) — его Kafka использует сама, мы его не трогаем. У каждого нашего топика в колонке +с партициями стоит **1**: топик маленький, делить не на что. + +**Сообщения в топике.** Открой `browser_events` → вкладку *Messages*. Это и есть события, +которые залил `make data`. У каждого сообщения видно: + +- **Offset** — порядковый номер сообщения в партиции (0, 1, 2, …); +- **Timestamp** — когда сообщение легло в Kafka (время *доставки*, не время самого события); +- **Value** — тело: JSON события целиком, например: + +```json +{"event_id": "8cca1c7d-...", "event_timestamp": "2022-11-28 20:51:05.627882", + "event_type": "pageview", "browser_name": "Chrome", "browser_language": "sat_IN"} +``` + +Загляни внутрь Value: у события есть своё `event_timestamp` (когда оно случилось, +здесь — 2022 год), и оно отличается от Kafka-Timestamp (когда оно попало в топик — при заливке стенда). +Два разных времени у одной записи — запомни этот момент, в уроке 1 он всплывёт уже на +стороне ClickHouse. + +**Consumer-группы** (раздел *Consumers*). Здесь видно, **кто читает** топики. Найдёшь +четыре группы — по одной на топик: + +- `ch_stg_browser`, `ch_stg_device`, `ch_stg_geo`, `ch_stg_location`. + +Это ClickHouse: он подключился к Kafka как читатель и для каждого топика завёл отдельную +группу. У каждой статус **STABLE** и **1 участник** (member) — тот самый единственный +consumer ClickHouse. + +--- + +## 3. Загляни глубже: что значат эти экраны + +Сложим увиденное в понятия — те же, что в видео, но теперь привязанные к экрану. + +| Понятие | Что это | Где увидел в Kafka UI | +|---------|---------|------------------------| +| **Топик** | именованный поток событий одного типа | `browser_events` и три соседних | +| **Партиция** | часть топика, внутри которой строгий порядок | у нас по 1 на топик | +| **Offset** | номер сообщения внутри партиции, по нему адресуют запись | колонка *Offset* в *Messages* | +| **Consumer-группа** | читатели топика; группа помнит, до какого offset'а дочитали | `ch_stg_*` в разделе *Consumers* | +| **Lag** | отставание: сколько в топике уже есть, но ещё не прочитано | колонка пустая — см. врезку ниже | + +Главная мысль: **offset — это адрес сообщения, а не его содержимое**. Пара «партиция + +offset» однозначно показывает на конкретную запись в топике. Именно по offset'у +consumer-группа помнит, где остановилась: дочитала до 19 — в следующий раз начнёт с 20, +а не сначала. Поэтому имя группы — не косметика: смени его, и читатель начнёт топик заново +(это ты потрогаешь руками в уроке 1, там группа задаётся в DDL как `kafka_group_name`). + +**Lag** (отставание) — это, по сути, простая арифметика: сколько сообщений в топике уже +есть, минус сколько группа успела прочитать. Ноль — читатель идёт вровень с потоком; растёт +— читатель не успевает. + +> **Маленькая честность про lag.** У групп `ch_stg_*` колонки offset и lag в Kafka UI +> окажутся **пустыми**, хотя сама группа живая (`STABLE`, 1 участник). Это не баг и не +> поломка: так уж ClickHouse-движок работает с группой — свой прогресс чтения он держит +> **на своей стороне** (его видно в системной таблице `system.kafka_consumers`), и именно +> там стоит смотреть, не отстаёт ли он. Не удивляйся пустой ячейке offset/lag в Kafka UI; +> проверять отставание ClickHouse удобнее с его конца — этим и займёшься в уроке 1. + +--- + +## 4. Проверь себя + +| Действие | Где смотреть | Что ожидать | +|----------|--------------|-------------| +| открыть список топиков | Kafka UI → *Topics* | 4 топика событий (`*_events`), у каждого 1 партиция | +| открыть `browser_events` | вкладка *Messages* | сообщения с offset'ами 0, 1, 2, …; в Value — JSON события | +| сравнить два времени | Value (`event_timestamp`) vs Kafka *Timestamp* | время события и время доставки в Kafka различаются | +| открыть consumers | Kafka UI → *Consumers* | 4 группы `ch_stg_*`, статус `STABLE`, по 1 участнику | + +--- + +## 5. Что должно получиться + +После урока у тебя в голове — карта живого кластера: четыре топика событий, в каждом +сообщения с offset'ами, и четыре consumer-группы ClickHouse, которые их читают. Ничего +не меняли — только сопоставили слова из видео с экранами Kafka UI. + +И проверь себя на словах: сможешь объяснить, чем **топик** отличается от **партиции**, что +такое **offset** и зачем consumer-группе его помнить? Короткая зацепка для ответа — что +произойдёт, если читатель забудет свой offset и начнёт топик сначала? Примерно такие вопросы +по теме урока всплывут на еженедельном созвоне. + +--- + +## Мост к уроку 1 + +Мы посмотрели на поток **со стороны Kafka**: события лежат в топиках, ClickHouse подключён +к ним группами и что-то вычитывает. Логичный следующий вопрос: **а куда именно ClickHouse +кладёт прочитанное и как он это делает?** Об этом — урок 1: там ты руками увидишь, как +сообщение из топика превращается в строку таблицы ClickHouse, и заодно посмотришь на чтение +Kafka «с другого конца».