Don't fix other people problems. If idempotent key was seen then send back response. Clients intention is outside the scope. If contract says "idempotency on key" the idempotent response on key. If contract says "idempotent on body hash" then response on body hash (which might or might not include extra data). APIs are contracts. Not the pinky promise of "I'll do my best guess"
The best pattern I've seen is hash the request and validate that it is the same one. If not, error out as it is an invalid argument. "Best guess" can be bad if it is not well-defined, but you can still make error detection obvious rather than hidden.
Idempotency is easy until the second request is different
121–130 of 244 posts
Re: Idempotency is easy until the second request is different
#122Don't fix other people problems. If idempotent key was seen then send back response. Clients intention is outside the scope. If contract says "idempotency on key" the idempotent response on key. If contract says "idempotent on body hash" then response on body hash (which might or might not include extra data). APIs are contracts. Not the pinky promise of "I'll do my best guess"
IMHO it's more: fix problems, or at least mitigate them, regardless whose problem it is. I've been in this situation, a clientside bug meant that different requests arrived with the same idempotency key. In my case, updating the client would have taken weeks, in the best case scenario. Updating the backend to check for a matching request body would have taken minutes, maybe hours. It took me a surprising amount of ar…
Then at least admit you’re just hacking quickly fixes, creating technical debt, and not fixing the actual problem.
I agree with your point that business interest is most important, I disagree that it’s the technically most appropriate solution.
The whole article is proclaiming that this is a technical problem about idempotency being hard, while it’s not. The whole premise of client side bugs must be resolved backend side as the correct solution is incorrect.
Re: Idempotency is easy until the second request is different
#123Earlier quoted context omitted.
> That's usually solved with traditional database transactions. That doesn't answer my question. What response do you return to the client in the case I described?
This is just normal concurrent programming? If two requests come in for the same idempotency key/customer reference id, only one will succeed. Use standard database transaction isolation. So one will complete with 200, one will complete with 409. It doesn't matter which. That said, there's something odd about the way you phrased this question. If the original request hasn't gotten a response yet, why is it sending a…
Um, because connections over the Internet aren't 100% always on? Because packets can get lost? Because computers sometimes have to reboot?
You're assuming that the client will always receive whatever response your server finally sends, and that the client will wait indefinitely to receive a response. Neither of those things are true. So the client can be in a state where it sends a retry because it got no response and doesn't know why. And that means a retry request could come in while the first one is still being resolved--because the client had a timeout or it rebooted or something else happened that made it lose the connection state it previously had. That's the case I'm asking about.
Re: Idempotency is easy until the second request is different
#124A couple of years ago, we experienced a silent data corruption incident in our checkout process due to this specific edge case. A user would generate the idempotency key by loading the front-end application, adding item(s) to their cart, submitting their order but timing out. The user would then navigate back to the front-end application and add another item and submit the order again. Since the user is submitting an…
Save only if the operation succeeds. It's meaningless to cache a failure, subsequent retries will result in failure from the cache.
Frankly you guys are overengineering the whole thing. We use the concept only for network outages i.e. it is only on timeout that we want to guard against fultilling duplicate request for the same operation.
Re: Idempotency is easy until the second request is different
#125Earlier quoted context omitted.
> That's usually solved with traditional database transactions. That doesn't answer my question. What response do you return to the client in the case I described?
The lock would normally make the second request wait, aka not return a response, until the first one is done. Then it sees it's a duplicate and returns that. Or it times out and returns an error. Then the client hopefully have some exponential back off strategy, so the third attempt doesn't suffer the same fate.
Re: Idempotency is easy until the second request is different
#126This is all way too much. If you see a duplicate idempotency key, skip the replay and always return 409. This becomes a client problem. Clients already need to help enforce idempotent contracts; "check for conflict response" is not an onerous imposition. I've built multiple ecommerce APIs with this approach and they work great. No heroic measures required. You can often satisfy this contract with a unique constraint;…
Re: Idempotency is easy until the second request is different
#127Earlier quoted context omitted.
Retries will only receive 409 if the original request was successful. If the original request failed, the server performs the operation as normal on the second request. It doesn't replay failures. The whole point of the idempotence mechanism is so you can make a reliable distributed system. If the first try fails, the client doesn't know if it succeeded or not, so the client should try again later ("at-least-once").…
You are improvising, and in the process, changing the semantics of a well established design pattern. Do not recommend.
The pattern I describe was the dominant design pattern for financial transaction processing systems before Stripe. Stripe's API makes life for the clients slightly easier at the expense of making life for servers more complicated, but the two approaches are equivalent in function.
Re: Idempotency is easy until the second request is different
#128No -- Idempotent means _no_ side-effects.
Re: Idempotency is easy until the second request is different
#129Earlier quoted context omitted.
> That's usually solved with traditional database transactions. That doesn't answer my question. What response do you return to the client in the case I described?
This is just normal concurrent programming? If two requests come in for the same idempotency key/customer reference id, only one will succeed. Use standard database transaction isolation. So one will complete with 200, one will complete with 409. It doesn't matter which. That said, there's something odd about the way you phrased this question. If the original request hasn't gotten a response yet, why is it sending a…
> why is it sending a retry?
may be two clients tries to do it? Or there's a bug with the client in how they do it?
Isn't the point of idempotency meant to enable clients to retry again, without fear that a 2nd request somehow breaking things?
Re: Idempotency is easy until the second request is different
#130Earlier quoted context omitted.
In the case of a PUT or DELETE, the key is in the URI. /custs/12345/orders/20260510T153023.239 for example.
Yes, I understand that, but I'm not sure how that changes anything? I mean: you still have the problem regardless of following HTTP verb semantics or not.