At-Most-Once vs At-Least-Once: The Failure Points That Cause Loss or Duplication
Delivery semantics describe what happens to a message when something fails. At-most-once means a message is delivered zero or one time, never more. At-least-once means a message is delivered one or more times, never zero. The difference is entirely about the order of operations between processing and committing the offset. In Kafka, the consumer commits its position in the partition log to the __consumer_offsets topic. If you commit the offset before processing the message, and processing fails, the message is lost because the consumer will resume after it on restart. That is at-most-once. If you process the message first and then commit, and the process crashes after processing but before committing, the message will be re-delivered on restart. That is at-least-once.
The mechanism matters because it determines where duplication or loss can occur. With auto-commit enabled (enable.auto.commit=true), the consumer commits offsets periodically in the background, independent of whether your processing succeeded. This is effectively at-most-once if your processing is asynchronous or slow, because the commit can happen before processing finishes. To get at-least-once, you disable auto-commit (enable.auto.commit=false) and commit manually after processing. But at-least-once has a subtle failure point: if the consumer processes a batch, commits, then crashes before the next poll, no duplication occurs. If it processes, crashes before committing, the whole batch is re-delivered, including messages that were already processed. This is why at-least-once requires idempotent processing on the consumer side; otherwise you get duplicate side effects.
A common mistake is to think that at-least-once is always the safe choice. It is safer against loss, but it pushes the duplication problem onto the consumer. Another mistake is to assume that committing more frequently reduces duplication. It does not eliminate it; it only reduces the window. The trade-off is between commit frequency and throughput: committing every message is expensive and kills throughput, so most consumers commit per batch or per poll. On the producer side, at-most-once is achieved by setting acks=0 or acks=1 with no retries; if the broker does not receive the message, the producer does not know and does not retry. At-least-once on the producer side is achieved with acks=all and retries > 0; if the ack is lost, the producer retries, which can cause duplicates. Exactly-once producer semantics require idempotence and transactions, which is a separate topic.
At-most-once: commit offset before processing; failure causes loss, never duplication.
At-least-once: process then commit; failure causes duplication, never loss.
Auto-commit is effectively at-most-once if processing is asynchronous or slow.
Manual commit after processing gives at-least-once, but duplicates occur for the whole batch on crash.
Producer at-most-once uses acks=0 and no retries; producer at-least-once uses acks=all with retries.
At-least-once requires idempotent consumer processing to avoid duplicate side effects.
Exactly-once requires idempotent producer plus transactions; it is not the default.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience