Lesson 5 · Reliability core
Delivery semantics
At-most, at-least, exactly-once — and which one your consumers actually get.
Your win: explain the three delivery semantics, show that the only thing that decides between at-most and at-least-once is when you commit the offset, and identify which one each of your consumers gets.
It all comes down to commit timing
A consumer processes an event and, at some point, commits its offset (Lesson 2–3). Whether you commit before or after processing is the whole story of delivery semantics.1
At-most-once
Commit first. A crash mid-processing loses that event. Never duplicated.
At-least-once
Commit last. A crash re-delivers the event on restart. Never lost, may duplicate. Kafka's default.
Exactly-once
Effect happens once, via idempotent producer + transactions. Powerful, heavier.
Both patterns live in your wrapper
Our internal/golibs/kafka ships two consumer implementations,
and a service picks one with the AlwaysCommit() option:
// STRICT (default) — commit AFTER the handler succeeds → at-least-once
strictConsumer: FetchMessage → run handler → CommitMessages() // consumer.go:203
// LOOSE — ReadMessage auto-advances the offset regardless → at-most-once for a fail
looseConsumer: ReadMessage (auto-commit) → run handler // consumer.go:165
spike email & conversationmgmt =
AlwaysCommit(). They
accept "the offset moves regardless" to guarantee the partition never stalls —
trading a rare lost message for liveness.
The dark side of at-least-once: the poison message
Strict/at-least-once has a sharp edge. If one event always fails the handler, the offset never advances — so that partition is stuck, re-delivering the same bad event forever and blocking everything behind it. This is the classic "poison message" problem, and our repo has no dead-letter topic to catch it (Lesson 6).2
That's exactly why the loose consumers exist: AlwaysCommit() can't get
stuck, because the offset moves whether or not the handler succeeded. Two honest
answers to the same tension — pick your poison, literally.
Where's exactly-once? We don't use it.
Kafka can do exactly-once (EOS) via an idempotent producer plus transactions.1 Our services deliberately don't. Instead they take at-least-once and make the handler idempotent, so a duplicate has no extra effect — the same guarantee, far less machinery. That's the entire subject of Lesson 6.
Confluent — Message Delivery Guarantees
The authoritative treatment of the three semantics and how commit timing produces each. Short and precise.
Check yourself (from memory)
Q1. Committing the offset after processing gives you…
Q2. Why does spike use AlwaysCommit() instead of the strict mode?
Q3. Our services get exactly-once effect by…
AlwaysCommit() mode that advances regardless.FetchMessage+CommitMessages
vs ReadMessage) that make each mode behave this way? Ask me.
1. Confluent — Message Delivery Guarantees.
2. Repo: repo-kafka-map.md — commit modes & the missing DLQ (consumer.go).