Модуль 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) различает три случая:

  1. Lag растёт равномерно по всем партициям — обработка медленнее продюсера. Считайте rate входа против rate обработки. Лечение: масштабировать консьюмеров до числа партиций (дальше — только партиции), ускорить обработку (батчи в БД, асинхронный I/O), поднять партиции — с оговоркой: новые партиции получают только новые ключи;
  2. Lag растёт по одной партиции — горячая партиция: ключ-константа (userId=admin), один горячий клиент. Лечение — чинить ключ (соль, составной ключ), а не наращивать консьюмеров: параллельность упирается в одну партицию;
  3. 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.

Два циклических инцидента: rebalance storm из-за превышения max.poll.interval.ms и предохранитель NOT_ENOUGH_REPLICAS при сжатии ISR

📖 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):

  1. Rebalance storm: консьюмер с большим poll-батчем и обработкой 150 мс/сообщение → шторм (лог + пила lag). Починить: уменьшить батч + InstanceID; убедиться, что рестарт воркера не устраивает общий rebalance.
  2. Горячая партиция: ключ-константа → lag по одной партиции; вернуть составной ключ.
  3. NOT_ENOUGH_REPLICAS: make kill-broker при acks=all, min.insync.replicas=2 → продюсер ловит ошибку; make up-broker → запись восстановилась. Запишите время недоступности.
  4. Unclean leader election (демо-режим): уменьшите на стенде replica.lag.time.max.ms, остановите реплику, дождитесь вылета из ISR, уберите лидера и включите unclean.leader.election.enable=true — увидеть «пропавший хвост» один раз достаточно, чтобы никогда не включать это в проде.
  5. Disk full (симулятор): топик с retention.bytes=1MB, segment.bytes=100KB; гоняйте трафик — смотрите, как сегменты роллятся и освобождаются.
  6. Офсеты: autocommit on + падение после обработки → дубль; autocommit off + commit после → потери нет, дубль контролируется идемпотентным обработчиком (задача из «Фундамента»).

Артефакт: журнал шести воспроизведений — «симптом / ведущая метрика / команда воспроизведения / лечение».


🧩 Quiz "Инцидент-квиз: причина по симптому"

1. Продюсеры ловят NOT_ENOUGH_REPLICAS при живом кластере из 3, RF=3, min.insync=2. Что случилось и что делать?
2. Диск брокера 95 %, retention 7 дней «должен чистить». Почему место не освобождается?
3. Группа мигает members, обработка дёргается паузами 10–30 с, lag — пила. URP=0. Диагноз?
4. «Письмо пришло дважды» при здоровом кластере. Первое, что смотреть в коде консьюмера?

✅ Итоги модуля

ИнцидентВедущая метрикаЯдро причины
Rebalance stormпила lag + мигание memberspoll-обработка vs таймауты группы; лечится батчем/InstanceID/cooperative
Lag без деградации кластераlag×partitionобработка / горячая партиция / партиции одного брокера
NOT_ENOUGH_REPLICASURP + kafka_brokersmin.insync — предохранитель, а не баг
ISR shrink на живом кластереIsrShrinks «дыхание»медленный узел: диск/GC/сеть
Disk fulldisk usageсегменты целиком + бюджет retention
Дубли/потери сообщенийметрика дублейавтокоммит и порядок commit↔обработка

🔗 Куда дальше

Эксплуатационная глубина курса собрана: модель (М1) → приборы (М2) → инциденты (М3). Дальше — Модуль 4: тюнинг под нагрузкой и exactly-once — цифры linger/batch/compression и честный ответ, когда транзакции Kafka нужны по-настоящему. А следующий курс трека — RabbitMQ с той же тройкой «модель → мониторинг → инциденты».