Питання 9 · Розділ 15

Які гарантії доставки повідомлень надає Kafka

Kafka надає три рівні гарантій доставки:

Мовні версії: English Russian Ukrainian

Рівень Junior

Три рівні гарантій

Kafka надає три рівні гарантій доставки:

1. At most once (не більше одного разу):

Повідомлення доставиться максимум один раз
Може загубитися при помилках

Як губиться: продюсер відправив і забув (acks=0). Якщо мережа впала — повідомлення загублене. Консьюмер автокоммітить offset до обробки — якщо консьюмер впав після комміту але до обробки — повідомлення загублене.

2. At least once (як мінімум один раз):

Повідомлення доставиться мінімум один раз
Може продублюватися при retry

3. Exactly once (рівно один раз):

Повідомлення доставиться рівно один раз
Працює ТІЛЬКИ для сценаріїв Kafka-to-Kafka. Для фінансових операцій з записом в БД потрібен Outbox pattern або idempotent writes.

Візуалізація

At most once:
  Продюсер → Kafka → (можливо втрачено) → Консьюмер

At least once:
  Продюсер → Kafka → Консьюмер → (можливо дублікат)

Exactly once:
  Продюсер → Kafka → Консьюмер → рівно один раз

Просте налаштування

// At most once
props.put("acks", "0");

// At least once (рекомендується)
props.put("acks", "all");
props.put("enable.idempotence", "true");

// Exactly once
props.put("enable.idempotence", "true");
props.put("isolation.level", "read_committed");

Коли НЕ використовувати кожну семантику

  • At-most-once — НЕ для платежів, замовлень, сповіщень
  • At-least-once — НЕ коли дублікати критичні (без ідемпотентної обробки)
  • Exactly-once — НЕ для Kafka-to-Database, НЕ для Kafka-to-HTTP

Рівень Middle

Налаштування гарантій

At most once:

props.put("acks", "0");
props.put("enable.idempotence", "false");
props.put("enable.auto.commit", "true");
// Швидко, але можлива втрата даних

At least once:

props.put("acks", "all");
props.put("enable.idempotence", "true");
props.put("retries", Integer.MAX_VALUE);
props.put("enable.auto.commit", "false");
// Надійно, але можливі дублікати

Exactly once:

// Producer
props.put("enable.idempotence", "true");
props.put("acks", "all");

// Consumer
props.put("isolation.level", "read_committed");
props.put("enable.auto.commit", "false");

Transaction API для Exactly Once

producer.initTransactions();

try {
    producer.beginTransaction();

    // Читання
    ConsumerRecords<String, String> records = consumer.poll();

    // Обробка
    for (var record : records) {
        process(record);
    }

    // Запис результату
    producer.send(new ProducerRecord<>("output", result));

    // Комміт обох дій
    producer.commitTransaction();

} catch (Exception e) {
    // Відкат при помилці
    producer.abortTransaction();
}

Типові помилки

  1. At least once без ідемпотентності:
    Retry → дублікати в топику → обробка двічі
    
  2. Комміт до обробки:
    consumer.commitSync();  // спочатку комміт
    process(records);       // потім обробка
    // Якщо впав → дані втрачені
    
  3. Exactly once без transaction:
    Producer і consumer не в одній транзакції
    → Можлива втрата або дублювання
    

Рівень Senior

Internal Implementation

Idempotent Producer:

Кожен producer отримує унікальний PID (Producer ID)
Кожне повідомлення отримує Sequence Number
Брокер відстежує послідовність

Дублікат з тим самим PID + Sequence → відхиляється

Transaction Coordinator:

Окремий компонент координує транзакції
Зберігає state транзакцій в __transaction_state topic
Забезпечує atomic commit/abort

Exactly Once — обмеження

Exactly Once працює тільки для:
  Kafka → Kafka (read-process-write)

Не працює для:
  Kafka → Database (Kafka не управляє транзакцією БД)
  Kafka → HTTP API (немає guarantees на стороні API)

Рішення для зовнішніх систем:

1. Idempotent writes в БД (unique constraints)
2. Two-phase commit (XA — не рекомендується)
3. Outbox pattern
4. Debezium CDC

End-to-End Exactly Once

Сценарій: Kafka → Processing → PostgreSQL

Рішення: Outbox Pattern
1. Записати в outbox таблицю (в тій самій транзакції)
2. Debezium CDC читає outbox
3. Відправляє в Kafka
4. Consumer обробляє і пише в target table
5. Idempotent upsert по business key

Failure Scenarios

1. Producer failure:

Відправив → не отримав ack → retry
Idempotent producer → дублікат відхилений

2. Broker failure:

Записав в Leader → не встиг реплікувати → Leader впав
acks=all + min.insync.replicas=2 → дані збережені

3. Consumer failure:

Прочитав → не обробив → не закоммітив
При перезапуску → прочитає знову (at least once)
Idempotent processing → дублікат оброблено коректно

Performance Trade-offs

Гарантія Latency Throughput Data Safety
At most once Мінімальна Максимальна Низька
At least once Середня Висока Висока
Exactly once Висока Середня Максимальна

Best Practices

✅ At least once за замовчуванням
✅ Exactly once коли критично (фінанси, біллінг)
✅ Ідемпотентна обробка на консьюмері
✅ Idempotent producer (enable.idempotence=true)
✅ Transactional read-process-write для Kafka-to-Kafka
✅ Outbox pattern для зовнішніх систем

❌ At most once для важливих даних
❌ Без обробки дублікатів
❌ Exactly once без розуміння обмежень
❌ Комміт до обробки

Архітектурні рішення

  1. At least once + idempotent consumer — оптимальний баланс
  2. Exactly only for Kafka-to-Kafka — не працює із зовнішніми системами
  3. Outbox pattern — для end-to-end guarantees
  4. Idempotent writes — універсальне рішення для зовнішніх систем

Резюме для Senior

  • Exactly Once працює тільки в сценарії Kafka-to-Kafka
  • Для зовнішніх систем використовуйте Outbox pattern або idempotent writes
  • At least once + idempotent consumer — оптимальний підхід
  • Transaction API забезпечує atomic read-process-write
  • Розуміння обмежень критично для правильної архітектури

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

Обов’язково знати:

  • Три гарантії: at-most-once (0-1 раз), at-least-once (1+ раз), exactly-once (рівно 1)
  • At-most-once: acks=0, швидко але можлива втрата даних
  • At-least-once: acks=all + enable.idempotence=true, надійно але можливі дублікати
  • Exactly-once: Transaction API, працює ТІЛЬКИ для сценаріїв Kafka-to-Kafka
  • Для зовнішніх систем (БД, HTTP) — Outbox pattern або idempotent writes
  • Idempotent producer: PID + Sequence Number, брокер відхиляє дублікати при retry
  • Комміт ДО обробки = втрата даних; комміт ПІСЛЯ = at-least-once

Часті уточнюючі запитання:

  • Чому exactly-once не працює для Kafka → БД? — Kafka не управляє транзакцією БД.
  • Як забезпечити end-to-end гарантію? — Outbox pattern: запис в outbox_table + CDC (Debezium).
  • Що робить idempotent producer? — Унікальний PID + sequence number, брокер відхиляє дублікати при retry.
  • Яку гарантію обрати за замовчуванням? — At-least-once + ідемпотентна обробка консьюмера.

Червоні прапорці (НЕ говорити):

  • «Exactly-once працює для будь-яких систем» — тільки Kafka-to-Kafka
  • «At-most-once підходить для платежів» — втрата даних неприпустима
  • «Комміт перед обробкою — нормально» — при crash дані втрачені
  • «Idempotent producer не потрібен» — без нього retry = дублікати

Пов’язані теми:

  • [[10. У чому різниця між at-most-once, at-least-once і exactly-once]]
  • [[11. Як налаштувати exactly-once семантику]]
  • [[23. Що таке idempotent producer]]
  • [[20. Що таке producer acknowledgment і які режими існують (acks=0,1,all)]]