Прямой ответ
Consumer group — механизм Kafka, которым набор потребителей делит партиции топика (каждая партиция в момент времени читается ровно одним потребителем группы — горизонтальный масштаб = число потребителей ≤ числа партиций — Kafka-основы — «добавил 10 потребителей на топик из 6 партиций — 4 простаивают» — партиционность задаёт потолок параллелизма); координатор группы (broker) ведёт offset'ы (позиция чтения каждой пары group×partition — сохраняется в __consumer_offsets — «где я остановился» — очереди-семантика); главный выбор — когда коммитить offset: (1) auto-commit (enable.auto.commit=true — коммит по таймеру — «прочитал, не обработал, упал — offset закоммичен — сообщение потеряно» — at-most-once — данные теряются — почти всегда неправильно для обработки); (2) ручной коммит после обработки (enable.auto.commit=false + commit() после успешной обработки — «обработал → зафиксировал» — падение до коммита → перечитывание — at-least-once — дубли возможны (идемпотентность обязательна — «Kafka гарантирует exactly-once» — миф на границе с внешней системой — запись в БД+коммит не атомарны — outbox/идемпотентный ключ — saga); (3) commit-синхронность (commitSync против commitAsync — sync надёжнее на shutdown, async быстрее в потоке — комбо: async в цикле + sync при закрытии — graceful); ребалансировка (перераспределение партиций при входе/выходе потребителя): триггеры — новый/умерший участник, смена числа партиций, «потребитель не отвечает» (max.poll.interval.ms превышен — обработка дольше интервала — координатор считает потребителя мёртвым — живой потребитель исключён — двойная обработка — классическая прод-боль); виды протоколов: eager (stop-the-world — все потребители отпускают партиции, перегруппировка, переназначение — пауза обработки на всю группу — legacy), cooperative/incremental (CooperativeStickyAssignor — перебалансировка только затрагиваемых партиций — «ребаланс без полной остановки» — дефолт-выбор 2026 — Kafka-эволюция), KIP-848 (новый протокол 3.7+ — брокер-центричная ребалансировка — меньше stop-the-world — включать осознанно); статическое членство (group.instance.id — потребитель переживает рестарт без ребаланса — «деплой = шторм ребалансов» — лечится — rolling-деплой без переназначений — деплой-стратегии); настройки-страховки: session.timeout.ms (детект смерти), heartbeat.interval.ms (частота heartbeats — фоновый поток — «долгая обработка без poll» — heartbeats идут, но max.poll.interval — отдельный таймер), max.poll.records (меньше записей на poll — успевать обработать до max.poll.interval — баланс пропускной способности и стабильности).
Практика: lag, обработка и надёжность
Consumer lag (главная операционная метрика — kafka-consumer-groups --describe / Burrow/kminion — метрики): lag = last_offset − committed_offset («насколько потребитель отстал» — растёт — потребитель не успевает — ёмкость); причины роста lag: медленная обработка (внешний API/БД — ретраи — batch-обработка — асинхронность), мало потребителей относительно партиций (масштаб упирается в партиционность — добавить партиции — но порядок по ключу может нарушиться — проектирование), ядовитое сообщение (обработка одного сообщения падает в цикле — «застрял на offset N» — dead letter — вебхук-аналогия — пропуск с фиксацией — DLQ-топик — не блокировать партицию); алерт на lag (порог по бизнес-SLO — «лаг > 5 минут — данные устарели» — алерты — SLO — lag — главный индикатор здоровья пайплайна); порядок (гарантирован внутри партиции, не между — Kafka-основы — ключ партиционирования определяет порядок — «заказы одного клиента в одну партицию» — порядок по клиенту); параллелизм внутри потребителя (один poll → обработка параллельно по пулу — но коммит-порядок усложняется (offset коммитится только когда все более ранние обработаны — иначе потеря при падении — идемпотентность — «параллельная обработка» — осторожно с коммитами); exactly-once в Kafka (транзакции + read_committed — атомарность «обработка+продюсирование в Kafka» — но граница с внешней БД — всё равно outbox/идемпотентность — saga — «EOS Kafka» не отменяет идемпотентность на границе).
Практический сценарий: потребитель терял сообщения — и почему
Контекст: платёжный пайплайн — consumer group обрабатывает payments.events (12 партиций), жалобы: «часть платежей не проведена», lag в норме (потребитель «успевал»); расследование: (1) конфиг — enable.auto.commit=true (дефолт — никто не менял — auto-commit каждые 5 с), max.poll.interval.ms=300000 (5 мин); (2) механизм потери: обработчик вызывал внешний процессинг (2–8 с на платёж — batch 500 записей на poll), падал на таймауте процессинга → исключение → poll-цикл продолжал, но auto-commit уже зафиксировал offset необработанных сообщений — потеря без следа (at-most-once в чистом виде — очереди-гарантии); (3) вторая находка — max.poll.interval.ms: при 500 записях × 5 с = 2500 с обработки — превышение 300 с — координатор исключал потребителя (живого!) — ребаланс — партиция другому потребителю — двойная обработка части платежей (те, что первый обработал, но не закоммитил — второй обработал заново — «двойное списание» — идемпотентность не была реализована); (4) фикс: enable.auto.commit=false + ручной коммит после успешной обработки каждого batch'а (at-least-once), max.poll.records=50 (50 × 8 с = 400 с... всё ещё риск — снизили до 20 → 160 с < 300 с — успевает), идемпотентный ключ платежа (payment_id unique в БД — дубль — no-op — идемпотентность — двойное списание закрыто на уровне данных), CooperativeStickyAssignor (ребалансы без stop-the-world), group.instance.id на каждый инстанс (деплой без шторма ребалансов — деплой), DLQ-топик для ядовитых (3 ретрая → dead letter — вебхук-паттерн — партиция не блокируется); (5) мониторинг: lag-алерт (алерты), метрика rebalance-частоты (шторм = проблема — метрики), DLQ-размер (растёт — разбор — incident); (6) проверка: нагрузочный прогон (locust-аналог — 1000 платежей/с) + убийство потребителя под нагрузкой — потери ноль, дубли ноль (идемпотентность), rebalance — cooperative (пауза < 1 с); вывод: «Kafka терял» — неправда — конфигурация auto-commit + отсутствие идемпотентности теряли — Kafka давал at-least-once, приложение сделало at-most-once — надёжность-дизайн — семантику доставки проектируют, не наследуют от дефолтов.
FAQ
Сколько партиций нужно и можно ли их добавлять?
Правило: партиций ≥ максимального числа потребителей в группе (иначе потребители простаивают — сценарий выше — ёмкость), с запасом на рост (партиции дешёвы до разумных пределов — сотни на брокер — Kafka-основы); расчёт: целевая пропускная способность / пропускная способность партиции (одна партиция — один писатель-поток — десятки МБ/с — ёмкость); добавление партиций: kafka-topics --alter --partitions N — только увеличение (уменьшение — не поддерживается — пересоздание топика), порядок по ключу ломается (хэш ключа % N меняется — «заказы клиента» разъезжаются по партициям — исторический порядок нарушен — критично для stateful-обработки — добавлять до пика нагрузки, не во время — проектирование); «много партиций — всегда хорошо» — нет: каждая партиция — файловые дескрипторы/память/время ребаланса (тысячи партиций — ребалансы долгие — лидер-выборы — ёмкость-баланс); практика: стартовать с разумного (12–24 для среднего пайплайна), мониторить lag/утилизацию, добавлять проактивно — планирование.
Почему происходит «шторм ребалансировок» и как его остановить?
Причины: потребители не успевают между poll'ами (max.poll.interval.ms — долгая обработка — сценарий выше), частые рестарты (деплой без static membership — каждый вход/выход — ребаланс — деплой), нестабильная сеть (heartbeats теряются — session.timeout.ms — сессии рвутся), авто-масштабирование (HPA — поды приходят/уходят — каждый — ребаланс); лечение: static membership (group.instance.id — рестарт в пределах session.timeout — без ребаланса), cooperative assignor (частичная ребалансировка — меньше пауз), tuning таймаутов (max.poll.records вниз — успевать обработку, max.poll.interval.ms вверх — запас, session.timeout.ms/heartbeat.interval.ms — под сеть), обработка быстрее (batch/async/кэш — корень часто в скорости — профили); диагностика: логи группы (kafka-consumer-groups --describe --state — поколение группы растёт быстро — шторм), метрика rebalance-rate (метрики — алерт на всплеск — алерты); «ребаланс — это нормально иногда» — да, но непрерывный шторм — симптом — техдолг.
Что такое consumer lag и как правильно на него алертить?
Lag — отставание committed_offset от last_offset (в сообщениях или «во времени» — lag × средний интервал — очереди); измеряют: kafka-consumer-groups --describe (по group×partition), Burrow/kminion (экспорт в Prometheus — метрики), JMX-метрики потребителя (records-lag-max — Grafana); алерт-дизайн: по бизнес-SLO («данные должны быть свежее 5 минут» — lag-во-времени > 5 мин — page — SLO), по тренду (lag монотонно растёт N минут — потребитель не догоняет — даже если абсолют ещё мал — алерт-дизайн), на «lag растёт при низком ingress» (потребитель деградировал — не нагрузка — ёмкость); ложные срабатывания: «lag скакнул при деплое» — static membership + silence-окно (деплой — алерт-гигиена), «lag огромен на новом потребителе» (auto.offset.reset=earliest — читает историю — разово — не алерт — Kafka-конфиг); lag=0 ≠ всё хорошо (потребитель может «успевать», но терять через auto-commit — сценарий выше — lag и корректность — разные вещи — надёжность).
auto.offset.reset=earliest или latest — что выбрать?
latest (дефолт) — новый потребитель (нет committed offset) читает только новые сообщения («подписался на сейчас» — потеря истории до старта — ок для real-time-потоков, плохо для «начать обработку заново»); earliest — с начала топика (вся история — retention-ограничена — Kafka-основы — «новый сервис обработал всё с начала» — может быть шоком нагрузки — ёмкость); критерий: новый consumer group на существующем топике — earliest если нужна история (репроцессинг/новый сервис), latest если только актуальное (real-time-дашборд); «сброс offset'ов» (перечитать с точки — kafka-consumer-groups --reset-offsets — осторожно: группа должна быть неактивна — иначе конфликт — очереди-операции — идемпотентность обязательна при репроцессинге — идемпотентность); антипаттерн: earliest + частое создание новых group-имён («тестовый запуск с новым group.id» — каждый раз читает всё — нагрузка — ёмкость — group.id стабилен — конфигурация).
Источники: документация Apache Kafka (consumer configs, consumer position, KIP-848) — ссылки в шапке.