API Concurrency Control Strategies

Matthew Holliday
The Startup
Published in
5 min readOct 6, 2019

One of the biggest API-scaling challenges facing developers is concurrency control. APIs that are subject to a high volume of unsafe requests must be designed with control strategies that can gracefully handle situations when multiple requests attempt to update a single resource.

As always, different strategies come with different trade-offs. In this article, we’ll examine the pros and cons of the most common approaches to addressing API concurrency so you can decide if you should implement concurrency control and if so, which strategy makes the most sense for scaling your API.

The Problem

API clients commonly perform updates by retrieving a copy of a resource, manipulating it locally, and posting the updated resource to its associated endpoint. This pattern is fine for low volumes of requests but things can become more complicated when multiple clients retrieve the same version of the resource. In that case, both clients make their respective changes locally and post the resource. Whichever request is written last overwrites the other request’s version of the resource, causing changes to be lost.

The simple strategy… do nothing.

Well, not exactly nothing. In the absence of an explicitly-defined concurrency strategy, the last update wins regardless if that update is based on the latest version of the resource. This is okay if concurrent updates are rare and/or low-impact.

Over-optimization is an expensive mistake in software development. This is doubly true in the world of API development, which by its very nature involves at least two parties; those developing the API and those consuming it.

Before embarking down the path of implementing any concurrency control strategy, find out if concurrency is a problem in the first place. If possible, log concurrency issues and see if any are occurring.

In the absence of empirical data, make an estimate based on the best information available to you. Some factors to consider are:

  • The business-value-impact of overwriting data.
  • The number of requests per unit time.
  • The number of resources being updated.
  • The length of time for one resource update to complete.

Avoid concurrency problems in the first place.

The same data can be modeled in different ways… some of which result in more concurrency problems than others. Bigger, more coarse-grained resources can often be broken down into smaller, granular resources to reduce the chance of a request collision.

Similarly, supporting the PATCH operation can reduce concurrency problems by directing the client to only update the parts of the resource that they desire to change. In this case, two clients can perform partial updates on the same resource; if there’s no overlap, there’s no problem.

ETags and Optimistic Locking to the rescue!

Before we describe optimistic locking, let’s take a look at its familiar counterpart; pessimistic locking. In a pessimistic locking scheme, the client locks a resource for exclusive use until it is finished updating the resource. Pessimistic locking is generally undesirable for REST API development. It forces the server to maintain state, a big REST no-no. It also complicates the design of the client and raises the possibility of deadlocking.

Enter optimistic locking.

In an optimistic locking scheme, an update request is only successful if the resource has not been modified since the client last checked. HTTP provides some built-in mechanisms for implementing an optimistic locking strategy using the If-Match conditional header and ETags (ETags can also be used with GET and HEAD requests to improve caching efficiency.)

An ETag is an identifier for the version of the underlying resource. It can be implemented using a hashing algorithm or using a version numbering scheme. It is also possible to use a timestamp for an ETag, though this is generally not recommended since machine clock times may be skewed across the distributed servers that back your API. The key quality of a good ETag scheme is that it produces a different ETag for each version of the resource.

To use make a request using optimistic concurrency, the client application first retrieves the resource that it wishes to update and stores the ETag that the server returns with the response body. The client application then performs any relevant updates and POSTs the resource back with the If-Match condition header and the ETag that it retrieved previously. If the ETag matches the resource’s current ETag, the server performs that update and responds with success. Otherwise, the server rejects the update with a 412 Precondition Failed response.

Optimistic Concurrency Flow (Success Example)
Optimistic Concurrency Flow (Failure Example)

Concurrency Strategies for Bulk Updates

Support for bulk updates can complicate your concurrency strategy. The ability to update large collections of resources dramatically increases the chance that multiple requests will attempt to update the same resource concurrently.

There is no one-size-fits-all approach to supporting bulk REST updates, so whatever concurrency strategy you decide to implement will have to be closely tailored to your API’s specific bulk update semantics. However, there are still a few considerations that apply across bulk API implementations:

  • If possible, allow as many individual resource updates in the batch to succeed as possible. How would you feel if your 2-million-record-update was rolled back after waiting 45 minutes because a single resource was updated by a concurrent request?
  • If extremely large updates are creating too many complications, you can consider lowering the limit on the number of resources a client can update in a single bulk request.
  • Give API consumers options. If appropriate, allow them to decide whether or not the bulk update should be rolled back in case of concurrent requests.

Conclusion

  • Not all APIs require an explicit concurrency strategy but whether or not you implement a concurrency strategy you still need to think carefully about the implications of concurrent requests as you scale your API.
  • Optimistic concurrency complicates client design so only implement it if you are reasonably certain that concurrent requests will pose a problem.
  • Optimistic concurrency is generally favored over pessimistic concurrency in a RESTful context.
  • In the case of bulk endpoints, err on the side of allowing as many updates to succeed as possible.

--

--

Matthew Holliday
The Startup

Programmer writing about machine learning, math, and software engineering. CEO at https://www.goldmountaintech.com/.