liveBook · Manning

Exactly-once delivery

As explained in Think Distributed Systems

A message-delivery guarantee in which each sent message is delivered exactly one time.

Exactly-once delivery means delivering each sent message one time. Without failures, this is straightforward; with failures, delays, or uncertainty about whether a side effect occurred, the guarantee is impossible. section 4.0 section 4.7

A sender that stops after receiving no response attempts at-most-once delivery: the message may arrive zero or one time. A sender that retries attempts at-least-once delivery, but messages may be duplicated or continuously lost. The sender cannot distinguish an absent response from an unperformed or delayed side effect. section 4.3

Exactly-once processing semantics offer the practical alternative: the final system state behaves as though the message was processed once, even when retries cause multiple processing attempts. This is equivalence, not literal equality; duplicated logs, for example, may be considered inconsequential. The application must define what counts as equivalent. section 4.4

Reliable semantics require an end-to-end design:

  • Keep submitting requests until receiving a response, rather than abandoning a possibly unprocessed message. section 4.4
  • Ensure the receiver does not skip processing, while making repeated processing idempotent or otherwise equivalent. section 4.4 section 4.7
  • Treat crashes, delays, reordering, duplication, and loss as interacting concerns; no universal idempotence solution exists. section 4.7

Charging a credit card

During airline-ticket booking, a missing response may lead an app to retry a card charge. An idempotency key, such as a reservation UUID, can make repeated requests return the same saved result. Without such protection, repeated app crashes and retries may produce several pending charges. section 4.6

requests := { } #1
on message request do #2
  if request in received then #3
    return
  end
  received := received + request  #4
    #5
  process(request) // e.g. transfer balance
  return
end

This receiver-side pattern records a request before processing it, exposing the crash window in which recording succeeds but processing does not. section 4.3

requests := { }
on message request do
  if request in received then
    return response
  end
  process(request) // e.g. transfer balance
    #1
  received = received + request #2
  return response
end

This receiver-side pattern records a request after processing it, exposing the opposite crash window in which processing succeeds but recording does not. section 4.3

How Think Distributed Systems covers it

How deep it goes
Derives impossibility from sender choices and failure uncertainty, then explains equivalence, idempotence, and end-to-end requirements.
Examples
Uses duplicate-suppression algorithms, airline-ticket payment retries, reservation identifiers, and Stripe-style idempotency keys.
What it assumes
Assumes a sender, receiver, requests, responses, and failures while teaching delivery guarantees and equivalence from first principles.
What you can do after
Evaluate delivery claims and design retries around idempotent processing rather than assuming literal exactly-once delivery.
How it connects
Connects at-most-once delivery, at-least-once delivery, exactly-once processing semantics, idempotence, and message processing.

Also explained in

Compare how 6 books explain exactly-once delivery →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Think Distributed Systems. The section links point to the parts of the book it draws on.