Exactly-Once Delivery Is a Bedtime Story We Tell Product Managers
Every few months someone asks for exactly once delivery like it is a config flag we have been too lazy to enable. So here is my standard whiteboard rant, in blog form.
Imagine I send you a message and require an acknowledgment. You process it, and your ack gets lost in the network. From my side, silence. Did you crash before processing, or after? I cannot know. The information does not exist on my side of the wire. So I choose: send again, risking a duplicate, or don't, risking a loss. At most once or at least once. Those are the choices physics offers. Exactly once delivery, as a transport guarantee between two machines over a lossy network, is not on the menu.
What is on the menu, and what people actually mean, is exactly once processing. Deliver at least once, and make processing duplicates harmless. That property has a name, idempotency, and unlike exactly once delivery it is something you can build.
Sometimes it is nearly free. Setting a row to status=shipped is idempotent by nature, run it twice, nothing changes. Sometimes you earn it with dedupe, stamp each message with a unique ID, record processed IDs, skip repeats. Kafka's "exactly once semantics" is this move done well inside one system's walls, transactional producers plus idempotent writes plus consumer offsets committed atomically with output. Beautiful engineering. It still ends at Kafka's boundary. The moment your consumer calls a payment API or sends an email, you are back in at least once land, holding an idempotency key, like the rest of us.
The bedtime story version is fine for a roadmap slide. Just make sure the people building the thing know which guarantee they are actually shipping, because the gap between the two is where double charges live.