Live data from Hacker News

Idempotency is easy until the second request is different

blog.dochia.dev

231–240 of 244 posts

Re: Idempotency is easy until the second request is different

#231
post #225

Earlier quoted context omitted.

> There is no value in returning a "hey this is in progress" error code to a client when transactions are short Fair enough. So basically your approach is to wait until the first request completes to decide how to respond to the second request that came in with the same idempotency key. However, that would seem to me to imply that when the second request comes in, you check its idempotency key, realize you've already…

While I'd love to take credit for it, this isn't literally "my" approach; this is just the standard way that transaction processing systems on the web work. And it's so standard that you don't even have to write code for it. Databases handle all the isolation and concurrency issues for you. Part of the problem here is that we're confusing how do you structure the API (replay? 409? something else?) with how we impleme…

> Part of the problem here is that we're confusing how do you structure the API (replay? 409? something else?) with how we implement the API.

It would seem to me that you would want "what happens if a second request comes in with the same idempotency key while the first is still in progress" to be part of the API, so clients would know what your server is going to do in that scenario.

Re: Idempotency is easy until the second request is different

#232
post #198

This 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;…

> If you see a duplicate idempotency key I'm no expert but an "idempotency key" already has some major smell to it.

Then learn from the experts, maybe? https://docs.stripe.com/api/idempotent_requests

Re: Idempotency is easy until the second request is different

#233
post #217

Earlier quoted context omitted.

There are a lot of ways to implement this, so I posted an example with one of the most common ways - a database which uses optimistic concurrency in serializable isolation level. Postgres is often configured this way, though it's not the only way it can be configured. With optimistic concurrency models, collisions are only detected at commit time. Two transactions can simultaneously update the same data; each update…

> There are a lot of ways to implement this Sure, I get that. What I don't get is why you would be using idempotency keys as part of the implementation if you're going to go ahead and start a second transaction when you get a duplicate request, and not even check the idempotency key, and let your database tell you you've got a duplicate when you try to commit the second transaction. This subthread is specifically abo…

Update: stickfigure basically answered this in another subthread: as I understand the answer, it is that you do check the idempotency key, but inside the DB transaction, so you have to start a new transaction on every request. If, inside the transaction, your idempotency key check shows that another request with that key was already received, you don't do anything to change the DB inside the transaction and just commit it as a no-op.

Re: Idempotency is easy until the second request is different

#234
post #145

Earlier quoted context omitted.

I appreciate when people tell me something is AI written, so I can avoid reading it.

But people claim that text is AI-written on the most ridiculous grounds, like using proper typography. You might be missing out on interesting stuff!

I'm always missing out on interesting stuff. There's more great stuff being produced every day than can ever be read. It's just one more heuristic to apply.

Re: Idempotency is easy until the second request is different

#235

Earlier quoted context omitted.

> You still return 409 No no no no no. You have multiple clients submitting the same business operation simultaneously. One must succeed, the others must fail. If you're using the 409 approach ("notify client that request is redundant") you must not send a 409 code until the work is complete. The client must interpret 200 and 409 as success cases. 200 means "it was done" and 409 means "it was already done". Clients l…

What's the best practice here? It's trying to represent as binary a multi-state operation, but the redundant clients should check the response's body to know why it 409'd. If the process is slow, it can't return a 200 immediately, and yet it should return 409 to all other attempts, even if the initial attempt ends up unsuccessful .

> and yet it should return 409 to all other attempts, even if the initial attempt ends up unsuccessful.

No, it shouldn't. The comment you're responding to is taking 200 to mean "success" and 409 to mean "it was done" so if it was not in fact done then you _must not_ return that.

That said, I thought one of the benefits of idempotency was nonblocking APIs so I'm not sure I like that scheme. It seems like 200 should mean "submitted, accepted, incomplete" and 409 should mean "previously completed". The client never knows which request succeeded but they're idempotent so that doesn't matter. You just poll until the 200 becomes a 409.

Of course that would provide zero diagnostics in the case of failure so I think it's not sufficient as described.

Re: Idempotency is easy until the second request is different

#236

Earlier quoted context omitted.

I pointed to Braintree in another comment where this is very much not fine. Also these providers operate at a lower level of the stack than we do, so they have finer control over the process than you or I. Even then, it’s not fine because those requests might time out, or your request times out waiting for theirs. Just because your provider abstracts behind one API doesn’t mean you necessarily can!

This problem is the entire point of the idempotency key system. You (the client) must retry the payment until success (where APIs that provide "this idempotency key already seen" is considered success). The idempotency key prevents you from creating duplicate payments. 500 errors, network timeouts, etc all happen. We can't run 2PC transactions with Stripe, so you need durable retries. People run billions of dollars t…

I’m not disagreeing with that point: I’m just pointing out that the idempotency key must be persisted in your own system before the payment is submitted. Some gateways (Braintree) will let you submit no key and just give you one on request completion.

My point is that you can’t rely on this specific mechanism because request failure does not mean the payment is not going through!

I’m not sure where we disagree so I must ask: do you disagree with what I’ve written and, if so, what and why?

Re: Idempotency is easy until the second request is different

#237
post #231

Earlier quoted context omitted.

While I'd love to take credit for it, this isn't literally "my" approach; this is just the standard way that transaction processing systems on the web work. And it's so standard that you don't even have to write code for it. Databases handle all the isolation and concurrency issues for you. Part of the problem here is that we're confusing how do you structure the API (replay? 409? something else?) with how we impleme…

> Part of the problem here is that we're confusing how do you structure the API (replay? 409? something else?) with how we implement the API. It would seem to me that you would want "what happens if a second request comes in with the same idempotency key while the first is still in progress" to be part of the API, so clients would know what your server is going to do in that scenario.

Nobody cares. These are short lived transactions (generally milliseconds); collisions are a rare edge case; it's fine to block. One request succeeds, the other gets a dup error (or a replay).

You could invent your own more sophisticated idempotency API but good luck finding someone that wants to implement it or use it. What real-world problem are you trying to solve?

Re: Idempotency is easy until the second request is different

#238

Earlier quoted context omitted.

> special cases where the original request is still in process This isn't a special case, and it's the same problem if you want to replay the original response on conflict. If the original request isn't complete, what are you going to replay ?

That is my point. When you are doing "normal" idempotency where you do the appropriate locking and keep around a table with ongoing request status and the result that you can return on a subsequent duplicate request, you handle all these cases. But in your "409" version of it, you haven't really saved much complexity on the server because you still need to keep around all that info if you're not just returning a 409…

I don't understand what you're saying. I can take any cheap rdbms, put a unique constraint on a column, and make my API return 409 for conflict vs 200 on success. There's so little code involved that it's embarrassing to charge money for it.

Re: Idempotency is easy until the second request is different

#239

Earlier quoted context omitted.

This problem is the entire point of the idempotency key system. You (the client) must retry the payment until success (where APIs that provide "this idempotency key already seen" is considered success). The idempotency key prevents you from creating duplicate payments. 500 errors, network timeouts, etc all happen. We can't run 2PC transactions with Stripe, so you need durable retries. People run billions of dollars t…

I’m not disagreeing with that point: I’m just pointing out that the idempotency key must be persisted in your own system before the payment is submitted. Some gateways (Braintree) will let you submit no key and just give you one on request completion. My point is that you can’t rely on this specific mechanism because request failure does not mean the payment is not going through! I’m not sure where we disagree so I m…

If the idempotency key must be persisted, that is the bookkeeping that has to occur to consider the request a success.

Re: Idempotency is easy until the second request is different

#240

Earlier quoted context omitted.

I’m not disagreeing with that point: I’m just pointing out that the idempotency key must be persisted in your own system before the payment is submitted. Some gateways (Braintree) will let you submit no key and just give you one on request completion. My point is that you can’t rely on this specific mechanism because request failure does not mean the payment is not going through! I’m not sure where we disagree so I m…

If the idempotency key must be persisted, that is the bookkeeping that has to occur to consider the request a success.

Ok, so I’m stating that the persisting of that key must happen before the call to the payment gateway happens and regardless of whether that call succeeds or even completes. Is this something we agree on?
Post reply on HN