Модуль 3. Инциденты Kafka: rebalance storm, ISR shrink, disk full — разбор и лечение
Цель модуля: вести инцидент Kafka от симптома к причине по метрикам Модуля 2. Шесть канонических отказов, каждый — симптом → метрика → причина → лечение, и все воспроизводятся на трёхузловом стенде.
Перед началом: Модули 1–2, стенд с
make kill-broker. Трудоёмкость: ~10–12 ч (разборы 5 ч · воспроизведения 5 ч · квиз 1 ч).
📖 3.1. Методика: очередь или кластер?
Как и в курсе Redis, первый вопрос дежурного разделяет поиск:
- Lag растёт, продюсер жив (messages-in стабильный) — проблема консьюмеров: обработка, rebalance, партиции;
- Messages-in просел / продюсеры ловят ошибки — проблема кластера: ISR, лидеры, диск, сеть;
- Что меняли? Новый деплой воркера с «улучшенным» батчингом — кандидат №1 в любом списке.
📖 3.2. Инцидент «Rebalance storm»: группа живёт вхолостую
Симптом: каждые пару минут — пауза обработки на 10–30 секунд;
lag растёт зубцами и догоняется. В логах консьюмеров —
Attempt to heartbeat failed и rejoining group, SyncGroup.
Метрики: lag — пила; members группы мигают 2→1→2; URP = 0 (кластер ни при чём).
Причина: консьюмер выбрасывается из группы раньше, чем завершает работу. Два таймаута решают всё:
session.timeout.ms(по умолчанию 45 с) — «я жив» через heartbeat; если обработка идёт в poll-потоке и блокирует heartbeat — группу покидает живой консьюмер;max.poll.interval.ms(5 мин) — «я ещё работаю» через poll; превышение — консьюмер уходит добровольно, группа перебалансируется.
Шторм рождается, когда max.poll.records большой (за poll взял 500
сообщений по 200 мс = 100 с), обработка не укладывается в интервал —
и так по кругу: rebalance → перекачка партиций → пауза → снова.
Лечение:
- уменьшить
max.poll.records(или объём фетча) так, чтобы «poll → обработка → commit» укладывался вmax.poll.interval.msс запасом ×3; session.timeout.msподнять, а heartbeat — из отдельного потока (kafka-goшлёт heartbeat отдельно — тогда session может быть честно мал);- static membership (
group.instance.id; в kafka-go —InstanceID) — рестарт воркера не устраивает rebalance всего: группа ждёт вернувшегося члена вместо перетасовки партиций; - cooperative sticky-протокол — инкрементальные rebalance вместо stop-the-world (все партиции → никому → всем).
📖 3.3. Инцидент «Lag растёт, все живы»: три лица одной метрики
Симптом: lag группы растёт линейно; консьюмеры на месте, ошибок нет.
Разбор по lag×partition (Модуль 2, §2.3) различает три случая:
- Lag растёт равномерно по всем партициям — обработка медленнее продюсера. Считайте rate входа против rate обработки. Лечение: масштабировать консьюмеров до числа партиций (дальше — только партиции), ускорить обработку (батчи в БД, асинхронный I/O), поднять партиции — с оговоркой: новые партиции получают только новые ключи;
- Lag растёт по одной партиции — горячая партиция: ключ-константа (userId=admin), один горячий клиент. Лечение — чинить ключ (соль, составной ключ), а не наращивать консьюмеров: параллельность упирается в одну партицию;
- Lag растёт по партициям одного брокера — брокер деградирует (диск/сеть/GC): смотрите URP и request latency этого узла; лечится узлом, а не консьюмерами.
📖 3.4. Инцидент «Запись падает: NOT_ENOUGH_REPLICAS»
Симптом: продюсеры ловят NOT_ENOUGH_REPLICAS при acks=all;
запись в топик невозможна, хотя «у нас же RF=3».
Метрики: URP > 0, kafka_brokers < ожидаемого (упал брокер),
IsrShrinksPerSec подскочил.
Причина: связка acks=all + min.insync.replicas=2 — контракт
«пишем только когда живы ≥2 реплики». Упал один брокер из трёх —
для партиций, у которых в ISR осталась одна реплика, запись закрывается:
это не баг, а предохранитель от потери (сравните с
min-replicas-to-write в Redis — Модуль 2 курса Redis).
Лечение:
- краткосрочно: вернуть брокера (
make up-broker); ISR восстановится сам, запись возобновится; - стратегия RF=3 + min.insync=2 правильна (терпит один отказ); RF=2 + min.insync=1 «чтобы не падало» — скрытая готовность терять данные: при отказе единственной синхронной реплики — unclean leader election и потеря хвоста;
unclean.leader.election.enable=falseдержим false и не трогаем.
Родственный инцидент — «ISR shrink без отказа узла»: реплики
вылетают из ISR на живом кластере (replica.lag.time.max.ms):
медленный диск, GC-паузы, сеть. IsrShrinks/Expands «дышит» — ищите
узел-аутсайдера по request latency и IO wait.
📖 3.5. Инцидент «Disk full»: retention, который не освобождает
Симптом: диск брокера 95 %; продюсеры начали ловить ошибки записи.
Метрики: disk usage (по брокерам), messages-in падает на одном узле.
Причина: retention освобождает место только целыми сегментами:
активный сегмент не удаляется никогда; маленькие segment.bytes при
большом числе партиций множат оверхед; retention.ms наступает «по
расписанию», а не «по необходимости». Плюс классика: после чистки место
на файловой системе не вернулось — удалённые сегменты ещё открыты
процессом (lsof | deleted).
Лечение:
- плановое: disk budget = retention × throughput × запас 30 %; алерт на 75 %, не на 95 %;
- аварийное: уменьшить retention по топику
(
kafka-configs.sh --alter --topic X --add-config retention.ms=...) и дождаться roll сегментов; как последнее — перенос партиций (reassignment) на свободные узлы; - профилактика:
retention.bytesкак потолок на случай всплеска трафика;segment.bytes/segment.ms— под ваш паттерн чтения.
📖 3.6. Инцидент «Пропали/задублировались сообщения»: офсеты
Симптом: «одно письмо пришло дважды», «часть заказов не обработалась». Кластер здоров, lag в нуле.
Причина: контракт офсетов у консьюмера нарушен вашим кодом:
enable.auto.commit=true(дефолт) коммитит по таймеру, а не по факту: упали после обработки, но до коммита — повтор; закоммитили вперёд обработки — потеря на рестарте;auto.offset.reset=latestу новой группы — «пропустили всё, что было до старта»: выглядит как «пропали сообщения»;- ручной commit по офсету последнего прочитанного, а не обработанного сообщения.
Лечение — контур из «Фундамента» (М1–М2):
- autocommit off; commit после обработки (at-least-once);
- идемпотентный обработчик (дедуп по ключу) — повторы легальны;
auto.offset.reset=earliestосознанно: новым группам, которым нужна история;- считайте повторы метрикой — «дублей N» при нулевых потерях это норма, а не дефект.
💻 Практика
Все шесть разборов — на трёхузловом стенде (make up-obs):
- Rebalance storm: консьюмер с большим poll-батчем и обработкой
150 мс/сообщение → шторм (лог + пила lag). Починить: уменьшить
батч +
InstanceID; убедиться, что рестарт воркера не устраивает общий rebalance. - Горячая партиция: ключ-константа → lag по одной партиции; вернуть составной ключ.
- NOT_ENOUGH_REPLICAS:
make kill-brokerприacks=all,min.insync.replicas=2→ продюсер ловит ошибку;make up-broker→ запись восстановилась. Запишите время недоступности. - Unclean leader election (демо-режим): уменьшите на стенде
replica.lag.time.max.ms, остановите реплику, дождитесь вылета из ISR, уберите лидера и включитеunclean.leader.election.enable=true— увидеть «пропавший хвост» один раз достаточно, чтобы никогда не включать это в проде. - Disk full (симулятор): топик с
retention.bytes=1MB,segment.bytes=100KB; гоняйте трафик — смотрите, как сегменты роллятся и освобождаются. - Офсеты: autocommit on + падение после обработки → дубль; autocommit off + commit после → потери нет, дубль контролируется идемпотентным обработчиком (задача из «Фундамента»).
Артефакт: журнал шести воспроизведений — «симптом / ведущая метрика / команда воспроизведения / лечение».
🧩 Quiz "Инцидент-квиз: причина по симптому"
✅ Итоги модуля
| Инцидент | Ведущая метрика | Ядро причины |
|---|---|---|
| Rebalance storm | пила lag + мигание members | poll-обработка vs таймауты группы; лечится батчем/InstanceID/cooperative |
| Lag без деградации кластера | lag×partition | обработка / горячая партиция / партиции одного брокера |
| NOT_ENOUGH_REPLICAS | URP + kafka_brokers | min.insync — предохранитель, а не баг |
| ISR shrink на живом кластере | IsrShrinks «дыхание» | медленный узел: диск/GC/сеть |
| Disk full | disk usage | сегменты целиком + бюджет retention |
| Дубли/потери сообщений | метрика дублей | автокоммит и порядок commit↔обработка |
🔗 Куда дальше
Эксплуатационная глубина курса собрана: модель (М1) → приборы (М2) → инциденты (М3). Дальше — Модуль 4: тюнинг под нагрузкой и exactly-once — цифры linger/batch/compression и честный ответ, когда транзакции Kafka нужны по-настоящему. А следующий курс трека — RabbitMQ с той же тройкой «модель → мониторинг → инциденты».