У чому різниця між at-most-once, at-least-once і exactly-once
Outbox pattern: (1) INSERT into outbox_table + business_data в одній транзакції БД. (2) Окремий процес читає outbox_table і відправляє в Kafka. (3) Після успішної відправки — DE...
Рівень Junior
Визначення
At-most-once (не більше одного разу):
- Повідомлення доставиться 0 або 1 раз
- Може загубитися при помилках
- ~1x baseline latency
At-least-once (як мінімум один раз):
- Повідомлення доставиться 1 або більше разів
- Може продублюватися при retry
- Дані не загубляться
Exactly-once (рівно один раз):
- Повідомлення доставиться рівно 1 раз
- Немає втрат, немає дублікатів
- 2-3x latency
Приклад з життя
At-most-once:
Відправив SMS → не перевірив чи дійшло
→ Може не дійти
At-least-once:
Відправив SMS → не отримав підтвердження → відправив знову
→ Може дійти двічі
Exactly-once:
Відправив SMS → отримав унікальний ID → перевірив доставку
→ Дійшло рівно один раз
Коли що використовувати
| Семантика | Коли використовувати |
|---|---|
| At-most-once | Метрики, логи, моніторинг |
| At-least-once | Більшість бізнес-систем |
| Exactly-once | Фінанси, біллінг, банківські операції |
Рівень Middle
Як це працює в Kafka
At-most-once:
// Producer — не чекає підтвердження
props.put("acks", "0");
// Consumer — коммітить одразу після отримання
props.put("enable.auto.commit", "true");
At-least-once:
// Producer — чекає підтвердження від усіх
props.put("acks", "all");
props.put("enable.idempotence", "true");
props.put("retries", Integer.MAX_VALUE);
// Consumer — коммітить після обробки
props.put("enable.auto.commit", "false");
consumer.commitSync(); // після process()
Exactly-once:
// Producer
props.put("enable.idempotence", "true");
props.put("transactional.id", "my-tx-id");
// Consumer
props.put("isolation.level", "read_committed");
Порівняльна таблиця
| Параметр | At-most-once | At-least-once | Exactly-once |
|---|---|---|---|
| Втрата даних | Так | Ні | Ні |
| Дублікати | Ні | Так | Ні |
| Продуктивність | Максимальна | Висока | Середня |
| Складність | Мінімальна | Середня | Висока |
| Use case | Метрики | Бізнес-події | Фінанси |
Idempotency — ключ до at-least-once
// Ідемпотентна обробка
public void process(Message msg) {
if (!alreadyProcessed(msg.getId())) {
doProcess(msg);
markAsProcessed(msg.getId());
}
}
Типові помилки
- At-least-once без обробки дублікатів:
Дублікати → подвійні списання → баги в бізнес-логіці - Exactly-once без розуміння обмежень:
Kafka → PostgreSQL → exactly-once не працює Kafka не управляє транзакцією PostgreSQL - Комміт до обробки:
consumer.commitSync(); // ❌ process(records); // Якщо впав — дані втрачені
Рівень Senior
“EndOfWorld” проблема
Exactly-once в Kafka працює “з коробки” тільки для Kafka-to-Kafka:
Kafka → Processing → Kafka ✅ Works
Kafka → Processing → PostgreSQL ❌ Does not work
Чому?
Kafka може гарантувати atomic commit тільки для:
- Запису в Kafka topic
- Комміту offsets в __consumer_offsets
Kafka не може управляти транзакцією в PostgreSQL!
Outbox pattern: (1) INSERT into outbox_table + business_data в одній транзакції БД. (2) Окремий процес читає outbox_table і відправляє в Kafka. (3) Після успішної відправки — DELETE з outbox_table.
Рішення для зовнішніх систем
1. Idempotent writes:
// PostgreSQL — UPSERT по унікальному ключу
INSERT INTO orders (id, data)
VALUES (?, ?)
ON CONFLICT (id) DO NOTHING;
2. Outbox Pattern:
1. Бізнес-операція → запис в outbox (в тій самій транзакції)
2. CDC (Debezium) → читає outbox
3. Відправляє в Kafka
4. Consumer → обробляє → пише в target
3. Two-Phase Commit (XA — не рекомендується):
Prepare phase → усі учасники готові
Commit phase → усі коммітять
Проблеми: блокування, складність, продуктивність
Internal Implementation Details
Idempotent Producer:
PID (Producer ID) + Sequence Number
Брокер відхиляє дублікати по послідовності
Працює на рівні партиції
Transactions:
Transaction Coordinator → управляє state
__transaction_state topic → зберігає state
Commit/Abort → атомарні операції
Performance Analysis
Latency comparison (відносна):
At-most-once: 1x
At-least-once: 1.2x
Exactly-once: 2-3x
Throughput comparison (відносна):
At-most-once: 100%
At-least-once: 85-95%
Exactly-once: 50-70%
When to Use What
At-most-once:
- Метрики моніторингу
- Логи додатків
- Real-time аналітика (прийнятна втрата)
At-least-once:
- Більшість бізнес-систем
- Обробка замовлень
- Сповіщення
- Оновлення кэшу
Exactly-once:
- Фінансові транзакції
- Біллінг
- Банківські перекази
- Тільки сценарії Kafka-to-Kafka
Best Practices
✅ Обирайте at-least-once за замовчуванням
✅ Прагніть до ідемпотентних консьюмерів
✅ Exactly-once тільки для Kafka-to-Kafka
✅ Outbox pattern для зовнішніх систем
✅ Idempotent writes в БД
❌ At-most-once для важливих даних
❌ Exactly-once для Kafka → Database
❌ Без обробки дублікатів
❌ Ігнорування performance trade-offs
Архітектурні рішення
- At-least-once + idempotent consumer — оптимальний баланс
- Exactly-once потребує налаштування параметрів транзакцій на брокері (transaction.state.log.replication.factor >= 2). В Kafka 1.0+ transaction support увімкнено за замовчуванням.
- Outbox pattern — універсальне рішення для end-to-end
- Idempotent writes — дешевше і надійніше транзакцій
Резюме для Senior
- Обирайте at-least-once за замовчуванням
- Прагніть зробити консьюмери ідемпотентними
- Exactly-once працює тільки Kafka-to-Kafka
- Для зовнішніх систем використовуйте Outbox або idempotent writes
- Розумійте performance trade-offs кожної семантики
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- At-most-once: 1x latency, можлива втрата, для метрик/логів
- At-least-once: 1.2x latency, дублікати можливі, для бізнес-систем
- Exactly-once: 2-3x latency, 50-70% throughput, тільки для фінансів
- Exactly-once працює з коробки тільки для Kafka-to-Kafka
- Для Kafka → БД: Outbox pattern або idempotent writes (UPSERT)
- At-least-once + idempotent consumer — оптимальний баланс для більшості систем
- Комміт ДО обробки = data loss, комміт ПІСЛЯ = at-least-once
Часті уточнюючі запитання:
- Чому exactly-once не працює із зовнішніми системами? — Kafka не може управляти транзакцією PostgreSQL.
- Що таке Outbox pattern? — INSERT в outbox_table в тій самій транзакції → Debezium CDC → Kafka.
- Яка семантика за замовчуванням? — At-least-once, прагніть до idempotent consumers.
- Який overhead у exactly-once? — 2-3x latency, 50-70% throughput.
Червоні прапорці (НЕ говорити):
- «Exactly-once працює для Kafka → HTTP API» — HTTP не підтримує транзакції Kafka
- «At-most-once для замовлень» — втрата замовлень неприпустима
- «Дублікати не проблема» — подвійні списання = баг в бізнес-логіці
- «Комміт перед обробкою — стандартна практика» — це data loss
Пов’язані теми:
- [[9. Які гарантії доставки повідомлень надає Kafka]]
- [[11. Як налаштувати exactly-once семантику]]
- [[23. Що таке idempotent producer]]
- [[25. Що таке DLQ (Dead Letter Queue)]]