Які гарантії доставки повідомлень надає Kafka
Kafka надає три рівні гарантій доставки:
Рівень 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();
}
Типові помилки
- At least once без ідемпотентності:
Retry → дублікати в топику → обробка двічі - Комміт до обробки:
consumer.commitSync(); // спочатку комміт process(records); // потім обробка // Якщо впав → дані втрачені - 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 без розуміння обмежень
❌ Комміт до обробки
Архітектурні рішення
- At least once + idempotent consumer — оптимальний баланс
- Exactly only for Kafka-to-Kafka — не працює із зовнішніми системами
- Outbox pattern — для end-to-end guarantees
- 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)]]