awesome-everything EN
↑ Обратно к восхождению

Очереди, потоки, события

Kafka exactly-once semantics: idempotent producer и транзакции

Суть Как Kafka idempotent producer убирает producer-retry дубликаты с ~3% overhead, и как транзакции расширяют exactly-once на multi-partition записи и offset commits с ~20–30% overhead.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 12 min

Kafka Streams job обогащает order-события и пишет результаты в output topic. Во время broker rolling restart job ретраит батч, уже записанный, производя 3000 дублирующих output-записей downstream. Фикс — одна строка конфига: enable.idempotence=true. Фича существует с 2017 года. Никто её не включал.

Слой 1 — Idempotent producer (KIP-98, Kafka 0.11.0)

Idempotent producer убирает дубликаты, вызванные producer-ретраями в брокер.

При запуске producer получает уникальный producer-ID от брокера. Каждое сообщение в partition несёт монотонно растущий sequence number, scoped к этому producer-ID и partition. Брокер трекает последний sequence per (producer-ID, partition).

Когда producer ретраит — потому что сеть уронила ack — брокер снова видит (producer-ID, sequence). Он распознаёт дубликат, тихо ack-ит и отбрасывает запись. Out-of-order sequences (возможны при позднем retry) отвергаются.

  • Включается через: enable.idempotence=true
  • Overhead: ~3% throughput
  • Доступно с: Kafka 0.11.0 (середина 2017)

Решает только дубликаты ножки 1. Не помогает при краше consumer после обработки.

Слой 2 — Transactional producer (KIP-98)

Транзакции расширяют exactly-once на полный read-process-write pipeline внутри Kafka.

Transactional producer оборачивает батч записей через несколько partition плюс consumer-offset commit в одну атомарную единицу, управляемую transaction coordinator брокером. Либо все записи и offset commit видны downstream consumer-ам, либо ничего.

Типичный Kafka Streams паттерн:

beginTransaction()
  consume из input-partition P0 at offset 42
  produce в output-partition P1
  sendOffsetsToTransaction(group-id, {P0: offset 43})
commitTransaction()

Если job падает в полёте, транзакция abort-ится на рестарте. Input offset не advance-ится. Частичный output rolled back (consumer-ы с isolation.level=read_committed пропускают aborted-записи). Job переобрабатывает с offset 42 — idempotent producer дедупит retry.

  • Включается через: transactional.id=my-app-v1
  • Consumer должен установить: isolation.level=read_committed
  • Overhead: ~20–30% throughput (two-phase commit между coordinator и partition leaders)
Kafka exactly-once: три слоя
Idempotent producer
Убирает: producer-retry дубликаты (ножка 1)
Config: enable.idempotence=true | Cost: ~3%
Транзакции
Убирает: partial-write + offset-skip (только внутри Kafka)
Config: transactional.id + read_committed | Cost: ~20–30%
Consumer dedup
Убирает: cross-system дубликаты (Kafka → Postgres, Kafka → Stripe)
Config: ON CONFLICT DO NOTHING + Idempotency-Key | Cost: одна DB-запись

Что Kafka transactions НЕ покрывают

Kafka transactions атомарны внутри Kafka. Как только ты пишешь в Postgres или вызываешь Stripe — ты выходишь за границу транзакции. Если Kafka offset commit-ится, но Postgres write падает (или наоборот), возникает partial-write gap.

Для Kafka-to-DB pipeline правильный паттерн — всё равно consumer-side dedup с идемпотентной DB-записью (ON CONFLICT DO NOTHING с Kafka offset как частью primary key). 20–30% Kafka transaction overhead заменяется одной дешёвой DB unique-constraint проверкой. Большинство production stream-to-DB pipeline выбирают этот гибрид: at-least-once Kafka delivery + idempotent DB consumer.

Викторина

Kafka idempotent producer убирает какой класс дубликатов?

Викторина

Kafka Streams job использует транзакции и пишет и в Kafka output topic, и в Postgres. Гарантирует ли Kafka-транзакция exactly-once для Postgres-записи?

Вспомните перед уходом
  1. 01
    Какие два значения Kafka-брокер трекает для дедупликации idempotent producer retry?
  2. 02
    Что делает isolation.level=read_committed на Kafka consumer?
  3. 03
    KIP-98 shipped в какой версии и году Kafka?
Итог

Exactly-once семантика Kafka строится в трёх слоях. Idempotent producer (enable.idempotence=true, ~3% cost) назначает producer-ID и per-partition sequence numbers, чтобы брокер мог прозрачно дедупить retry. Транзакции (transactional.id + read_committed consumer-ы, ~20–30% cost) оборачивают multi-partition записи и offset commits в одну атомарную единицу, управляемую coordinator-брокером, позволяя полному read-process-write-commit циклу внутри Kafka быть exactly-once. Но транзакции скопированы к Kafka: любая запись в Postgres или внешний API выходит за границу транзакции и требует своего idempotency-механизма — обычно ON CONFLICT DO NOTHING с Kafka offset как частью ключа.

Связанные уроки
встречается в178
Продолжить восхождение ↑SQS visibility timeout, DLQ и outbox pattern
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.