Live data from Hacker News

Idempotency is easy until the second request is different

blog.dochia.dev

131–140 of 244 posts

Re: Idempotency is easy until the second request is different

#131
post #32

> If you’re still in school, here’s a fact: you will learn as much or more every year of your professional life than you learned during an entire university degree—assuming you have a real engineering job. This rubs me the wrong way. It's stated as fact without any trace of evidence, it is probably false, and it seems to serve no purpose but to make struggling students feel worse (and make the author feel superior).

I think it's that the things learned in school are academic (red-black trees, dynamic programming, writing toy OS and programming languages, etc.) In the real world you're faced with building five nines active-active systems that interface across various stakeholders, behaviour has to be eventually consistent, you've got a long list of requirements and deadlines, etc. It's practical, hands on, and people are there to…

Sure, it's different than gaining professional experience. It's more theorical, more foundational, broader. But that doesn't mean you're learning less, let alone 4 times less on a yearly basis.

I've been a CS teacher and I found that it's terribly easy to underestimate how much there is to learn and how much effort that learning takes, when you've internalized a skill yourself a long time ago.

Re: Idempotency is easy until the second request is different

#132

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…

There's nothing special about any particular HTTP method; it is the inherent nature of distributed systems that you can not have exactly-once semantics with a single HTTP request. To create exactly-once semantics, you need a client which retries until success and a server which prevents duplicates.

The only difference between the approach I'm describing and Stripe's approach is the detail of how the client knows that it's done. "My" mechanism (notify the client that a request is a duplicate) was dominant in financial transaction processing systems before Stripe; I used probably a half dozen of them back in the day.

Stripe came along and from the beginning decided "we want to compete based on making a friendly API". They succeeded, and if you ever look at Paypal's original API, it's easy to see why. It's true that repeating successful API responses makes life slightly easier for clients; they never need to check for a 409 (or whatever) response. It comes at a cost of making life quite a bit more complex on servers. Personally I don't think the tradeoff is worth it, but YMMV. If your single competitive advantage is "easy API" maybe it makes sense. If you're normal B2B, almost certainly not.

Re: Idempotency is easy until the second request is different

#133

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.

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

Yes, and if you are building a payment API you need to be robust to client bugs.

Re: Idempotency is easy until the second request is different

#134

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

[deleted]

Re: Idempotency is easy until the second request is different

#135
post #116

Earlier quoted context omitted.

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.

In the case in the article, the request is being rebuilt again by the client, and may be slightly different. Typically, the server doesn't have to care about any of that if it's just "did we get something for this ID?" and either it did and errors (could be a 4xx or a 5xx depending on what it now has), or it didn't, and processes the request.

So what you propose is first you create the request payload and POST it, which generates a request-id-bound URL (but it does nothing stateful yet) and then you actually request to perform it? Because otherwise I don't see any difference.

Re: Idempotency is easy until the second request is different

#136
> “Put an Idempotency-Key on the request. Store the response. Replay it on retry.”

Is this the new normal? Assert something, that id clearly broken as the correct, then write a blog fixing their broken logic?

You don’t replay it on retry. You signal it is a success on first try, and subsequent request with the same key return 409.

Anything else and you are doing it wrong.

Re: Idempotency is easy until the second request is different

#137
post #129

Earlier quoted context omitted.

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…

So you have to serialize the requests, and have one of them wait for the other to finish to return the 409? > 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?

By definition we have to serialize something somewhere so we can decide which request is the success and which request is the duplicate. There's nothing special about the case of retries, this is standard concurrent programming 101. Two conflicting requests come in, which one wins?

You absolutely must wait for one request to finish before any other request can return a 409. 409 is a signal to the client that they can stop retrying, the job is done. If some request returns 409 early and the "original" request fails, you will not get further retries and the message will be lost.

Stripe's approach requires serialization as well. Only one request can succeed. If you send multiple conflicting requests in simultaneously, some of those have to block.

The good news is that we have been solving this problem for decades and we have incredibly well refined tools - database transactions and isolation levels - for solving this problem.

Re: Idempotency is easy until the second request is different

#138

Earlier quoted context omitted.

You are improvising, and in the process, changing the semantics of a well established design pattern. Do not recommend.

Your comment contributes nothing to the discourse. It's FUD. 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.

The topic is about idempotency though, and what you are describing is not idempotency. You are describing something different, and arguing “but it accomplishes a similar thing.” It appears you have come to the same conclusion as the article that building idempotency isn’t trivial.

Re: Idempotency is easy until the second request is different

#139
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.

Being charitable, I'd say the poster above is saying that in the web architecture you can (should?) shift more of the burden for idempotence to the client.

But, rather than 409, I'd say that you should be using opportunistic concurrency control if you adopt this perspective. There should be a resource context for the request, so the client can obtain an ETag and send If-None-Match headers, and get a 412 response if things are out of sync. That allows them to retry a failed/lost request and safely prevent a double action.

Under a 412, they have to step back and retry a larger loop where they GET some new state and prepare a new action. Just like in DB transaction programming, where your failed commit means you roll back, clean the slate, and start a whole new interrogation of transaction-protected state leading up to your new mutation request.

Re: Idempotency is easy until the second request is different

#140

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

>Client_Provided_Key + Hash(Request_Payload) Congrats on destroying the purpose of Idempotency Keys. Ask yourself, why not just `Hash(Request_Payload)`? That'll give you half of what you need to know about why the Idempotency Key header is useful in the first place. The other half you already know? You just described your bug, it's a bug, on your front-end, this has nothing to do with idempotency; if anything, the sy…

[deleted]
Post reply on HN