Live data from Hacker News

Idempotency is easy until the second request is different

blog.dochia.dev

111–120 of 244 posts

Re: Idempotency is easy until the second request is different

#111
post #96

Earlier quoted context omitted.

GET is not supposed to make changes on the server. The usual idempotent verbs for making changes are PUT and DELETE. One thing that's confusing, here, is that idempotency only applies for the same request, but the article implies that idempotency is about whether the request contains a specific "idempotency key". Don't do that, and this problem evaporates.

> Don't do that, and this problem evaporates. Don't do that, and you solved nothing. Either I'm missing what you mean, or half the comments here are missing the point of idempotency. Let's say your server received this request twice within one minute: { items: [ { id: 123, amount: 1 } ], creditCardInfo: { ... } } How can you tell from the server if that's a retry (think e.g. some reverse proxy crashed and the first r…

In the case of a PUT or DELETE, the key is in the URI. /custs/12345/orders/20260510T153023.239 for example.

Re: Idempotency is easy until the second request is different

#112
post #109

Earlier 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").…

What if the original request is still being processed when the retry comes in? That doesn't fall into either of your categories: the request isn't successful, but it hasn't failed either.

That's usually solved with traditional database transactions.

Even if you have a complex long-running multistep orchestration problem, you can break it down into simpler transactions. Eg you could start with a "lock the resources" txn.

But 99% of these conversations around idempotence are simple POST operations like "create order" that regular old database concurrency management handles just fine.

Re: Idempotency is easy until the second request is different

#113
post #109

Earlier quoted context omitted.

What if the original request is still being processed when the retry comes in? That doesn't fall into either of your categories: the request isn't successful, but it hasn't failed either.

That's usually solved with traditional database transactions. Even if you have a complex long-running multistep orchestration problem, you can break it down into simpler transactions. Eg you could start with a "lock the resources" txn. But 99% of these conversations around idempotence are simple POST operations like "create order" that regular old database concurrency management handles just fine.

> 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?

Re: Idempotency is easy until the second request is different

#114
post #113

Earlier quoted context omitted.

That's usually solved with traditional database transactions. Even if you have a complex long-running multistep orchestration problem, you can break it down into simpler transactions. Eg you could start with a "lock the resources" txn. But 99% of these conversations around idempotence are simple POST operations like "create order" that regular old database concurrency management handles just fine.

> 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

#115
post #113

Earlier quoted context omitted.

That's usually solved with traditional database transactions. Even if you have a complex long-running multistep orchestration problem, you can break it down into simpler transactions. Eg you could start with a "lock the resources" txn. But 99% of these conversations around idempotence are simple POST operations like "create order" that regular old database concurrency management handles just fine.

> 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 retry? What you're asking is more general: What happens when two conflicting requests come in? This is something we've been solving with RDBMSes since the 1970s.

Re: Idempotency is easy until the second request is different

#116
post #96

Earlier quoted context omitted.

> Don't do that, and this problem evaporates. Don't do that, and you solved nothing. Either I'm missing what you mean, or half the comments here are missing the point of idempotency. Let's say your server received this request twice within one minute: { items: [ { id: 123, amount: 1 } ], creditCardInfo: { ... } } How can you tell from the server if that's a retry (think e.g. some reverse proxy crashed and the first r…

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.

Re: Idempotency is easy until the second request is different

#117

Earlier quoted context omitted.

But that's not idempotent? If I'm a client and I don't know if the original request went through, getting a 409 on any subsequent requests tells me nothing about whether the original request was successful or not.

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.

Re: Idempotency is easy until the second request is different

#119

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

But that's not idempotent? If I'm a client and I don't know if the original request went through, getting a 409 on any subsequent requests tells me nothing about whether the original request was successful or not.

If you're a client using the same idempotency key for a materially different request you have a bug.

Re: Idempotency is easy until the second request is different

#120

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

I really like this approach, and am going to keep it in my back pocket.

However, it's good for e-commerce where there are a subset of "important" operations, but I'd argue the idempotency key is better for a financial API like Stripe where the majority of your operations need these semantics.

You also run into a problem if PUT/PATCH needs to be exactly-once instead of at least once. Not as common, but again, might be something you run into with a financial API.

Post reply on HN