Exactly-once delivery does not exist
While this is obvious, for some strange reason it has to be explained.
When message delivery trade-offs are explained, three models are mentioned: at-least-once, at-most-once and exactly-once. The third one is hypothetical and doesn’t exist in real world, but for some reason is perceived as achievable by many people, which leads to constant confusion.
The whole problem in message delivery lies in uncertainty whether receiver did receive and process message or not — once an origin sends a message to receiver it enters a window of doubt until receiver confirms that it has processed the message. Before the confirmation origin doesn’t know what is happening or has happened: message could have never been delivered to the receiver, could have been delayed, receiver could have crashed, receiver could have successfully processed the message but crashed before sending out a confirmation, network disruption could have occurred and healthy receiver couldn’t confirm successful processing and so on; before getting the response origin has to admit that both “message has been / will be successfully processed” and “message will never be successfully processed” are possible (and never getting any response at all is an option too). Because of that origin has two options: forget about message right after send and fingers-crossedly hope that receiver will eventually read and process it (at-most-once) or make that message eligible for another consumption after configured timeout, sending it again in absence of confirmation (at-least-once). There is no any option in between: to implement the hypothetical exactly-once delivery model one would have to get rid of the ambiguity window itself, and to do so one would have to make the whole publish-and-confirm flow a single atomic operation. This is simply not possible, because:
- Speaking about network services, one has to first send and then read data from the network, and such combination is non-atomic by default, since network transmission is not immediate and takes time.
- The only type of processing in which receive and confirmation can be an atomic operation is a no-op.
Again, exactly-once delivery requires whole delivery and processing to be a single atomic operation. This is not possible at all when speaking about network services, and only example of viable system that allows that is a simple atomic operation within same process with extra sophisticated properties, e.g. processing result and confirmation have to be published via atomic operation which can’t fail (i.e. things like CAS are forbidden by design, since facing an unexpected value in register will result in a no-op).
As a last note, as long as your operations are idempotent and ordered, at-least-once will produce the same result as hypothetical exactly-once, so don’t even bother about this non-existing model.