📨 Раздел 15 · Вопрос #22

Как работает компрессия сообщений

В отличие от классических очередей сообщений (JMS, RabbitMQ), где сжатие чаще всего настраивается вручную на уровне тела каждого отдельного сообщения, в Kafka сжатие является вс...


🟢 Junior Level

Что такое компрессия сообщений в Kafka

Компрессия (сжатие данных) в Apache Kafka — это процесс уменьшения объема передаваемых и хранимых данных путем применения алгоритмов сжатия без потерь (lossless compression) к пакетам сообщений (батчам, ProducerBatch).

В отличие от классических очередей сообщений (JMS, RabbitMQ), где сжатие чаще всего настраивается вручную на уровне тела каждого отдельного сообщения, в Kafka сжатие является встроенным платформенным механизмом, который применяется на стороне Producer к целому батчу записей.

Отправка без компрессии:
App ──send(100 сообщений)──> [Пакет 100 КБ] ──Сеть──> Брокер (Диск: 100 КБ) ──Сеть──> Consumer

Отправка с компрессией (например, LZ4):
App ──send(100 сообщений)──> [Пакет 100 КБ] 
                                    │ (сжатие на клиенте)
                                    ▼
                             [Пакет 25 КБ]  ──Сеть──> Брокер (Диск: 25 КБ)  ──Сеть──> Consumer
                                                                                        │ (распаковка)
                                                                                        ▼
                                                                                 [Пакет 100 КБ]

Зачем нужна компрессия

  1. Снижение сетевого трафика (Network Bandwidth): Сокращение объема передаваемых байтов между продюсерами, брокерами и консьюмерами в 2–7 раз, что критично при межсегментной передаче (cross-AZ, cross-region в Cloud-провайдерах, где за исходящий трафик взимается плата).
  2. Экономия дискового пространства: Логи партиций (.log) хранятся на брокерах в сжатом виде, что многократно увеличивает глубину хранения (retention period) без закупки дополнительных NVMe/SSD дисков.
  3. Эффективное использование Page Cache: В ОС брокера помещается значительно больше сжатых сегментов лога, что повышает кэш-хиты и снижает физические чтения с диска.
  4. Рост общей пропускной способности (Throughput): Сетевые интерфейсы и шины передачи данных перестают быть узким местом.

Доступные кодеки сжатия

Kafka Producer поддерживает четыре алгоритма сжатия (параметр compression.type):

  • none — сжатие отключено (по умолчанию).
  • lz4 — рекомендуемый стандарт индустрии для большинства enterprise-систем. Обеспечивает экстремально быструю компрессию и декомпрессию при очень низком потреблении CPU.
  • snappy — разработан Google. Отличная скорость работы при умеренном сжатии, исторический фаворит для low-latency систем.
  • zstd (Zstandard) — разработан Meta (Facebook). Обеспечивает рекордный коэффициент сжатия (на уровне GZIP) при скорости декомпрессии, близкой к Snappy/LZ4. Поддерживает тонкую настройку уровней компрессии.
  • gzip — классический алгоритм с наивысшим коэффициентом сжатия, но крайне медленный и прожорливый по CPU (создает серьезный bottleneck).

Базовая настройка Producer в Java

Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka-broker1:9092,kafka-broker2:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());

// 1. Включение кодека компрессии (lz4, zstd, snappy, gzip)
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "lz4");

// 2. Компрессия работает эффективно только при наличии батчинга!
// Задаем объем батча 64 КБ и задержку накопления 10 мс
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 65536);
props.put(ProducerConfig.LINGER_MS_CONFIG, 10);

KafkaProducer<String, String> producer = new KafkaProducer<>(props);

🟡 Middle Level

Сравнительный анализ алгоритмов сжатия

Выбор кодека компрессии всегда представляет собой компромисс (trade-off) между нагрузкой на CPU и экономией сети / диска:

Кодек Коэффициент сжатия Скорость сжатия Скорость распаковки Нагрузка на CPU Рекомендованный сценарий
none 1.0 (нет) Мгновенно Мгновенно 0% Локальная отладка, CPU-bound продюсеры с огромным запасом сети
lz4 Средний (~2.5–4.5x) Экстремально высокая Экстремально высокая Минимальная (~3–5%) Выбор по умолчанию для 90% микросервисов и high-throughput потоков
snappy Умеренный (~2.0–3.5x) Очень высокая Очень высокая Низкая (~5–8%) Системы с жесткими SLA по latency, легаси-окружения
zstd Максимальный (~3.5–7.0x) Настраиваемая Очень высокая Средняя (~10–20%) Межрегиональная репликация (MirrorMaker), долгий retention, экономия облачного трафика
gzip Очень высокий (~3.0–6.0x) Низкая Умеренная Очень высокая (>35%) Не рекомендуется для современных систем из-за деградации пропускной способности

Почему сжатие выполняется именно на батче (Batch-level Compression)

Словарные алгоритмы (LZ4, ZSTD, Deflate) находят повторяющиеся подстроки и заменяют их ссылками на ранее встретившиеся фрагменты.

  • Сжатие единичного сообщения (например, JSON на 200 байт): Содержит заголовки и ключи {"userId": 12345, "event": "CLICK"}. Внутри одного JSON повторяемости почти нет. Заголовок самого алгоритма компрессии занимает 15–30 байт. В результате сжатое сообщение может оказаться больше оригинала!
  • Сжатие батча из 100–500 таких сообщений (40–100 КБ): Ключи JSON ("userId", "timestamp", "eventType"), метаданные и схемы повторяются сотни раз. Алгоритм формирует единый словарь повторов внутри батча, сжимая данные в 3–5 раз.

Важно: Без включенного батчинга (linger.ms > 0 и адекватного batch.size) компрессия практически теряет эффективность!

Конфигурация брокера и топика: compression.type

Поведение брокера при приеме сообщений управляется параметром compression.type на уровне топика или всего кластера (server.properties):

# 1. По умолчанию на брокере установлено значение "producer"
# Брокер НЕ распаковывает батч, а сохраняет его в лог в формате продюсера
kafka-configs.sh --bootstrap-server localhost:9092 --entity-type topics --entity-name orders \
  --alter --add-config compression.type=producer

# 2. Принудительное задание алгоритма на уровне топика (например, lz4)
kafka-configs.sh --bootstrap-server localhost:9092 --entity-type topics --entity-name telemetry \
  --alter --add-config compression.type=lz4

Возможные значения compression.type на брокере:

  • producer (рекомендуется): брокер принимает сжатый батч и записывает его на диск без изменений.
  • uncompressed: брокер принудительно распаковывает входящий батч и хранит его на диске несжатым.
  • lz4 / snappy / zstd / gzip: брокер проверяет кодек батча. Если продюсер прислал батч в другом кодеке (или несжатым), брокер обязан распаковать его и заново сжать указанным алгоритмом.

🔴 Senior Level

Сквозной жизненный цикл данных и архитектура Zero-Copy

Главное инженерное преимущество Kafka в отношении компрессии — это сохранение принципа Zero-Copy транзита данных через брокер.

+──────────────────────────────────────────────────────────────────────────────+
|                           PRODUCER (JVM + JNI)                               |
| 1. Сериализация Record -> RecordAccumulator -> Наполнение ProducerBatch       |
| 2. Завершение батча -> Вызов JNI (liblz4 / libzstd) в Off-Heap буфере        |
| 3. Формирование RecordBatch Header (Magic 2, Attributes: bits 0-2 = Codec)   |
| 4. Отправка через Java NIO SocketChannel -> Сеть                              |
+──────────────────────────────────────────────────────────────────────────────+
                                        │
                                        ▼ (Сжатый сетевой пакет TCP)
+──────────────────────────────────────────────────────────────────────────────+
|                               BROKER (Linux)                                 |
| 1. Чтение TCP сокета в сетевой буфер                                         |
| 2. Валидация заголовка батча: базовая проверка CRC, Offset, Attributes        |
| 3. НЕТ ДЕКОМПРЕССИИ: сжатые байты записываются напрямую в Page Cache через   |
|    FileChannel.write() -> Сегмент лога *.log                                 |
| 4. Запрос Consumer (FetchRequest):                                           |
|    Брокер использует системный вызов sendfile(2) (Zero-Copy):                |
|    Данные из Page Cache передаются напрямую в сокет сетевой карты (NIC),     |
|    минуя User Space брокера и не нагружая его CPU!                           |
+──────────────────────────────────────────────────────────────────────────────+
                                        │
                                        ▼ (Сжатый сетевой пакет TCP)
+──────────────────────────────────────────────────────────────────────────────+
|                           CONSUMER (JVM + JNI)                               |
| 1. Получение сжатого RecordBatch через сетевой сокет                          |
| 2. Парсинг заголовка: чтение битов компрессии в поле Attributes              |
| 3. Распаковка через JNI в память JVM                                         |
| 4. Итерация по записям ConsumerRecord и передача в poll()                    |
+──────────────────────────────────────────────────────────────────────────────+

Формат записи RecordBatch и биты атрибутов (Magic Byte 2)

Начиная с Kafka 0.11 (KIP-98 / KIP-101), используется формат сообщений RecordBatch. В заголовке пакета присутствует 16-битное поле attributes:

  • Биты 0–2 (Compression Codec):
    • 0b000 (0): No compression
    • 0b001 (1): GZIP
    • 0b010 (2): Snappy
    • 0b011 (3): LZ4
    • 0b100 (4): Zstandard (ZSTD)
  • Бит 3 (Timestamp Type): CreateTime (0) или LogAppendTime (1).
  • Бит 4 (Is Transactional): 0 — нет, 1 — транзакционный батч.
  • Бит 5 (Is Control Batch): маркер коммита/аборта транзакции.

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

Опасность перекомпрессии на брокере (Broker Recompression Trap)

Когда брокер работает в штатном режиме compression.type = producer, утилизация его процессора близка к нулю (работает только I/O).

Однако возникает сценарий катастрофической деградации кластера — Broker Recompression:

  1. Несоответствие настроек: Топик настроен с compression.type=zstd, а группа продюсеров шлет данные с compression.type=lz4 или none.
  2. Переход на LogAppendTime: Если на топике выставлено message.timestamp.type=LogAppendTime, брокер обязан перезаписать метку времени в каждой внутренней записи батча.
  3. Даунгрейд версий протокола (Message Format Down-Conversion): Если старый консьюмер (до Kafka 0.11, v0/v1) запрашивает данные из кластера с форматом v2, брокер обязан распаковать RecordBatch, переконвертировать сообщения в MessageSet v0/v1 и заново запаковать их на лету.

Последствия перекомпрессии на брокере:

  • Брокер декомпрессирует батч в Heap/Off-Heap память.
  • Распакованные сообщения валидируются, обновляются заголовки и смещения (baseOffset).
  • Брокер запускает собственный компрессор для повторного сжатия.
  • Zero-Copy полностью отключается.
  • Утилизация CPU брокеров взлетает до 100%, latency запросов Produce и Fetch вырастает в сотни раз, кластер рискует выпасть из ISR по таймаутам.

Низкоуровневая реализация: JNI и Off-Heap память

Клиенты Kafka на Java используют нативные C/C++ библиотеки через JNI:

  • LZ4: org.lz4:lz4-java (использует JNI-биндинги или pure Java / unsafe fallback, если нативный бинарник недоступен).
  • Zstandard: com.github.luben:zstd-jni (прямые JNI-вызовы к нативной реализации Zstandard).
  • Snappy: org.xerial.snappy:snappy-java.

При сжатии данные из пула BufferPool продюсера (ByteBuffer) передаются через JNI прямо в нативную память. Это предотвращает промежуточное создание массивов byte[] в Java Heap, защищая сборщик мусора (GC) от нагрузки и исключая Stop-the-World паузы при высоких RPS.

Тонкая настройка ZSTD: Уровни сжатия

Алгоритм ZSTD поддерживает уровни компрессии от 1 до 22 (по умолчанию в Kafka — 3). Начиная с Kafka 2.1, продюсер позволяет задавать уровень сжатия:

props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "zstd");
// Тонкая настройка уровня компрессии Zstandard
props.put("compression.zstd.level", 3); // 3 — оптимальный баланс CPU/Ratio
  • Уровень 1 дает производительность, близкую к LZ4, со сжатием лучше Snappy.
  • Уровни 7–9 подходят для пакетных ETL-процессов и архивных логов.
  • Уровни выше 12 не рекомендуются для real-time систем из-за экспоненциального роста задержки на стороне Producer.

4 Tricky Questions

1. Как компрессия влияет на ограничение max.message.bytes на брокере и max.request.size на продюсере?

Ответ: Проверка лимитов на клиенте и брокере выполняется над сжатым размером батча:

  1. На продюсере: параметр max.request.size (по умолчанию 1 МБ) ограничивает суммарный размер сериализованного и сжатого сетевого запроса.
  2. На брокере: параметр топика max.message.bytes (по умолчанию 1 048 588 байт ~ 1 МБ) проверяет размер входящего RecordBatch после сжатия.
  3. Ловушка при декомпрессии на консьюмере: Если продюсер собрал батч из несжатых сообщений суммарным объемом 4 МБ, сжал его с помощью ZSTD до 800 КБ, брокер успешно примет и запишет этот батч (800 КБ < 1 МБ). Однако когда консьюмер прочитает этот батч, ему потребуется выделить 4 МБ памяти в JVM Heap для распаковки. Если у консьюмера параметр max.partition.fetch.bytes меньше 800 КБ, консьюмер вообще не сможет получить этот батч и зависнет в бесконечном цикле.

2. Может ли включение компрессии уменьшить задержку (end-to-end latency) сообщений в высоконагруженной системе?

Ответ: Да, парадоксальным образом может. Хотя компрессия добавляет микросекунды на этапе сжатия в CPU продюсера и декомпрессии на консьюмере:

  1. В high-throughput системах сетевой стек (TCP socket buffers, bandwidth) и дисковый контроллер часто являются главным узким местом (bottleneck).
  2. Сжатие в 3–4 раза уменьшает размер TCP-фреймов, сокращает количество round-trips и window scaling задержек в протоколе TCP.
  3. Сжатый батч передается по сети в разы быстрее, чем суммарное время, потраченное CPU на его сжатие.
  4. На брокере сжатый пакет быстрее сбрасывается в Page Cache. В результате выигрыш во времени передачи по сети и записи на диск часто значительно превосходит накладные расходы процессора на компрессию.

3. Что произойдет, если продюсер отправляет уже предварительно сжатые файлы (например, архивы .zip, картинки .jpg или видеопотоки) с compression.type=lz4?

Ответ: Произойдет деградация производительности системы (negative impact):

  1. Предварительно сжатые бинарные форматы (JPEG, PNG, GZIP, ZIP, MP4) обладают максимальной информационной энтропией — в них практически отсутствуют повторяющиеся последовательности байт.
  2. Алгоритм LZ4 просканирует весь массив данных, не найдет совпадений в словаре и запишет данные в исходном виде, добавив служебный заголовок сжатого фрейма.
  3. Итог:
    • Коэффициент сжатия равен 1.0 (или размер даже увеличится на 0.1–1% из-за метаданных LZ4).
    • CPU продюсера будет впустую тратить такты на сканирование памяти.
    • CPU консьюмера будет впустую тратить ресурсы на распаковку.
    • Решение: Для топиков, передающих медиафайлы или предварительно сжатые бинарные блобы, компрессию продюсера следует отключать (compression.type=none).

4. Почему при compression.type = producer брокер не может проверить целостность CRC отдельных сообщений внутри батча?

Ответ: В формате RecordBatch (начиная с Kafka 0.11) контрольная сумма CRC32C вычисляется для всего батча целиком, включая его сжатое тело (payload).

  1. Отдельные записи (Record) внутри сжатого батча не имеют собственных контрольных сумм в заголовках.
  2. При получении запроса ProduceRequest брокер проверяет CRC32C заголовка батча над сжатыми байтами.
  3. Если бы брокер валидировал CRC каждого отдельного сообщения, ему пришлось бы декомпрессировать весь батч в оперативную память. Это нарушило бы фундаментальный принцип нулевых накладных расходов (Zero-Copy) на брокере.
  4. Целостность конкретных сообщений гарантируется сквозной проверкой: брокер гарантирует, что сжатый батч не поврежден при передаче по сети и записи на диск, а проверку распакованных записей производит уже конечный Consumer при декомпрессии.

🎯 Шпаргалка для интервью

30-секундный ответ (Elevator Pitch)

«Компрессия в Kafka — это платформенный механизм сжатия батчей сообщений на стороне Producer без потерь. Kafka поддерживает алгоритмы lz4, snappy, zstd и gzip. Стандартом индустрии является LZ4 благодаря минимальной нагрузке на CPU при высоком сжатии; для максимального сжатия и экономии межрегионального трафика используется ZSTD. Сжатие применяется не к отдельным сообщениям, а к целому батчу, что позволяет словарным алгоритмам эффективно сжимать повторяющиеся структуры (JSON, Protobuf, Avro). Брокер хранит данные в сжатом виде и передает их консьюмеру через Zero-Copy (sendfile) без декомпрессии, а распаковка происходит на стороне Consumer. Критическая ошибка — изменение compression.type на брокере, что вызывает принудительную перекомпрессию и 100% утилизацию CPU кластера».

Ключевые метрики для Production-мониторинга

  • kafka.producer:type=producer-metrics,client-id={id} -> compression-rate-avg: средний коэффициент сжатия (отношение несжатого размера к сжатому). Значение 0.3 означает, что размер данных уменьшился до 30% от оригинала (сжатие в 3.3 раза). Если близко к 1.0 — батчинг не работает или данные не сжимаемы.
  • kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec / BytesOutPerSec: входящий и исходящий трафик брокера в байтах.
  • kafka.server:type=BrokerTopicMetrics,name=RecompressionRate: частота перекомпрессии сообщений брокером. Должна быть строго 0.
  • jvm_cpu_load на брокерах: резкий рост при неизменном трафике часто сигнализирует о включившейся перекомпрессии или даунгрейде формата сообщений.

Красные флаги (Чего говорить НЕЛЬЗЯ)

  • ❌ «Kafka сжимает каждое сообщение отдельно перед помещением в батч» — Грубая ошибка. Сжимается уже полностью сформированный ProducerBatch. Поштучное сжатие микро-сообщений неэффективно.
  • ❌ «Брокер распаковывает каждое сообщение, сохраняет в базу, а при отправке консьюмеру снова сжимает» — Брокер сохраняет сжатый батч на диск “as is” и отправляет через sendfile(2) Zero-Copy.
  • ❌ «GZIP — лучший выбор для production, потому что он сильнее всех сжимает» — GZIP создает огромную нагрузку на CPU (>30-40%) и превращает продюсеры в узкое горлышко. Стандарт — LZ4 или ZSTD.
  • ❌ «Компрессия бесплатна и ее нужно включать везде» — Требует ресурсов CPU. Для уже сжатых данных (картинки, архивы) она бесполезна и вредна.

Связанные темы