← Все статьи

Kafka на 94 % диска, 17 часов в запасе и retention, который хранил вдвое больше

Трафик вырос в 150 раз, а тома никто не пересматривал. Хуже того, при дефолтном роллинге сегментов retention в 7 дней на деле означал 7–14. Как мы выиграли время и устранили причину.

В Gambchamp сработал алерт: Kafka забивает свои тома. Вопрос был обычный — потерпит ли до утра?

Не потерпело бы. Все три брокера стояли на 36,6 из 39,1 ГиБ, 94 %. Рост в среднем 3,37 ГиБ в сутки и до ~5 ГиБ в сутки в вечерний пик. Оставалось примерно 17 часов, то есть диск закончился бы посреди ночи, прямо в пик. Следующая автоматическая очистка retention ожидалась только через двое суток.

Ничего не сломалось

Причин было две, и ни одна не была поломкой.

Трафик вырос примерно в 150 раз. С ~5 сообщений в секунду в начале августа до плато ~780 сообщений в секунду после запуска продукта. Lag у консьюмеров нулевой, ошибок в логах брокеров нет, under-replicated партиций нет. Тома по 40 ГиБ просто проектировались под другую нагрузку.

Retention на деле был 7–14 дней, а не 7. В конфиге брокеров retention не задавался вовсе, работали дефолты Kafka: log.retention.hours=168 и log.roll.hours=168, сегменты по 1 ГиБ. Retention удаляет только закрытые сегменты. Наши партиции набирали несколько сотен мегабайт в неделю и до 1 ГиБ не дорастали, поэтому сегмент закрывался по 7-дневному таймеру — и только тогда у него начинался отсчёт retention. Отсюда «пила» на графике диска: большой сброс раз в неделю, между зубцами не освобождается ничего.

Практический вывод: такой том нельзя планировать как «7 дней × скорость». С такими дефолтами — 14.

Выигрываем время

Тома были на расширяемом классе gp3, поэтому первый шаг — онлайн-расширение PVC с 40 до 100 ГиБ. Без рестартов, без under-replicated партиций, GitOps-приложение осталось синхронным — PVC создаются из volumeClaimTemplates и в Git их нет, так что self-heal нечего было откатывать. Сразу 100, а не 60, потому что AWS разрешает модифицировать том не чаще раза в шесть часов.

Устраняем причину

На следующий день выяснилось, что retention в 7 дней — реальное требование архитекторов бэкенда. Это перевернуло смысл фикса: система не недодерживала, а хранила почти вдвое больше требуемого. Роллинг сегментов раз в 12 часов (log.roll.ms=43200000) возвращает реальное удержание к 7–7,5 дням, не трогая log.retention.hours.

При прогнозе роста в 4 раза стационар выходил ~185 ГиБ как есть против ~99 ГиБ с 12-часовыми сегментами, поэтому целевой том стал 200 ГиБ. Пропускная способность, IOPS, файловые дескрипторы и CPU были далеко от пределов — потолок был в объёме, а не в производительности.

Три ловушки по дороге

  • Неверный ключ, на который ответили «Completed». Сначала мы выставили log.segment.ms — такой настройки у брокера нет (на уровне топика она называется segment.ms). kafka-configs.sh ответил Completed updating default config и ничего не поменял. Правильное имя — log.roll.ms, а что реально действует, показывает только --describe --all.
  • Динамический override перекрывает файл. Динамический конфиг брокера приоритетнее server.properties. Пока override не снят, значение в Git декоративно — а если снять его слишком рано, роллинг ненадолго вернётся к 168 часам.
  • «Безопасное» пересоздание StatefulSet’а перезапустило все брокеры. В живом pod template осталась старая аннотация kubectl.kubernetes.io/restartedAt, которой не было в рендере из Git, — хэш изменился, и все брокеры прокатились. По одному, штатно, но незапланированно.

После изменений: тома по 200 ГиБ заняты на 21 %, всё приходит из Git, сегменты роллятся дважды в сутки.

Выводы

  • Время до заполнения считайте по пиковой скорости и сравнивайте со следующим сбросом retention. Если сброс позже — ждать нельзя.
  • Требование, совпавшее с дефолтом, — это незаписанное требование. Задавайте retention явно, даже если значение равно дефолтному.
  • kafka-configs.sh сообщает об успехе и для неизвестных ключей. Проверяйте через --describe --all.
  • Перед delete sts --cascade=orphan проверьте, нет ли в pod template аннотации restartedAt.