Skip to main content
Book contents

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 OnceProducerKafkaConsumer×message may be lostAt Least OnceProducerKafkaConsumer×message may be duplicatedExactly OnceProducerKafkaConsumer×processed exactly once
At Most Once loses messages on failure before confirmation; At Least Once duplicates messages on failure after processing; Exactly Once, with idempotency and transactions, guarantees single processing even under failure.

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

GuaranteeProducerConsumer
At Most Onceacks=0 or acks=1 with no retryAuto commit, or commit before processing
At Least Onceacks=all with producer retryManual commit, after processing finishes
Exactly OnceIdempotent producer + transactionsIdempotent 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?".