Hacker News new | past | comments | ask | show | jobs | submit login

But it's only the POST that this is a problem for, right? PUT and DELETE are supposed to be idempotent so retries are okay, yes?



There could be races between PUT/DELETE. There is no grantee how the retry was made.


A race shouldn't matter so long as both succeed at least once; that would have the same effect as if either/both had succeeded multiple times. The only difference is whether the user is informed of whether a related action obviated their request, which is going to happen anyway.

Edit: turns out I was wrong and assumed PUT should fail if the resource doesn't exist, which isn't how it works. (Probably because of writing apps that deprecate it in favor of PATCH.)


> A race shouldn't matter so long as both succeed at least once; that would have the same effect as if either/both had succeeded multiple times.

Idempotence only means that the same single method repeated additional times on its own will not produce different end states. It doesn't necessarily guarantee this for combinations of methods in different orderings.

It doesn't stop PUT/DELETE/PUT/DELETE from having different results than PUT/DELETE/DELETE/PUT to the same resource. (You can do assure that these are equivalent in a particular HTTP-compliant application, but it goes beyond the base semantics of HTTP to do so.)


I was only saying there that the combination didn't create other problems (due to race conditions), not that that fact was related to idempotence. Though it happens to be true for the combination of PUT/DELETE as well!

I think you're equating my claims about what methods are idempotent with my claims about what reorderings matter.


Shouldn't matter?

  DELETE foo/bar
  PUT foo/bar
If that delete gets a retry, actual execution order could be

  PUT foo/bar
  DELETE foo/bar
Or am I misunderstanding this?


It doesn't matter: as I said, that has the same end state (no foo/bar resource), the only possible difference is response code i.e. whether (in this case) you get to learn that your update doesn't matter.


The first sequence ends with foo/bar existing. The second one ends with it not existing.

I'd say that anybody that sends a pair of PUT/DELETE requests in fast succession over the web and expects a stable result is a fool. This should have no effect on practice, because nobody should be relying on the ordering anyway.


Ah, my mistake. I had always equated PUT with updating and assumed it should fail if it doesn't find the resource. Big oversight!


I think you're correct, actually. If you want to create a new object, it should be a POST. In a well designed RESTful service, a PUT on an object that doesn't exist should fail, and both of the PUT/DELETE orderings above should result in the same state of the world: the object does not exist.


I thought so too, but when I looked at the spec [1], it agreed with the others:

>>The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI

[1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html 9.6


Sounds like the others are indeed correct. Thanks for the citation!


You're probably thinking of PATCH. In many, if not most, RESTful services, PUT is given PATCH semantics. PUT is supposed to be insert-or-update.


Ah, I stand corrected. Thanks!


This is why the If-Match and If-None-Match preconditions exist; they resolve PUT races by checking that the resource is in the expected state.


I think you mean If-Match and If-Unmodified-Since. Actually they are relevant for DELETE as well. E.g. you might not want to DELETE if another client has just PUT.


If-None-Match: * ensures that another client hasn't created the resource you are trying to create. It is equally important as If-Match for resolving race conditions.


It's fairly uncommon to use PUT for resource creation. In that case, however, if the server supported it, yes you could use If-None-Match. I really have to wonder about the architecture of the system, however, if two clients can simultaneously decide to create the same resource rather than two similar resources.


Surely you're joking.

* Literally the first thing RFC 7231 says about PUT is "The PUT method requests that the state of the target resource be created or replaced […]". RFC 7231 takes into account many changes in HTTP practice over the past decade (even bizarre ones like POST-to-GET on a 301 redirect); if create-on-PUT were frowned upon, it would be called out.

* PUT as described in RFC 7231 is the same thing as UPSERT in an RDBMS, or a write operation in a key-value store. These are certainly not uncommon DB operations; their REST analogue is similarly useful.

Here's some examples:

* PUT is how documents are created in WebDAV. WebDAV is multi-user, so two users may decide to create a document with the same name, just like on any file system. If-None-Match: * is the only way to support the O_EXCL flag on POSIX open(2).

* A resource which represents attributes of arbitrary external resources will have a URI named after the external resource (e.g. UPC or SHA-1, etc.), and therefore must be created with PUT. If-None-Match: * is the only way to prevent lost updates when the external resource is first made known to the system.

PUT-as-create is sound design supported by precedent for any system where the keys have a priori meaning.


Well, mostly If-None-Match is used to save bandwidth by allowing the client to validate a stale resource.


Yes. Incorrectly implementing the HTTP spec is widespread though, I imagine especially with PUT.

People are writing custom HTTP application servers and making PUT do anything and everything. Idempotence doesn't usually enter the picture.


"Yes. Incorrectly implementing the HTTP spec is widespread though"

Huh??? First of all, that's a pretty serious statement to be making with no evidence to support it. Plus, that is no excuse for any server that is NOT compliant.

The very fact that nginx decides to create HTTP status codes, willy-nilly, for its own use shows at least the suspicion that strict compliance is not a priority for them. Thankfully, it is for other web servers.


From the few days I ever did API design (as a trainee, not even employee yet), idempotence is one of the first things you encounter when looking for general design and it definitely came into view for me. Then again I'm not the average student, but still.


I mean, this is subject to human beings writing things, unfortunately


Huh, didn't know. Thanks for pointing that out!


Sure, but the point is you should design your POSTs to be idempotent as well.


Well, they are defined as non-idempotent, so there's no reason why you 'should' design this way. You can't make every request idempotent.

It's best to design so that duplicate POSTs are handled sensibly (e.g. you don't make a user pay for the same product twice), but the response to the second POST is unlikely to be the same as the first one, so they aren't idempotent.

More difficult cases are where an action could legitimately be performed twice, e.g. adding an item to a shopping cart. You must differentiate between a wrongly-duplicated request, and a real request to add the second item. One way to do this is to add parameters to the POST so that it can be identified as a duplicate. But it can be tricky to do this without holding a lot of extra state in the server application, and there are all sorts of concurrency problems when you have a cluster of servers.


There's really no such thing as perfectly idempotent operations. But it's an ideal to be emulated as much as possible. Even (or maybe especially) when something appears to be by definition not idempotent.


I think the most of these cases can be handled via PUT i.e. update a cart so it contains these items. That way you keep all the state on the client.


That's great unless people want to shop in multiple browser tabs or anything like that. The real solution here is to use the fact that we have POST which is specced as non-idempotent and not depend on the very small set of technologies that purposefully disobeys the spec.


"Should" is as strong as you can go with that statement. Not all POSTs can be idempotent. Nginx has to deal with that in the general case.


A better approach IMHO is to turn them into PUTs. If something would normally be a POST but you've eg used GUIDs to ensure that creation actions are idempotent, then such actions should be PUTs.

But then again, some things must not be idempotent: eg "shuffle this deck of cards in an order that is random to me".

Edit: On second thought, you could make that idempotent too, albeit at the cost of increasing server load and your app's architecture's complexity -- you would just have to verify that the deck has had some reordering since that client's request, and not make any further reorderings in response to that client, since from their perspective it's still randomized.




Consider applying for YC's first-ever Fall batch! Applications are open till Aug 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: