Part IV — Reliability
Delivery guarantees
At Most Once, At Least Once, and Exactly Once — what each guarantee actually promises, and where exactly-once stops applying.
On this page
Chapters 7 and 9 already touched on message loss and duplication, each from its own angle (commit, retry). This chapter formally names the three contracts that summarize that behavior — and clears up a common misunderstanding about what "exactly-once" actually covers.
The three guarantees
At Most Once
The message is delivered at most once — never duplicated, but it can be lost. This happens when the commit (or the producer's ack) occurs before any confirmation that the next step succeeded.
At Least Once
The message is delivered at least once — never lost, but it can be duplicated. This happens when the commit (or the producer's retry) only occurs after the previous step has been confirmed, accepting reprocessing in case of a failure between the confirmation and recording it.
Exactly Once
The message is processed exactly once from the point of view of the observable effect — neither lost nor duplicated — through a combination of an idempotent producer, transactions, and an idempotent consumer, not some magic single-delivery guarantee.
Where the guarantee is decided
On the producer side, the acks setting decides the trade-off between availability and durability (Chapter
5): acks=0 favors speed and accepts loss; acks=all favors durability. On the consumer side, the order
between processing and committing (Chapter 7) decides between loss (commit before processing) and
duplication (commit after processing, but with a risk of failure in between).
Typical configuration per guarantee
| Guarantee | Producer | Consumer |
|---|---|---|
| At Most Once | acks=0 or acks=1 with no retry | Auto commit, or commit before processing |
| At Least Once | acks=all with producer retry | Manual commit, after processing finishes |
| Exactly Once | Idempotent producer + transactions | Idempotent consumer, or a transaction spanning read+write |
Why At Least Once is the practical default
At Most Once is rarely acceptable in financial systems
Silently losing a message — a payment event that's never processed — is generally worse than processing it twice, because duplication is at least detectable and fixable with idempotency, while loss doesn't even leave a trace. That's why the most common configuration in financial systems is At Least Once, combined with idempotent processing in the consumer.
That's why idempotency (Chapter 11) isn't a "quality extra" — it's the necessary complement to At Least Once, which is the guarantee most used in practice.
Exactly Once: what it covers and what it doesn't
"Exactly Once means the message is never processed twice, full stop"
Kafka's exactly-once semantics (EOS) guarantees single processing within the Kafka ecosystem — between an input topic, processing, and writing to another Kafka topic, using transactions. The moment the effect leaves Kafka — an HTTP call, a write to an external database, an email sent — that guarantee automatically stops applying, because those external systems don't participate in Kafka's transaction.
Exactly-once doesn't eliminate the need for idempotency in the real world
If the consumer writes to a database or calls an external API as part of processing, that write falls outside the scope of Kafka's transaction. A failure right after processing but before Kafka confirms the transaction commit can still lead to a repeat of that external effect. That's why systems integrating Kafka with databases and external APIs still need idempotency in those integrations, even when using exactly-once semantics internally.
How this shows up in interviews
"What is exactly-once, and does it solve duplication for good?" is this section's most common trap. The weak answer says "yes, it eliminates duplication." The strong answer explains the scope: EOS covers the Kafka-to-Kafka flow via transactions, but any side effect outside Kafka (database, API, email) still requires its own idempotency.
Dica de entrevista
When asked about exactly-once, always scope it: "within Kafka, between reading from one topic and writing to another, via transactions." That signals you understand the guarantee's boundary, not just its name.
Relation to Java and Spring Boot
In Spring Kafka, acks=all and enable.idempotence=true on the producer (the default since recent client
versions) avoid message duplication caused by the producer's own automatic retries. For full exactly-once
semantics across topics, KafkaTransactionManager is used, wrapping reads and writes in the same Kafka
transaction. But as soon as the listener calls an external HTTP service or writes to a relational database
outside that transaction, the responsibility for avoiding duplication falls back on the developer —
typically via a uniqueness constraint and an idempotency check (Chapter 11).
PIX received notification
The notificacao-service runs with At Least Once: if it crashes after sending the push notification but
before committing the offset, the event will be reprocessed and a second notification may be sent. That's
acceptable for a notification (the worst case is the customer getting two alerts), but it would be
unacceptable for the saldo-service, which needs explicit protection against a duplicate credit — hence the
difference in idempotency rigor between the two consumers of the same event.
Resumo
At Most Once never duplicates, but it can lose. At Least Once never loses, but it can duplicate — and it's the practical default in financial systems, combined with idempotency. Exactly Once, via Kafka transactions, guarantees single processing only within the Kafka ecosystem; any external side effect (database, API, email) still requires its own idempotency, regardless of the guarantee configured internally.
Pode vir a seguir
Likely follow-ups: "how would you avoid duplicate processing?" (Chapter 11) and "what acks configuration
would you use for a financial transactions topic, and why?".