Як працює компресія повідомлень
На відміну від класичних черг повідомлень (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 КБ]
Навіщо потрібна компресія
- Зниження мережевого трафіку (Network Bandwidth): Скорочення обсягу переданих байтів між продюсерами, брокерами та консьюмерами в 2–7 разів, що критично при міжсегментній передачі (cross-AZ, cross-region у хмарних провайдерів, де за вихідний трафік стягується плата).
- Економія дискового простору: Логи партицій (
.log) зберігаються на брокерах у стиснутому вигляді, що багаторазово збільшує глибину зберігання (retention period) без закупівлі додаткових NVMe/SSD дисків. - Ефективне використання Page Cache: В ОС брокера вміщується значно більше стиснутих сегментів лога, що підвищує кеш-хіти та знижує фізичні читання з диска.
- Зростання загальної пропускної здатності (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 compression0b001(1): GZIP0b010(2): Snappy0b011(3): LZ40b100(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:
- Невідповідність налаштувань: Топік налаштований з
compression.type=zstd, а група продюсерів шле дані зcompression.type=lz4абоnone. - Перехід на LogAppendTime: Якщо на топіку виставлено
message.timestamp.type=LogAppendTime, брокер зобов’язаний перезаписати мітку часу в кожному внутрішньому записі батча. - Даунгрейд версій протоколу (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 на продюсері?
Відповідь: Перевірка лімітів на клієнті та брокері виконується над стиснутим розміром батча:
- На продюсері: параметр
max.request.size(за замовчуванням 1 МБ) обмежує сумарний розмір серіалізованого та стиснутого мережевого запиту. - На брокері: параметр топіка
max.message.bytes(за замовчуванням 1 048 588 байт ~ 1 МБ) перевіряє розмір вхідногоRecordBatchпісля стиснення. - Пастка при декомпресії на консьюмері:
Якщо продюсер зібрав батч із нестиснутих повідомлень сумарним обсягом 4 МБ, стиснув його за допомогою ZSTD до 800 КБ, брокер успішно прийме і запише цей батч (800 КБ < 1 МБ). Проте коли консьюмер прочитає цей батч, йому знадобиться виділити 4 МБ пам’яті в JVM Heap для розпакування. Якщо у консьюмера параметр
max.partition.fetch.bytesменший за 800 КБ, консьюмер взагалі не зможе отримати цей батч і зависне в нескінченному циклі.
2. Чи може ввімкнення компресії зменшити затримку (end-to-end latency) повідомлень у високонавантаженій системі?
Відповідь: Так, парадоксальним чином може. Хоча компресія додає мікросекунди на етапі стиснення в CPU продюсера та декомпресії на консьюмері:
- У high-throughput системах мережевий стек (TCP socket buffers, bandwidth) та дисковий контролер часто є головним вузьким місцем (bottleneck).
- Стиснення в 3–4 рази зменшує розмір TCP-фреймів, скорочує кількість round-trips та window scaling затримок у протоколі TCP.
- Стиснутий батч передається по мережі в рази швидше, ніж сумарний час, витрачений CPU на його стиснення.
- На брокері стиснутий пакет швидше скидається в Page Cache. У результаті виграш у часі передачі по мережі та запису на диск часто значно перевершує накладні витрати процесора на компресію.
3. Що станеться, якщо продюсер відправляє вже попередньо стиснуті файли (наприклад, архіви .zip, картинки .jpg або відеопотоки) з compression.type=lz4?
Відповідь: Відбудеться деградація продуктивності системи (negative impact):
- Попередньо стиснуті бінарні формати (JPEG, PNG, GZIP, ZIP, MP4) мають максимальну інформаційну ентропію — у них практично відсутні повторювані послідовності байтів.
- Алгоритм LZ4 просканує весь масив даних, не знайде збігів у словнику і запише дані у вихідному вигляді, додавши службовий заголовок стиснутого фрейму.
- Підсумок:
- Коефіцієнт стиснення дорівнює 1.0 (або розмір навіть збільшиться на 0.1–1% через метадані LZ4).
- CPU продюсера буде марно витрачати такти на сканування пам’яті.
- CPU консьюмера буде марно витрачати ресурси на розпакування.
- Рішення: Для топіків, що передають медіафайли або попередньо стиснуті бінарні блоби, компресію продюсера слід вимикати (
compression.type=none).
4. Чому при compression.type = producer брокер не може перевірити цілісність CRC окремих повідомлень усередині батча?
Відповідь:
У форматі RecordBatch (починаючи з Kafka 0.11) контрольна сума CRC32C обчислюється для всього батча цілком, включаючи його стиснуте тіло (payload).
- Окремі записи (
Record) усередині стиснутого батча не мають власних контрольних сум у заголовках. - При отриманні запиту
ProduceRequestброкер перевіряє CRC32C заголовка батча над стиснутими байтами. - Якби брокер валідував CRC кожного окремого повідомлення, йому довелося б декомпресувати весь батч в оперативну пам’ять. Це порушило б фундаментальний принцип нульових накладних витрат (Zero-Copy) на брокері.
- Цілісність конкретних повідомлень гарантується наскрізною перевіркою: брокер гарантує, що стиснутий батч не пошкоджено при передачі по мережі та запису на диск, а перевірку розпакованих записів здійснює вже кінцевий 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. Для вже стиснутих даних (картинки, архіви) вона марна і шкідлива.