Acks и Retries
Настройка acks — это компромисс между надёжностью и производительностью. Этот урок объясняет, что на самом деле означают acks=0, acks=1 и acks=all, как min.insync.replicas влияет на реальную гарантию, и что происходит при ошибках сети — механизм retry.
Понимание этих параметров критически важно: неправильная конфигурация может означать, что ваш продюсер думает, что данные сохранены, хотя они потеряны.
acks=0 — Fire and Forget
При acks=0 продюсер не ждёт никакого подтверждения от брокера. Сообщение считается успешно отправленным, как только оно покинуло буфер продюсера и было передано в сеть.
producer = KafkaProducer(
bootstrap_servers=['broker:9092'],
acks=0, # Fire and forget
)
future = producer.send('metrics', value={'cpu': 95.2})
# future.get() всегда вернёт offset=0 или фиктивный offset
# Нет гарантии, что брокер получил сообщение
Характеристики:
- Максимальная пропускная способность (нет round-trip ожидания)
- Минимальная latency отправки
- Нет гарантии доставки: брокер может упасть до записи, сеть — обрезать соединение
retriesигнорируется — продюсер не знает об ошибках брокера
Применение: метрики и аналитические события, где потеря 0.1% данных допустима и throughput критически важен.
acks=1 — Leader Acknowledged
При acks=1 продюсер ждёт подтверждения от leader-реплики партиции. Брокер записывает сообщение в свой log и отвечает продюсеру.
producer = KafkaProducer(
bootstrap_servers=['broker:9092'],
acks='1', # или acks=1 — эквивалентно
)
Характеристики:
- Leader подтвердил запись — сообщение в log leader’а
- Follower-реплики могут ещё не получить сообщение
- Если leader падает до репликации на follower — сообщение потеряно
Producer → ProduceRequest
Продюсер отправляет запись на broker-leader партиции. ProduceRequest содержит один или несколько батчей.Leader: записал, ответил ACK
Broker Leader получает запись, пишет в свой log (append). Отвечает ProduceResponse с offset — всё в порядке с точки зрения продюсера.Follower: ещё не получил
Follower-реплики асинхронно тянут (fetch) данные от leader. В момент ACK продюсеру follower может ещё не получить запись. Если leader упадёт в этот момент — запись потеряна: новый leader (бывший follower) не знает об этой записи.Продюсер: доставка подтверждена (но follower не синхронизирован!)
Продюсер считает доставку успешной (получил ACK). Но если leader упадёт прямо сейчас — новый leader, избранный из follower, не будет содержать эту запись. Данные потеряны, хотя продюсер не знает об этом.Применение: большинство production-сценариев с умеренными требованиями к надёжности. Риск потери данных существует, но мал (только при одновременном падении leader сразу после записи).
acks=all — All ISR Acknowledged
При acks=all (или acks=-1) продюсер ждёт подтверждения от всех реплик в ISR (In-Sync Replicas — реплики, синхронизированные с leader’ом).
producer = KafkaProducer(
bootstrap_servers=['broker:9092'],
acks='all', # или acks=-1 — эквивалентно
)
Механизм:
- Продюсер отправляет запись leader’у
- Leader записывает в свой log
- Leader ожидает, пока все follower’ы из ISR не подтвердят репликацию (fetch + запись)
- Leader отвечает продюсеру ACK
Если leader падает после ACK, новый leader гарантированно содержит запись — она была реплицирована на все ISR-реплики.
min.insync.replicas — критически важный параметр
min.insync.replicas определяет минимальное количество реплик в ISR, которые должны подтвердить запись при acks=all. Устанавливается на уровне топика или брокера.
# На уровне топика (kafka-configs или при создании)
kafka-topics.sh --create --topic orders \
--replication-factor 3 \
--config min.insync.replicas=2
acks=all с min.insync.replicas=1 ЭКВИВАЛЕНТНО acks=1 с точки зрения надёжности! Если ISR содержит только одну реплику (leader), acks=all ждёт только подтверждения от неё. Падение leader приведёт к потере данных так же, как и при acks=1. Для реальной защиты от потери данных нужны: acks=all + min.insync.replicas=2 + replication.factor=3.
Правильная конфигурация для гарантии без потерь
# Продюсер
producer = KafkaProducer(
bootstrap_servers=['broker:9092'],
acks='all',
retries=10,
enable_idempotence=True, # Обязателен при retries > 0 с acks=all
)
# Топик с replication.factor=3, min.insync.replicas=2
# Терпит падение 1 из 3 брокеров без потери данных
# При падении 2 из 3 — запись заблокируется (NotEnoughReplicasException)
Сравнение конфигураций надёжности
| Конфигурация | Допустимых падений | Потеря данных | Throughput |
|---|---|---|---|
| acks=0 | Любое | Возможна всегда | Максимальный |
| acks=1 | До падения leader | При падении leader до репликации | Высокий |
| acks=all, min.isr=1 | До падения leader | При падении leader | Высокий |
| acks=all, min.isr=2, RF=3 | 1 из 3 брокеров | Нет (при RF=3) | Умеренный |
| acks=all, min.isr=3, RF=3 | 0 | Нет | Низкий |
Retry-механизм
Когда брокер возвращает ошибку или соединение обрывается, продюсер может автоматически повторить попытку.
producer = KafkaProducer(
bootstrap_servers=['broker:9092'],
acks='all',
retries=2147483647, # Максимальное число попыток (по умолчанию в Kafka 2.1+)
retry_backoff_ms=100, # Пауза между попытками (по умолчанию 100 мс)
delivery_timeout_ms=120000, # Общий таймаут доставки — 2 минуты
request_timeout_ms=30000, # Таймаут одного запроса — 30 секунд
)
delivery.timeout.ms — главный контролирующий параметр
delivery.timeout.ms (по умолчанию 120 000 мс = 2 минуты) — это суммарное время, в течение которого продюсер будет пытаться доставить сообщение, включая:
- время в буфере (до первой отправки)
- время всех retry-попыток
- задержки между попытками (
retry.backoff.ms)
Жизнь сообщения:
send() → [буфер] → попытка 1 → FAIL → [пауза 100мс] →
попытка 2 → FAIL → [пауза 200мс] → попытка 3 → OK
<─────────────── delivery.timeout.ms ────────────────>
Если delivery.timeout.ms истёк — future.get() выбрасывает TimeoutException.
Ошибки: retriable vs non-retriable
Не все ошибки брокера подлежат повтору.
Retriable ошибки (продюсер повторяет автоматически):
LEADER_NOT_AVAILABLE— leader партиции недоступен (переизбрание)NOT_ENOUGH_REPLICAS— ISR меньшеmin.insync.replicasREQUEST_TIMED_OUT— таймаут запросаNETWORK_EXCEPTION— сетевые ошибки
Non-retriable ошибки (продюсер НЕ повторяет):
INVALID_TOPIC_EXCEPTION— топик не существует и auto.create отключёнMESSAGE_TOO_LARGE— сообщение превышаетmax.message.bytesAUTHORIZATION_FAILED— нет прав на топикRECORD_LIST_TOO_LARGE— батч превышает лимит
from kafka.errors import KafkaError
def on_send_error(exc):
if exc.retriable:
print(f"Retriable error, будет retry: {exc}")
else:
print(f"Fatal error, retry не поможет: {exc}")
# Нужна ручная обработка, алерт, dead-letter queue
producer.send('orders', value=order) \
.add_errback(on_send_error)
Риск нарушения порядка при retry
При retries > 0 и max.in.flight.requests.per.connection > 1 возможна ситуация, когда сообщения приходят на брокер в другом порядке:
Продюсер отправляет: Батч 1, Батч 2
Батч 1 потерян → retry
Батч 2 доставлен успешно
Батч 1 доставлен (retry) → порядок нарушен!
Решения:
max.in.flight.requests.per.connection=1— только один запрос в полёте (снижает throughput)enable.idempotence=True— брокер сам выстраивает порядок по sequence number (рекомендуется)
В Kafka 3.0+ enable.idempotence=True является значением по умолчанию. При этом acks автоматически устанавливается в all, retries — в максимальное значение, max.in.flight.requests.per.connection — в 5. Идемпотентность и порядок гарантированы без ручной настройки.
Ключевые выводы
acks=0— максимальная скорость, нет гарантий.acks=1— лидер подтвердил.acks=all— все ISR подтвердили.acks=allбезmin.insync.replicas=2+— ложная безопасность. Эквивалентенacks=1при ISR=1.- Правильный рецепт надёжности:
acks=all+min.insync.replicas=2+replication.factor=3. delivery.timeout.msконтролирует суммарное время попыток доставки.- Retry при
max.in.flight > 1может нарушить порядок — используйтеenable.idempotence=True.