← Все статьи

Лаг упал в ноль, а ничего не починилось: зависший Kafka-консьюмер, спрятанный переименованием group.id

Лаг консьюмера рос пять дней, потом упал в ноль и там остался. Выглядело как «само рассосалось». На деле консьюмер завис, метрика исчезла, а алерт зарезолвился сам.

На одной продуктовой платформе в Gambchamp график лага консьюмера пять дней рос примерно до 80 000 сообщений — а потом упал в ноль и лёг ровной линией. Казалось, проблема ушла сама. Одновременно панель «суммарный лаг» показывала отрицательное число, а «активные группы» — одну, хотя в кластере десятки живых групп.

Три вещи, каждая из которых выглядела нормально, были сломаны одновременно.

Слой первый: консьюмер жив и ничего не делает

После пересоздания брокеров committed offset группы застыл и не двигался больше восьми часов, пока продюсеры продолжали писать. Консьюмер оставался членом группы, так что для Kafka он был жив — просто не продвигался. Клиентская библиотека не переподключилась как следует после рестарта брокеров.

Правду дала сама Kafka, в обход экспортера:

kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group <group>

CURRENT-OFFSET, который не меняется при растущем LOG-END-OFFSET, — это зависший консьюмер, что бы ни говорило членство в группе.

Слой второй: ноль — не выздоровление

Падение в ноль совпало с деплоем, который поменял схему именования group.id. Старые группы осиротели, новая начала читать. Но новое имя не попало под whitelist --group.filter экспортера, и метрика по ней просто исчезла.

Алерт по лагу увидел, что серия пропала, и зарезолвился. Ещё три дня правило ни за чем не следило, а график выглядел здоровым, потому что на нём ничего не было.

Слой третий: отрицательный лаг

kafka-exporter отдаёт lag = -1 для партиций без committed offset — там, где сама Kafka показывает LAG = "-". Панель, суммирующая лаг, уходила в минус, а порог алерта тихо сдвигался на число таких партиций.

В довесок метрики консьюмеров самого приложения существовали, но не собирались: чарт объявлял порт метрик 9001, а приложение слушало 9006.

Исправления

  • Скрейпить реальный порт. Relabel в vmagent оставляет объявленный порт для дедупликации, но переписывает адрес на тот порт, который приложение реально слушает.
  • Blacklist вместо whitelist. --group.filter=.* плюс исключение эфемерных групп, привязанных к поду. Whitelist по имени ломается при первом же ренейме — ровно это нас и ослепило.
  • clamp_min(..., 0) в панелях лага. Убирает -1, сохраняя серию; фильтр > 0 выкинул бы серию целиком.
  • Алертить на клиентскую метрику. highwater - position от самого консьюмера не зависит ни от именования групп, ни от фильтра экспортера.

Выводы

  • Каждому пороговому алерту нужен парный absent(). Иначе исчезновение метрики читается как выздоровление — ровно это и произошло.
  • Whitelist в экспортере метрик — мина, которая ждёт следующего переименования.
  • LAG = "-" — это не ноль, а -1 от экспортера — не число, которое можно суммировать.
  • Проверяйте, что порт, объявленный в чарте, — это тот порт, который слушает приложение.
  • Один канал для всех алертов — это отсутствие алертинга: пятидневный firing утонул среди тысяч уведомлений. Владелец и SLA для warning важнее ещё одного правила.