The labs on this site need JavaScript to run. The same content is written out below.
Learn › Networking › In front of the web server
CDN
The origin was changed, so why do users still get the old file?
A CDN keeps copies of the files held by the origin server on servers close to the users (edge servers) and has the edge servers answer on its behalf. The first request finds no copy at the edge, so it goes all the way to the origin (MISS), but after that the edge answers right away (HIT). In exchange, the edge does not ask the origin again during the freshness lifetime of a copy, so if you change the file at the origin in the meantime, users still receive the old copy.
Partly checked against real execution · Ran 16 combinations with an origin server and a self-built caching server (acting as the edge) started on 127.0.0.1, and checked. Not checked against a real CDN service
Concepts in this lab
- CDN
- A network that keeps copies of content on servers placed in many regions around the world and has a server close to the user respond. Static files such as images, video, and scripts are delivered faster, and fewer requests go to the origin server.
- Origin server and edge server
- The origin server is the server that holds the original of a file, and an edge server is a server of the CDN, placed close to users, that keeps copies of it and answers on its behalf. A request from a user arrives at the edge first. If the edge has a usable copy, it answers right away (HIT), and if not, it fetches the file from the origin, passes it on, and keeps a copy (MISS). The edge does not ask the origin again during the freshness lifetime that the origin set in the response header. That makes responses faster and reduces the load on the origin, but changes made at the origin in the meantime are not reflected at the edge.
- Cache key
- The cache key is the information a cache uses to look up a stored copy. An HTTP cache by default uses the request method and the whole address (host, path, and query string) as the cache key. What a CDN puts in the cache key differs by product and setting, and some CDNs do not include the query string by default (the default cache key of CloudFront is the domain name and the path). When the query string is in the cache key, even a one-character difference in the query string of the same file makes it a different copy, and the request goes to the origin server again. Using this the other way around, if you change the version value in the address each time the file changes, you can send out the new content without deleting the old copy. So that query strings unrelated to the content do not split the copies into small pieces, CDNs have settings that decide which values go into the cache key and which are left out.
- Cache invalidation (purge)
- Cache invalidation (purge) is the act of forcibly deleting a copy from the cache while it still has freshness lifetime left. If the edge of a CDN keeps sending out an old copy after you changed the origin, requesting invalidation of that address makes the next request fetch the new content from the origin. It is not a function defined in the HTTP standards; each CDN or proxy product offers its own, and it deletes by a single address or by a group of paths. It deletes only the copy at the edge, and it cannot delete the copy that a user's browser keeps separately. So for files that change often, rather than relying on invalidation, it is also common to use a version or a hash in the address.
- TTL (time to live)
- The length of time for which data is valid. If you set it when you put a value in a cache, the value is deleted automatically after that time passes. It is a basic safeguard that stops a value that no longer matches the source from staying in the cache forever.
Try these
- Default settings — Freshness lifetime of 300 seconds. Although the origin was changed, the users at 40s and at 80s both get the old content.
- If the freshness lifetime is 60 seconds — At 40s the user gets the old content, and at 80s the edge asks again and the user gets the new content.
- Invalidation (purge) — The copy at the edge is deleted, and the next request fetches the new content from the origin.
- Change the address — ?v=2 is a different address, so the edge has no copy of it and fetches the new content.
- If a query string is added — Even though it is the same file, it is treated as a different copy, and the request goes to the origin one more time.
- When the file is not changed — The request goes to the origin only once, and the edge answers all the rest.
Running with the default settings
- 0s: A user sends GET /app.css?v=1. The request does not go to the origin; it arrives at the edge server close to the user. The edge has no copy of this address (MISS). The edge fetches the file from the faraway origin, gives it to the user, and keeps a copy. According to the s-maxage=300 that the origin wrote in the response, this copy may be used as it is for 300 seconds.
- 10s: Another user requests the same address. The edge has a copy, and it was fetched 10 seconds ago, so it is within its freshness lifetime (HIT). The edge answers right away without asking the origin. The origin does not even know that this request happened. Because many users share one copy, this is called a shared cache.
- 30s: A developer changes the app.css at the origin to new content and deploys it. The origin does not tell the edge about this. The copy the edge has stored is still the old content.
- 40s: A user requests the address /app.css?v=1. The copy at the edge was fetched 40 seconds ago, so it is within the freshness lifetime (300 seconds) (HIT). The edge gives the stored copy as it is. The file at the origin was changed 10 seconds ago, but the edge does not know. The user gets the old content.
- 80s: A user requests the address /app.css?v=1. There is a copy, and 80 seconds have passed since it was fetched, which is within the freshness lifetime (300 seconds) (HIT). The edge answers right away. It has been 50 seconds since the file at the origin changed, but the user still gets the old content. This keeps happening until the freshness lifetime ends at 300 seconds.
Result of this run
- When the edge answers right away, only a short distance is traveled, so it is fast (20ms in the example), and going to the origin adds the long distance (220ms in the example). The number of requests the origin receives also goes down. These times are examples to show the difference.
- The edge does not ask the origin again during the freshness lifetime. So even if you change the file at the origin, copies that still have freshness lifetime left continue to go out. The longer the freshness lifetime, the faster the responses, but the more slowly the changed content spreads.
Code
# Response header that the origin adds when it serves app.css
Cache-Control: public, s-maxage=300 # a shared cache (the edge) may use the copy as it is for 300 seconds
// Edge: a server that keeps copies and answers on their behalf (a shortened version in Node.js)
const store = new Map(); // address (path + query string) → stored copy
http.createServer(async (req, res) => {
if (req.method === 'PURGE') { // invalidation: deletes the copy of that address
store.delete(req.url);
return res.end('purged');
}
const copy = store.get(req.url);
if (copy && age(copy) < copy.ttl) { // there is a copy and it is within the freshness lifetime
res.setHeader('X-Cache', 'HIT');
return res.end(copy.body); // answers right away without asking the origin
}
const fresh = await fetchFromOrigin(req.url); // if there is none or the freshness lifetime has passed, go to the origin
store.set(req.url, fresh);
res.setHeader('X-Cache', copy ? 'EXPIRED' : 'MISS');
res.end(fresh.body);
});
// Order of requests
// 0s curl https://cdn.shop.example/app.css?v=1
// 10s curl https://cdn.shop.example/app.css?v=1
// 30s deploy the new content of app.css to the origin
// 40s curl https://cdn.shop.example/app.css?v=1
// 80s curl https://cdn.shop.example/app.css?v=1
Assumptions of this model
All 16 combinations (if the file is not changed, "What is done after the change" is not used) were run using an origin server and a small caching proxy acting as the edge, both started on 127.0.0.1, and it was confirmed that for each of the four requests whether it was a MISS, HIT, or EXPIRED, the content received, and the number of requests that reached the origin all matched the model. The edge is a test one built with Node.js 24; deciding whether a response may be stored and whether its freshness lifetime remains was left to http-cache-semantics 4.2 (shared cache mode), an implementation of the HTTP caching rules. To avoid waiting 80 seconds, the clock of the edge was run fast (1 second of the model as 30ms in real time), and the part of the user was played alternately by fetch of Node and curl 8.21. This was not checked on a real CDN service. The times taken (20ms, 220ms) and "close" and "far" are not measurements but examples to show the difference. That s-maxage sets the freshness lifetime of a shared cache and that a copy past its freshness lifetime is not used as it is follows sections 4.2 and 5.2.2.10 of RFC 9111, and that a copy is looked up by the request method and the address follows section 2. The names HIT, MISS, and EXPIRED and the X-Cache header are not standards but conventions used by many CDNs and proxies, and the spelling differs by product. Invalidation (purge) is also a product-specific feature that is not in the standards, and here it follows the common approach of deleting the copy of one address with a PURGE method. When the freshness lifetime has passed, the edge in this model fetches the whole file again, but real CDNs usually first ask with a conditional request only whether the file "has changed" (see the HTTP caching and ETag lab). Not covered: that there are several edges in several regions, each keeping its own copies; the copies that browsers keep separately; and a feature that fills in copies in advance.
Previous lab: Proxies and reverse proxies