ETags and Optimistic Concurrency Control

I last wrote about ETags in context of Conditional GETs, which allow a server to see if a resource has been modified since the last time a client checked. If it has not been modified, the server can return a 304 Not Modified response.

Another use for Etags is Concurrency Control, specifically Optimistic Concurrency Control.

This helps solve the Lost Update problem which is what happens when multiple people edit a resource without knowledge of eachothers' changes. In this scenario, the last person to update a resource "wins", and previous updates are lost.

For Example

Let's say that 2 API clients both GET a resource example.com/api/article/4, retrieving a blog article with an ID of 4:

$ curl -i example.com/api/article/4

HTTP/1.1 200 OK
Date: Sat, 09 Feb 2013 16:09:50 GMT
Last-Modified: Sat, 02 Feb 2013 12:02:47 GMT
Content-Type: application/json

{
    id: 4,
    content: "Heres a blog article, it has lots of content...",
    status: "published".
    created: "Sat, 02 Feb 2013 08:29:53 GMT",
    updated: "Sat, 02 Feb 2013 12:02:47 GMT",
}

Now, client A notices a grammar error with the word 'Heres' and decides to fix it. Client A issues a PUT request, with updated text.

$ curl -i -X PUT -d "content=Here's a blog article, it has lots of content..." example.com/api/article/4

HTTP/1.1 200 OK
Date: Sat, 09 Feb 2013 16:11:46 GMT
Last-Modified: Sat, 09 Feb 2013 16:11:46 GMT
Content-Type: application/json

{
    id: 4,
    content: "Here's a blog article, it has lots of content...",
    status: "published".
    created: "Sat, 02 Feb 2013 08:29:53 GMT",
    updated: "Sat, 09 Feb 2013 16:11:46 GMT",
}

Now, around the same time, client B decides to add some content. However, client B doesn't know about client A's change! Client B makes an update:

$ curl -i -X PUT -d "content=Heres a blog article, it has lots of content, and now has an extra sentence..." example.com/api/article/4

HTTP/1.1 200 OK
Date: Sat, 09 Feb 2013 16:12:02 GMT
Last-Modified: Sat, 09 Feb 2013 16:12:02 GMT
Content-Type: application/json

{
    id: 4,
    content: "Heres a blog article, it has lots of content, and now has an extra sentence...",
    status: "published".
    created: "Sat, 02 Feb 2013 08:29:53 GMT",
    updated: "Sat, 09 Feb 2013 16:12:02 GMT",
}

The incorrect 'Heres' is back! Client A's update is lost; We have experienced the Lost Update Problem.

How ETags can help

ETags can be used in combination with the If-Match header to let the server decide if a resource should be updated.

This works by letting a client know that a resource has been updated since the last time they checked. The server informs the client via a 412 Precondition Failed response.

>If a resource changes, so does it's ETag. In this way, each "version" of the resource has a unique ETag. If a client attempts and update with a mismatching ETag, then the update is cancelled and a 412 status is given.

This is how the previous example would play out if ETags were employed.

The 2 API clients both GET the resource /api/article/4, retrieving the article. This time, an ETag is returned.

$ curl -i example.com/api/article/4

HTTP/1.1 200 OK
Date: Sat, 09 Feb 2013 16:09:50 GMT
Last-Modified: Sat, 02 Feb 2013 12:02:47 GMT
ETag: c0947-b1-4d0258df1f625
Content-Type: application/json

{
    id: 4,
    content: "Heres a blog article, it has lots of content...",
    status: "published".
    created: "Sat, 02 Feb 2013 08:29:53 GMT",
    updated: "Sat, 02 Feb 2013 12:02:47 GMT",
}

Now, client A once again notices the grammar error and decides to fix it. Client A issues a PUT request, with updated text and the ETag received in the first request, in the form of the If-Match header.

$ curl -i -X PUT -d "content=Here's a blog article, it has lots of content..." -H "If-Match: c0947-b1-4d0258df1f625" example.com/api/article/4

HTTP/1.1 200 OK
Date: Sat, 09 Feb 2013 16:11:46 GMT
Last-Modified: Sat, 09 Feb 2013 16:11:46 GMT
ETag: df35b-af-36f3b324a3ff6
Content-Type: application/json

{
    id: 4,
    content: "Here's a blog article, it has lots of content...",
    status: "published".
    created: "Sat, 02 Feb 2013 08:29:53 GMT",
    updated: "Sat, 09 Feb 2013 16:11:46 GMT",
}

What happened here? The ETag given by Client A matches the ETag of the resource, and so we know that the resource has not changed without Client A's knowledge. The update is performed, and a new ETag is generated for that resource.

Around the same time, client B decides to add some content. However, client B doesn't know about client A's change! Client B attempts an update, with the old ETag:

$ curl -i -X PUT -d "content=Heres a blog article, it has lots of content, and now has an extra sentence..." -H "If-Match: c0947-b1-4d0258df1f625" example.com/api/article/4

HTTP/1.1 412 Precondition Failed
Date: Sat, 09 Feb 2013 16:12:02 GMT

Here we see that the update did not occur. The ETag used in the If-Match header did not match the ETag of the resource. Client B therefore received a 412 Precondition Failed response and then knows it needs to update it's knowledge of the resource before attempting to update again.

Notes

  • This type of concurrency control puts a higher onus on the client. The client would need to implement ETags, using If-Match headers and handling 412 responses.
  • Note that the server needs decide to what to do if no If-Match header is present - Does it enforce its use, or only use concurrency control when it has the needed headers? The answer to this may boil down to your particular needs.

What we have covered

  1. ETags can be used for Optimistic Concurrency Control to solve the Lost Update problem
  2. We saw an example with no concurrency control
  3. We saw an example with concurrency control
  4. I wrote some notes on other considerations

Soon I hope to write about implementing ETags with Laravel 4 here, as an extension of my recent Laravel 4 API article.

comments powered by Disqus