Kafka retention.ms 24 часа: почему сообщения не удаляются вовремя

Представьте: вы задали для топика в Kafka `retention.ms = 86400000` (24 часа), отправили тестовое сообщение и уверены, что ровно через сутки оно исчезнет. Проходит 24 часа, вы читаете из топика… а сообщение по‑прежнему на месте. Возникает резонный вопрос: почему оно «живёт» дольше заявленного срока и когда на самом деле будет удалено?

Ответ кроется в том, как Kafka хранит данные на диске и как устроен механизм retention. Сообщения не удаляются поштучно в момент, когда на них «тикает» `retention.ms`. Kafka работает с крупными блоками — сегментами логов, и именно они являются объектом удаления.

---

Как Kafka хранит сообщения: лог и сегменты

Лог топика в Kafka — это не один монолитный файл, а последовательность файлов‑сегментов. Каждый сегмент — это часть непрерывного лога партиции. Примеры имен файлов:

- `00000000000000000000.log`
- `00000000000000005368.log`
- `00000000000000010678.log`

Имя сегмента обычно соответствует начальному смещению (`start_offset`) первого сообщения внутри него. Например, файл `00000000000000005368.log` начинается с offset 5368.

Сегмент — это бинарный структурированный файл, в котором хранятся:

- ключ и значение сообщения;
- заголовки;
- метаданные (таймштемпы, контрольные суммы и др.).

Размер одного сегмента контролируется параметром `log.segment.bytes` (по умолчанию 1 ГБ). Пока активный сегмент не достиг этого размера (или не сработают другие условия), брокер продолжает дописывать новые сообщения в его конец.

Главный момент: Kafka удаляет данные целиком по сегментам, а не отдельные записи. Если в сегменте есть хотя бы одно «живое» сообщение, сегмент останется на месте, а значит и старые сообщения внутри него тоже.

---

Когда сегмент перестаёт быть активным

Сегменты можно условно разделить на:

- активный — текущий сегмент, в который продолжают записываться новые сообщения;
- неактивные — закрытые сегменты, в которые больше не пишут, но из которых читают.

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

1. По размеру
Когда файл достигает размера `log.segment.bytes` (по умолчанию 1 ГБ), брокер «закрывает» текущий сегмент и начинает записывать в новый.

2. По времени
Если включён `log.roll.ms` или `log.roll.hours`, сегмент будет принудительно переведён в неактивные через заданный интервал времени, даже если он не заполнился по размеру.
Значение по умолчанию — `log.roll.hours = 168` (7 дней).

3. По индексу
Существует также ограничение `log.index.size.max.bytes`. Если индекс сегмента разрастается слишком сильно, это также может привести к его «закрытию». Этот механизм используется реже и менее очевиден, но игнорировать его не стоит.

Ключевая деталь: активный сегмент не удаляется никогда, даже если все сообщения внутри него уже «старше» `retention.ms`. Пока сегмент активен, retention к нему просто не применяется.

---

Почему сообщения не исчезают ровно через `retention.ms`

Популярное заблуждение выглядит так:
«Если `retention.ms = 86400000`, то каждое сообщение будет удалено точно через 24 часа после записи».

На практике Kafka работает иначе:

1. Срок хранения (`retention.ms`) применяется не к отдельному сообщению, а ко всему сегменту.
2. Сегмент может быть удалён, только если:
- он уже не активен;
- он является «достаточно старым» по времени или лог превышает лимит по размеру;
- поток очистки логов (log cleaner / логовый janitor) успел его проверить и пометить к удалению.
3. Проверка на устаревание выполняется периодически, с интервалом `log.retention.check.interval.ms` (по умолчанию 300000 мс — 5 минут).

Поэтому сообщение, которому исполнилось 24 часа, останется в логе до тех пор, пока:

- весь сегмент, в котором оно находится, не станет старше `retention.ms`;
- этот сегмент не будет отмечен как кандидат на удаление при очередной проверке;
- и не пройдет задержка перед физическим удалением файла (`log.segment.delete.delay.ms`).

В сухом остатке: срок удаления всегда «плавающий» и привязан к поведению сегментов и циклам проверки.

---

Ключевые параметры retention и их приоритет

Kafka управляет сроком жизни сообщений двумя основными группами настроек.

1. Основные параметры времени и размера хранения

- `log.retention.ms` / `log.retention.minutes` / `log.retention.hours`
Определяют, как долго сегменты считаются актуальными по времени.
Приоритет имеет наиболее детальный параметр — миллисекундный (`log.retention.ms`).
По умолчанию чаще всего используется `log.retention.hours = 168` (7 дней).

- `log.retention.bytes`
Максимальный суммарный размер логов на партицию, при превышении которого Kafka начнёт удалять самые старые сегменты, даже если они ещё не достигли лимита по времени.
Значение `-1` означает отсутствие ограничения по размеру.

Эти параметры можно задавать:

- на уровне брокера (дефолт для всех топиков);
- переопределять на уровне конкретного топика, если для него требуется своя политика хранения.

2. Параметры частоты и точности удаления

- `log.segment.bytes`
Размер одного сегмента. Чем он больше, тем дольше заполняется сегмент и тем реже появляются новые неактивные сегменты, готовые к удалению.

- `log.roll.ms` / `log.roll.hours`
Максимальное время, после которого активный сегмент будет принудительно закрыт, даже если не достиг `log.segment.bytes`. Это улучшает предсказуемость удаления по времени, но увеличивает количество мелких файлов.

- `log.retention.check.interval.ms`
Интервал между фоновыми проверками сегментов на предмет устаревания. По умолчанию — 5 минут.
Если этот интервал уменьшить, удаление станет точнее «по часам», но вырастет нагрузка на брокер.

- `log.segment.delete.delay.ms`
Задержка между пометкой сегмента к удалению и фактическим удалением файла с диска. По умолчанию 60000 мс (1 минута).
Это своего рода «буфер безопасности», позволяющий избежать конфликтов с процессами, которые могли еще держать файл открытым.

---

Пример расчёта реального времени жизни сообщения

Возьмём упрощённый, но достаточно жизненный сценарий.

- Задан `retention.ms = 86400000` (24 часа).
- Размер сегмента по умолчанию: `log.segment.bytes = 1GB`.
- Нагрузка на партицию — около 100 МБ в час.

Чтобы заполнить один сегмент:

- 1024 МБ / 100 МБ в час ≈ 10,24 часа (примерно 10 часов 14 минут).

Теперь представим, что ваше тестовое сообщение — одно из первых в новом сегменте:

1. Сообщение записывается в только что созданный активный сегмент.
2. Сегмент заполняется до 1 ГБ примерно за 10 часов и становится неактивным.
3. От этого момента начинается «отсчёт старения» всего сегмента по `retention.ms`:
- сегмент должен стать старше 24 часов с момента своей последней записи (упрощённо — с момента, когда он был закрыт/перестал дописываться).
4. Когда возраст сегмента превышает `retention.ms`, он считается кандидатом на удаление, но:
- будет удалён только при очередном проходе потока очистки (каждые 5 минут);
- и ещё выдержит задержку `log.segment.delete.delay.ms` (1 минута).

Если сложить всё вместе, получаем:

- около 10 часов до «закрытия» сегмента;
- плюс 24 часа до наступления retention‑порога для этого сегмента;
- плюс до 5 минут ожидания следующей проверки;
- плюс около 1 минуты задержки удаления.

В итоге: вместо ожидаемых 24 часов сообщение живёт 34+ часа. Именно поэтому оно спокойно «переживает» формальный срок жизни.

---

Почему уменьшение retention не всегда даёт мгновенный эффект

Иногда администратор меняет `retention.ms` с нескольких дней до, скажем, 1 часа и ждёт, что старые данные тут же «смоет». Но Kafka продолжает хранить их ещё длительное время. Это нормальное поведение.

Причины:

- старые сегменты ещё не достигли нового порога устаревания;
- активный сегмент нельзя удалить, даже если там уже есть сообщения старше retention;
- проверка на удаление запускается периодически;
- часть сегментов может быть удержана ограничениями по размеру, времени и нюансами конфигурации.

Поэтому изменение настроек retention — это не кнопка «удалить всё немедленно», а корректировка будущей политики хранения.

---

Влияние размера сегмента на контролируемость хранения

`log.segment.bytes` — один из ключевых параметров, который напрямую определяет поведение retention.

- Крупные сегменты (например, 1–2 ГБ):
- меньше файлов на диске, проще файловая система;
- но каждый сегмент заполняется дольше, и старые сообщения могут «застревать» в активном сегменте;
- удаление получается «крупными порциями» и менее предсказуемым во времени.

- Мелкие сегменты (например, 128–256 МБ):
- чаще создаются новые сегменты;
- проще добиться того, чтобы «старый» сегмент целиком попадал под retention и удалялся ближе к ожидаемому времени;
- но возрастает количество файлов, нагрузка на файловую систему и метаданные.

Если для вас критична управляемость времени хранения (например, требования регуляторов, договорённости по SLA или лимиты по диску), имеет смысл уменьшить размер сегментов и/или сократить `log.roll.ms`, чтобы активные сегменты закрывались чаще.

---

Баланс между точностью retention и производительностью

Увеличить точность удаления можно:

- уменьшая `log.retention.check.interval.ms`;
- уменьшая `log.segment.bytes`;
- укорачивая `log.roll.ms`.

Но за это приходится платить:

- ростом количества файлов на диске;
- увеличением числа операций ввода‑вывода;
- повышенной нагрузкой на брокер и GC (если файлов и индексов много);
- рисками упереться в лимиты файловой системы (количество inode и т. п.).

Поэтому настройки retention всегда — вопрос баланса между:

- предсказуемостью удаления;
- эффективным использованием диска;
- производительностью кластера.

---

Как «читать» retention: гарантия «не раньше чем»

Важно менять взгляд на retention в Kafka.

Retention‑параметры дают гарантию:

- «сообщение не будет удалено раньше, чем через retention.ms»,
но не гарантируют:
- «оно будет удалено точно через retention.ms».

Kafka стремится к тому, чтобы данные точно пережили заданный срок, но фактическое время удаления оказывается больше из‑за:

- пакетной природы хранения (сегменты);
- периодичности проверок;
- настроек размерности и времени закрытия сегментов;
- задержки физического удаления файлов.

Если планировать систему с учётом этого принципа, многие «неожиданности» с retention исчезают.

---

Практические рекомендации по настройке retention

Чтобы поведение retention было более предсказуемым, можно действовать так:

1. Оценить реальную нагрузку на топик.
Понять, сколько данных в час/сутки поступает в каждую партицию. Это база для выбора `log.segment.bytes`.

2. Подогнать размер сегмента под желаемое время жизни.
Если вам важно, чтобы сообщения жили близко к 24 часам, сделайте так, чтобы сегмент заполнялся, условно, за 2–6 часов, а не за сутки или больше.

3. Использовать `log.roll.ms` как ограничитель времени жизни активного сегмента.
Это не даст одному сегменту «жить» слишком долго и смешивать в себе очень старые и относительно новые сообщения.

4. При необходимости уменьшить `log.retention.check.interval.ms`.
Но делать это аккуратно: снижение, например, до 1 минуты увеличит нагрузку, но сделает удаление более «чётким».

5. Не забывать про `log.retention.bytes`.
Если диск — ограниченный ресурс, лучше задать адекватный предел по размеру, чтобы система гарантированно освобождала место.

---

Выводы

- В Kafka никогда не происходит точечного, моментального удаления индивидуального сообщения в момент истечения `retention.ms`.
- Объектом удаления являются сегменты, а не отдельные события.
- Активный сегмент не может быть удалён, даже если все его сообщения формально «старше» retention.
- Фактическое время жизни сообщения — это:
- время, пока заполняется и закрывается сегмент;
- плюс заданный retention по времени или размеру;
- плюс интервал до ближайшей проверки;
- плюс задержка физического удаления.
- В реальных условиях 24 часа, заданные в `retention.ms`, легко превращаются в 30–35 и более часов реального хранения.
- Retention в Kafka — это обещание «не раньше чем», а не «строго через».

Понимая устройство сегментов и работу фоновых процессов очистки, можно настроить Kafka так, чтобы сроки хранения были не сюрпризом, а осознанным и предсказуемым инструментом управления данными.

Прокрутить вверх