Optimistic concurrency control with ETags
Implementing safe concurrent updates without server-side state (locking) using optimistic concurrency control (OCC) via HTTP’s ETags.
The problem
Let there be a central content management system (CMS) with a lot of texts.
- A wants to proofread a text, so she downloads the text from the CMS and starts working on her local copy.
- While she is making corrections, somewhere else in the world B decides to add a few sentences to the same text, so he fetches another copy and starts typing.
- Now A is done and she sends her copy with the corrections back to the CMS, which overwrites the text with her version.
- Shortly after her, B also finishes his work and sends his copy to the CMS, which accepts his version.
The system worked, nobody got an error message, everybody is happy, but without knowing it, B overwrote A’s version of the text, so A’s work is lost.
A solution
One way to avoid this problem is using entity tags (ETags). When A and B fetch a copy of the text, the server sends an ETag along with the text that identifies the current version. This can be a revision number, the latest modification date, or a hash of the text. When A sends her modified copy back to the server, she attaches the ETag the server sent her before. The CMS then determines if her ETag still matches that of the current version of the text. It does, so her request is accepted and the text is updated. When B sends his copy, the ETag of the current version on the server no longer matches his ETag, so his request is rejected and he receives an error message. To commit his changes, he needs to fetch the latest version of the text and apply his edits again.
The ETag header was introduced in HTTP 1.1. The client reads and stores the tag from this header and later can send a conditional request, where the tag is sent back to the server in the If-Match header. If the tag matches, the request is accepted, otherwise the server returns a 412 client error (Precondition Failed). It’s important that tag validation and data write are performed as a single atomic operation, otherwise there can be race conditions. A system that supports this strategy is, for example, Azure’s blob storage.
Closing
OCC works best in systems where it’s unlikely that multiple updates compete for the same resource (there is low data contention), otherwise the cost of repeating failed requests becomes a problem. However, if we were to actually implement a CMS, we probably wouldn’t use this technique even if concurrent edits were unlikely, because we never want to force users to redo their work and there are better options for collaborative text editing, for example, operational transformation (OT).