Card 01/ 06
Why Clients Send the Same Write Twice
A timeout does not say whether the server acted, so a careful client retries and may pay twice.
A client sends the same write twice whenever it cannot tell whether the first one worked, and the commonest reason is a timeout. The request may have been lost on the way, in which case nothing happened, or the response may have been lost on the way back, in which case the server already acted. From the client's side the two look identical, so a client that retries may repeat a write that succeeded.
Picture someone on a train pressing Pay in a mobile app. The request reaches the server and the card is charged, then the train enters a tunnel and the response never arrives. The app shows a spinner, gives up after 30 seconds, and offers "Try again".
Who retries. The user pressing the button again is only one source. HTTP client libraries retry on a dropped connection, mobile operating systems resend when a network returns, load balancers and proxies may resend a request to a second server, and job queues redeliver a message whose worker crashed. Many of these retries happen without the application code knowing.
Which requests are at risk. A safe or idempotent method can be repeated with no extra effect: a second GET reads again, a second PUT sets the same state, a second DELETE finds nothing left to delete. POST and PATCH carry no such promise, so a repeated POST /payments makes a second payment and a repeated PATCH that appends a line adds a second line.
Why "do not retry" is not an answer. A client that never retries leaves a user who paid wondering whether they did, and an order that failed for a passing reason stays failed. Retrying is right; the request has to be made safe to retry.
So any POST or PATCH whose repetition would cost something, a payment, an order, a message sent to a customer, needs a way for the server to recognise a second copy.
← → move through sections and cards