📨 Раздел 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

Внутренняя архитектура брокeра: 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/hard reset), а Брокеры 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 упал ниже минимума. Сообщение осталось в логе лидера, но продюсеру возвращается ошибка (при retry с идемпотентностью дублирования не произойдет).

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

Главное о 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.

Связанные вопросы