API Caching, Part III

In the first two API caching articles, we focused on using Fastly for API Caching. In part one, we learned that we could program our APIs to report when content has changed and keep the cached information up-to-date using Instant Purging. In part two, we focused on cache control headers and explored how they could be used to control how an API is cached at a more granular level.

In this, our final API Caching installment, we're going to explore how to use Surrogate Keys to reduce the overall complexity of caching an API. This method collapses multiple purges into a single, key-based purge, and generally makes API caching a whole lot easier.

What is a Surrogate Key?

A Surrogate Key is a special HTTP header that tells Fastly an origin response is associated with one or more categories, or keys. When we fetch content from your origin server, we check to see if you have specified a Surrogate Key header, and if so, we add the response to a list we’ve set aside for each of the given keys.

When you want to purge all of the responses associated with a key, you simply issue a “purge by key” request to Fastly, and then we will do the hard work of making sure each of the items under that key is purged. This makes it possible to collapse many purges into a single request and ultimately makes it easier to manage categorically related data.

For more detailed information on how we actually implement Surrogate Key purging, check out Devon O’Dell’s excellent post on the subject.

Example: E-Commerce Products API

To see how the Surrogate Key header works in practice, let’s imagine an API endpoint that returns the details of a product for an ecommerce company. Consider the case where a user wants to get information about a specific product, in this case, a keyboard. The request might look something like this:

GET /product/12345

If the API is using Fastly, and the response is not already cached, then the cache server will make a request to the API’s origin and receive a response like this:

HTTP/1.1 200 OK
Content-Type: text/json
Cache-Control: private
Surrogate-Control: max-age=86400
Surrogate-Key: peripherals keyboards

{id: 12345, name: “Uber Keyboard”, price: “$124.99”}

Notice how the response includes a Surrogate Key header that relates the product to two keys, in this case the shopping categories “peripherals” and “keyboards.”

The API’s developer knew that sometimes entire product categories would need to be purged, so they included the product categories as keys in the response. When Fastly receives such a response, we add it to an internal map, strip the Surrogate Key header, cache the response, and then deliver it to the end user.

Now let’s say at some point in the future that the marketing team for the ecommerce company decides to apply a 10% discount to all peripherals. At this point, the developer shrugs, yawns, and simply issues a key-based purge to Fastly using the peripheral key:

PURGE /service/:service_id/peripherals

Fastly then references its list of content associated with the “peripherals” key and systematically purges each piece of content in the list. Finally, the developer smiles, watches a cat video, and casually updates the marketing team over coffee five minutes later.

Relational Dependencies

Surrogate Keys can be used by an API whenever the system needs to purge a large number of items that can be grouped together in some way. Think about the ecommerce company above: they can use “product type,” a specific sale, or manufacturer to group related items together.

From this perspective, the Surrogate Key header gives information to the cache about relations and possible dependencies between different API endpoints. That is to say, whenever there is a relation between two different types of resources in an API, there might be a good reason to keep them categorized by using a Surrogate Key.

Example: Product Reviews & Action Shots

To make the company more hip, the marketing team has decreed that buyers should be able to post product reviews and “Action Shots” depicting the products as they are actually used. In order to support this hot new feature, the API needs to be changed in a couple of ways.

First, an entire reviews API needs to be fleshed out:

GET /review/:id
POST /review
...

Second, a new endpoint needs to be made to allow buyers to upload their awesome action shot photos:

POST /product/:product_id/action_shot
GET /product/:product_id/action_shot/:shot_id
...

At this point, since both the review endpoints and action shot endpoints refer to specific product data, they need to be purged any time relevant product information changes.

Instead of sending off a slew of individual purges every time a product changes, our sly developer opts to use Surrogate Keys. To do so he ensures that each review and action shot endpoint returns the following header:

Surrogate-Key: product/:id

This relates each of the endpoints to a specific product in the cache (where :id is the product’s unique identifier). When the product information changes, the API issues a single, key-based purge like so:

PURGE /service/:service_id/product/:id

When Fastly receives and processes the purge request each of the related endpoints are purged at the same time. With such an elegant system in place, the developer now has time to turn to more pressing concerns (e.g., finding the last bag of white cheddar popcorn in the company kitchen).

Variations on a Theme

Another problem that Surrogate Key purging solves is that of variations on a theme. Sometimes there are many different API endpoints that all derive from a single source. Any time the source data changes, each of the endpoints associated with it must be purged from the cache. By having each of the endpoints report that they are related to the source by a Surrogate Key, a single purge can be issued to wipe them all from the cache whenever the source changes.

Example: Product Images

To get a feel for how this works, imagine that the ecommerce company has an API endpoint for retrieving product images in various sizes:

GET /product/:id/image/:size

This endpoint returns an image of the appropriate :size (e.g. small, medium, large) for the product of the given :id. To save precious disk space, our clever developer opted to have the API generate each specifically sized image from a single source image using an imaging library like ImageMagick.

Surrogate-Key: product/:id/image

Since the sales and marketing team often uses the API to upload new product images, our developer set up the endpoint to include a special surrogate key, like so:

PURGE /service/:service_id/product/:id/image

Whenever the PUT endpoint for uploading a product image is called, the API sends a purge request like so:

The end result? Every time a new product image is uploaded each of its size variations are automatically purged. The user is happy because they alway have the newest image, the marketing team is happy because they can easily change product images, and the developer is happy because he has more time for nerf gun skirmishes.

That’s All Folks

It’s been a lot of fun writing about API caching in the last few articles, and we hope you all found them to be informative and useful. While we didn’t cover all of the various corner cases that can crop up when caching an API, we think we covered the big ones.

By combining the power of Instant Purge with the flexibility of Surrogate-Control and Surrogate-Key headers it’s possible, and often easy, to wrangle even the most wily APIs into the Fastly cache corral. But if you happen to run into one of the more gnarly cornercases involved in API caching, you can always give us a shout at support@fastly.com. We’ll help you untangle the problem and get things working properly.

Thanks for reading! And good luck with caching your API using Fastly.

Published

6 min read

Want to continue the conversation?
Schedule time with an expert
Share this post

Ready to get started?

Get in touch or create an account.