📨 Розділ 15 · Питання #20

Що таке producer acknowledgment і які режими існують (acks=0,1,all)

Починаючи з Kafka 3.0, значення за замовчуванням змінено з acks=1 на acks=all спільно з увімкненою за замовчуванням ідемпотентністю (enable.idempotence=true).


🟢 Junior Level

30-секундна відповідь

Producer Acknowledgment (acks) — це параметр конфігурації продюсера в Apache Kafka, який визначає, скільком брокерам (реплікам) необхідно підтвердити отримання повідомлення перед тим, як продюсер вважатиме відправку успішною:

  1. acks=0 (Fire and Forget): продюсер не очікує відповіді від брокера. Повідомлення вважається надісланим одразу після розміщення в локальному буфері сокета. Максимальна пропускна здатність, нульова гарантія доставки.
  2. acks=1 (Leader Only): продюсер чекає підтвердження тільки від брокера-лідера партиції. Якщо лідер вийде з ладу до того, як репліки скопіюють запис, дані будуть втрачені.
  3. acks=all (або acks=-1) (Leader + ISR): продюсер очікує підтвердження від лідера та всіх синхронізованих реплік (In-Sync Replicas, ISR). Максимальна надійність (при min.insync.replicas >= 2).

Починаючи з Kafka 3.0, значення за замовчуванням змінено з acks=1 на acks=all спільно з увімкненою за замовчуванням ідемпотентністю (enable.idempotence=true).


Наочна аналогія з життя

  • acks=0 (Звичайний лист у синю поштову скриньку на вулиці): вкинув конверт у щілину скриньки і пішов далі. Жодних квитанцій; згоріла скринька — лист безслідно зник.
  • acks=1 (Вручення посилки кур’єру в руки): кур’єр розписався в накладній і забрав пакунок. Але якщо кур’єр потрапить в аварію по дорозі до сортувального центру — посилка не дійде.
  • acks=all (Банківський сейф із трьома ключами): головний клерк та його довірені асистенти (ISR) одночасно реєструють злиток у своїх журналах. Лише коли запис зроблено у кожного клерка, клієнту видається офіційний сертифікат.

Приклад налаштування в Java

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

// Конфігурація максимальної надійності
props.put(ProducerConfig.ACKS_CONFIG, "all");
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true");
props.put(ProducerConfig.RETRIES_CONFIG, Integer.MAX_VALUE);

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

🟡 Middle Level

Детальна механіка роботи режимів

acks=0:
  Producer ---- [Message] ----> (Socket Buffer) ----> Одразу Success! (не чекає брокера)

acks=1:
  Producer ---- [Message] ----> Broker Leader (запис у Page Cache) ----> Success!
                                      |
                               (асинхронно реплікується фоловерами)

acks=all:
  Producer ---- [Message] ----> Broker Leader (Log.appendAsLeader)
                                      |-- реплікація на Follower 1 (ACK)
                                      |-- реплікація на Follower 2 (ACK)
                                      v
                               Тільки коли всі ISR підтвердили ----> Success!

Чому acks=all не рятує без min.insync.replicas

Параметр acks=all вимагає підтвердження від усіх учасників поточного ISR.

  • Якщо у топіка replication.factor=3, але два брокери впали, в ISR залишиться рівно один вузол (сам лідер).
  • У цьому стані acks=all зажадає підтвердження лише від цього єдиного лідера, деградуючи до acks=1!
  • Якщо єдиний лідер аварійно зупиниться до підняття впалих нод — підтверджені повідомлення будуть втрачені.

Золоте правило надійності Kafka:

  1. replication.factor = 3
  2. min.insync.replicas = 2
  3. acks = all

Якщо розмір ISR стає меншим за min.insync.replicas, лідер відхиляє запис продюсера і повертає NotEnoughReplicasException.


Порівняльна таблиця режимів

Режим Підтвердження від кого Latency (затримка) Throughput Ризик втрати даних Підтримка Retries Типовий Use Case
acks=0 Нікого (буфер сокета) < 1 ms Максимальний Дуже високий ❌ Неможливий Метрики, клікстрім, GPS-трекінг датчиків
acks=1 Тільки Лідер 2–5 ms Високий Середній (падіння лідера до sync) ✅ Так Логування, інвалідація кешів
acks=all Лідер + весь ISR 5–20 ms Високий (при батчингу) Нульовий (при min.insync >= 2) ✅ Так Платежі, замовлення, білінг, критичні події

🔴 Senior Level

Внутрішня архітектура брокера: DelayedProduce та Purgatory

Коли брокер-лідер отримує ProduceRequest з acks=all, він не блокує мережевий потік KafkaRequestHandler:

  1. Запис у локальний лог: Лідер виконує Log.appendAsLeader(), зберігаючи батч повідомлень у Page Cache своєї ОС.
  2. Розміщення в Purgatory: Створюється об’єкт DelayedProduce, що поміщається в структуру даних DelayedOperationPurgatory (ієрархічний таймер на основі тайм-коліс Hierarchical Timing Wheels).
  3. Реплікація фоловерами: Фоловери запитують дані через FetchRequest. У міру підтвердження зсувів (LEO) лідер оновлює статус реплікації.
  4. Зсув High Watermark (HW): Коли всі члени поточного ISR наздогнали оффсет нового батча, High Watermark просувається вперед.
  5. Завершення DelayedProduce: Purgatory тригерить callback, формується ProduceResponse, і продюсер отримує підтвердження про успішний запис.
Producer                     Broker (Leader)                   Follower (ISR)
   |                                |                                 |
   |--- ProduceRequest (acks=all) ->|                                 |
   |                                |-- 1. Log.appendAsLeader()       |
   |                                |-- 2. Register DelayedProduce    |
   |                                |      in Purgatory               |
   |                                |                                 |
   |                                |<--- FetchRequest (LEO update) --|
   |                                |--- FetchResponse -------------->|
   |                                |                                 |
   |                                |-- 3. HW advanced to match LEO   |
   |                                |-- 4. Complete DelayedProduce    |
   |<-- ProduceResponse (Success) --|                                 |

Взаємодія ACKS з Idempotence та max.in.flight.requests

Для запобігання дублюванню та порушенню порядку повідомлень при ретраях:

  • enable.idempotence=true вимагає:
    • acks=all (або -1)
    • retries > 0
    • max.in.flight.requests.per.connection <= 5
  • Кожному повідомленню призначається монотонний SequenceNumber у межах продюсера (ProducerId). Якщо лідер отримує дублікат батча (наприклад, через втрату ACK за таймаутом), він підтверджує отримання продюсеру, але не записує дублікат у лог повторно.

4 Tricky Questions

1. Чому зв’язка acks=all та replication.factor=3 без налаштування min.insync.replicas може призвести до втрати підтверджених повідомлень?

Відповідь: За замовчуванням у ранніх версіях Kafka або при недбалому налаштуванні топіка min.insync.replicas=1.

  • Уявімо топік з 3 репліками. Брокери 2 та 3 вийшли з ладу.
  • Контролер виключає брокери 2 та 3 з ISR. В ISR залишається тільки один Брокер 1 (Лідер).
  • Продюсер з acks=all надсилає повідомлення. Лідер перевіряє: «Чи всі члени поточного ISR підтвердили запис?». Оскільки в ISR лише він сам, запис фіксується негайно, і продюсеру повертається Success!
  • Якщо через секунду Брокер 1 виходить з ладу (апаратний збій / kernel panic), а Брокери 2 та 3 повертаються до ладу, новим лідером стане вузол без цих даних. Підтверджені повідомлення безповоротно втрачені.
  • Захист: встановлення min.insync.replicas=2 змушує Лідера негайно викидати NotEnoughReplicasException, блокуючи запис у вразливому стані.

2. Чи може продюсер з acks=0 вичерпати пам’ять і впасти з OutOfMemoryError або заблокуватися?

Відповідь: Так, може.

  • При acks=0 продюсер не очікує відповіді від брокера, але повідомлення все одно поміщаються у внутрішній буфер RecordAccumulator перед відправкою по мережі фоновим потоком Sender.
  • Якщо швидкість генерації повідомлень застосунком перевищує пропускну здатність мережевого сокета або буфер брокера переповнений, буфер buffer.memory (типово 32 МБ) заповниться повністю.
  • У цей момент метод producer.send() заблокується на час max.block.ms (типово 60 секунд), після чого викине TimeoutException.
  • Якщо застосунок не обробляє помилки і накопичує посилання на об’єкти в heap, це призведе до OutOfMemoryError.

3. Брокер перебуває у важкій Stop-the-World паузі GC. Як це вплине на продюсерів з acks=all?

Відповідь:

  • Якщо завис фолловер, що входить до ISR:
    • Лідер чекає на відповідь від цього фолловера, утримуючи запит продюсера в Purgatory.
    • Продюсери відчувають різке зростання latency аж до значення request.timeout.ms (типово 30с).
    • Лідер очікує таймауту replica.lag.time.max.ms (типово 30с). Після завершення цього часу лідер оголошує фолловер вибулим і виключає його з ISR (ISR Shrink).
    • Щойно розмір ISR зменшився, умова запису вважається виконаною, і продюсер отримує запізнілий Success.
  • Якщо завис сам лідер: продюсер чекає request.timeout.ms, отримує TimeoutException і повторює відправку (retries).

4. У чому різниця між NotEnoughReplicasException та NotEnoughReplicasAfterAppendException?

Відповідь:

  • NotEnoughReplicasException: повертається до спроби запису в локальний журнал лідера. Лідер перевірив поточний розмір ISR, побачив size(ISR) < min.insync.replicas і негайно відхилив запит продюсера. Лог лідера не змінено.
  • NotEnoughReplicasAfterAppendException: брокер успішно записав повідомлення у свій локальний лог (оскільки на момент старту розмір ISR був достатнім), проте під час очікування підтвердження від фолловерів репліки відвалилися, і розмір ISR впав нижче мінімуму. Повідомлення залишилося в лозі лідера, але продюсеру повертається помилка (при повторі з ідемпотентністю дублювання не відбудеться).

🎯 Шпаргалка для інтерв’ю

Головне про Producer Acknowledgment

  • acks=0: відправив і забув; нульова затримка, нульова гарантія, відсутність ретраїв.
  • acks=1: підтвердження від лідера; безпечно при стабільній роботі, ризик втрати при аварії лідера до завершення реплікації.
  • acks=all: підтвердження від лідера та всіх учасників ISR. Стандарт за замовчуванням у Kafka 3.0+.
  • Формула надійності: replication.factor=3 + min.insync.replicas=2 + acks=all + enable.idempotence=true.
  • Purgatory: механізм асинхронного утримання запитів acks=all на брокері без блокування потоків вводу-виводу.

Матриця режимів ACKS

| Параметр | acks=0 | acks=1 | acks=all | | :— | :— | :— | :— | | Ким підтверджується | Локальний сокет | Тільки Лідер | Лідер + усі члени ISR | | Гарантія доставки | At-most-once | At-least-once (з ризиком) | At-least-once / Exactly-once (з ідемпотентністю) | | Втрати при падінні лідера | Ймовірні | Можливі | Виключені (при ISR >= 2) | | Дефолтне значення | Kafka 0.8.x | Kafka 0.9.x – 2.8.x | Kafka 3.0+ |

Червоні прапорці на співбесіді (Чого говорити не можна)

  • ❌ «При acks=all продюсер сам відправляє повідомлення паралельно на всі брокери топіка» — продюсер спілкується виключно з лідером партиції.
  • ❌ «acks=all гарантує стовідсоткову відсутність дублікатів» — acks=all відповідає за надійність збереження; від дублікатів захищає ідемпотентність (enable.idempotence=true).
  • ❌ «Якщо acks=all, повідомлення одразу стає доступним консьюмерам» — консьюмери читають суворо до High Watermark, тому повідомлення стає доступним лише після фіксації всім ISR.

Пов’язані запитання