Команда сокращает RAM на брокерах Kafka: heap JVM не заполнен, сборщик мусора работает спокойно, а журнал всё равно хранится на диске. На панели это выглядит как безопасная экономия. Через несколько дней отставший потребитель начинает читать старые сообщения, одновременно восстанавливается реплика, и задержка выдачи данных резко растёт. Процесс Kafka по-прежнему не упирается в свой heap. Проблема находится рядом с ним: операционной системе осталось меньше памяти для недавно использованных частей файлов журнала. Вместо быстрого чтения из RAM брокер чаще обращается к накопителю, где клиентские запросы, репликация и фоновые операции образуют общую очередь.
Fetch latency — время, которое потребитель ждёт ответ на запрос данных у брокера. Когда оно растёт, обработчик позже получает сообщения, отставание способно накапливаться, а обещанный бизнесу срок доставки события нарушается. Поэтому свободное место в heap не доказывает, что память узла избыточна.
Heap — только один потребитель памяти брокера
Брокер Kafka — сервер, который принимает записи от производителей, хранит их в разделах журнала и отдаёт потребителям и другим брокерам. Heap JVM нужен его объектам, метаданным и рабочим структурам. Но сами сегменты журнала лежат в файлах, и путь к ним во многом управляется операционной системой. Page cache, или файловый кэш ОС, хранит в RAM недавно использованные части файлов. Kafka сознательно опирается на этот механизм вместо огромного собственного кэша объектов внутри Java.
В результате одна и та же свободная память узла может ускорять чтение журнала без увеличения heap и без дополнительной работы сборщика мусора. Поэтому уменьшение RAM способно улучшить показатель плотности серверов и одновременно ухудшить работу брокера. JVM видит прежний лимит и чувствует себя нормально, однако ОС удерживает меньше горячих сегментов. Экономия затрагивает второй слой памяти, который панель Java не описывает.
Почему догнавший потребитель почти не касается диска
Consumer, или потребитель, запрашивает у Kafka очередную часть журнала, начиная со своей позиции. Если он почти догнал поток записей, ему обычно нужны самые свежие сегменты. Эти данные недавно поступили на брокер и с большой вероятностью ещё находятся в page cache. Официальное описание Kafka 4.1 отмечает, что при в основном догнавших потребителях данные могут обслуживаться целиком из кэша ОС без чтения с диска. Это не гарантия для каждого запроса, а следствие типичного последовательного пути: запись прошла через файловую систему, а вскоре те же байты потребовались читателю.
Kafka также использует оптимизированный путь sendfile, когда среда его поддерживает: ОС передаёт данные из page cache к сетевому сокету без лишнего копирования через пользовательский буфер процесса. Такой путь часто называют zero-copy, то есть передачей с устранением лишних копий. Он уменьшает работу CPU, но не делает данные независимыми от памяти и накопителя. Есть важная граница: документация Kafka указывает, что этот путь не используется ею при SSL, потому что обработка шифрования проходит в пользовательском пространстве. Сеть, шифрование и версия платформы поэтому меняют цену передачи. Но наличие или отсутствие sendfile не отменяет главного: попадание нужного сегмента в page cache определяет, понадобится ли физическое чтение.
Холодное чтение меняет весь путь данных
Отставший потребитель просит сообщения, которые уже не относятся к горячему хвосту. Такой же эффект создаёт повторная обработка истории или новый сервис, начинающий чтение со старой позиции. Если нужных страниц нет в RAM, брокеру приходится возвращать их с накопителя прежде, чем отправить клиенту. Перезапуск процесса Kafka сам по себе не обязан очистить page cache: кэш принадлежит ОС и способен пережить рестарт брокера. Но перезагрузка хоста, перенос на другой узел, конкуренция за RAM или большой поток других чтений делают кэш холоднее. После запуска меняется набор востребованных сегментов, и прогрев оплачивается реальным I/O.
Восстановление реплики создаёт похожую нагрузку с другой целью. Реплика запрашивает отсутствующую часть журнала у лидера, чтобы снова догнать его. Если одновременно клиент читает старую историю, оба потока могут претендовать на накопитель, сеть и обработчики запросов брокера. Так появляется причинная цепочка: промах в кэше приводит к чтению, чтения конкурируют за накопитель, запрос fetch дольше ждёт, а потребитель получает меньше данных за то же время. Если новые сообщения приходят быстрее обработки, растёт lag — расстояние между позицией потребителя и концом журнала.
Lag не является диагнозом нехватки RAM
Consumer lag показывает отставание потребителя, но не объясняет причину. Приложение может медленно обрабатывать уже полученные сообщения, зависеть от внешней базы, переживать перераспределение разделов или иметь недостаточную параллельность. В таком случае добавление памяти брокеру не устранит узкое место. Fetch latency ближе к пути чтения, но тоже не доказывает промахи page cache. Ответ способен задержаться в очереди брокера, в сети, при ограничении пропускной способности или из-за самого потребителя. Поэтому рост времени fetch связывают с изменением физического чтения и очереди накопителя, а не трактуют отдельно.
Репликационный поток также нужно отделять от клиентского. В мониторинге Kafka запросы FetchConsumer относятся к чтению клиентов, а FetchFollower — к получению данных ведомыми репликами. У них общий серверный путь, но разные последствия: первый влияет на приложения, второй — на актуальность копий данных. ISR — набор реплик раздела, которые успевают следовать за лидером в допустимых условиях. Когда реплика не догоняет, набор способен сократиться, а запас устойчивости раздела — уменьшиться. Это не означает, что любой выход из ISR вызван RAM: причиной могут быть сеть, CPU, накопитель или сбой брокера.
Remote storage, или удалённое многоуровневое хранилище Kafka, — ещё один отдельный путь. В Kafka 4.1 завершённые сегменты могут находиться во внешней системе при соответствующей реализации, и историческое чтение тогда имеет собственную задержку. Его нельзя смешивать с локальным page cache или считать обычным промахом кэша на NVMe брокера.
Один накопитель обслуживает несколько обязательств
Когда горячие страницы помещаются в RAM, клиентское чтение меньше зависит от диска, а накопитель может спокойнее обслуживать запись журнала и другие операции. После сокращения памяти чтение возвращается в этот же ресурсный контур. Экономия DRAM превращается в дополнительный спрос на I/O. Особенно заметно это во время восстановления. Лидер отдаёт историю реплике, отставшие потребители читают свои диапазоны, новые сообщения продолжают записываться, а ОС пытается выбрать, какие страницы удержать.
Даже быстрый NVMe имеет конечную пропускную способность и время обслуживания запросов. Очередь важна не меньше максимальной скорости накопителя. Короткий запрос потребителя может оказаться за большим потоком восстановления и получить длинный хвост задержки. Среднее время остаётся умеренным, а p99 — граница для самых медленных ответов — ухудшается именно в критичной фазе.
Экономить можно только вместе с качеством потока
Сокращение RAM оправдано, если реальный диапазон горячего чтения продолжает помещаться в page cache, а исторические обращения не забирают I/O у критичных потребителей и реплик. Если же каждый необычный запрос вызывает очередь на накопителе, дешёвый узел становится дорогим из-за задержек и более долгого восстановления. Решение не всегда состоит в покупке памяти. Причиной lag может быть медленный consumer, сеть или внешний сервис; причиной fetch latency — очередь запросов или remote storage. Разделение клиентского и репликационного чтения, расписание исторических выборок и достаточный класс накопителя иногда важнее дополнительной DRAM.
Прозрачный страничный уровень класса gigaRAM в принципе имеет смысл для Kafka только при условии, что горячий page cache остаётся в DRAM, подходящие менее активные страницы могут обслуживаться через NVMe, а общий I/O не насыщен. Публичное позиционирование продукта не подтверждает совместимость с Kafka, поэтому такой уровень нельзя называть кэшем Kafka, заменой tiered storage или всей оперативной памяти. Управленческий вопрос звучит так: сохраняется ли срок доставки события и восстановление реплик после изменения бюджета RAM. Heap отвечает лишь за внутреннюю часть брокера. Полная стоимость включает page cache, физическое чтение, сеть, репликацию и хвост задержки приложений.
Источники. Apache Kafka 4.1 — Design описывает page cache, sendfile и чтение догнавших потребителей.
Apache Kafka 4.1 — Monitoring разделяет FetchConsumer, FetchFollower, lag реплик и ISR.
Apache Kafka 4.1 — Tiered Storage разграничивает локальное и удалённое хранение сегментов.
Google SRE подтверждает значение хвоста задержки и насыщения.
Meta Engineering — TMO используется для общей механики переноса страниц.
Страница продукта — только для позиционирования решения.
