Does stale-while-revalidate work?


#1

Cloudflare documents a list of directives for the Cache-Control header, including stale-while-revalidate.

  • stale-while-revalidate=<seconds>
    When present in an HTTP response, the stale-while-revalidate Cache-Control extension indicates that caches MAY serve the response in which it appears after it becomes stale, up to the indicated number of seconds since the object was originally retrieved.

I set my Cache-Control header to public, max-age=1, stale-while-revalidate=30 but I never seem to get a cache hit. Does Cloudflare actually support this?

To reproduce: curl -v https://www.choiceofgames.com/swr/test.js and repeat it again a second later.

Actual: The response headers say:

cache-control: public, max-age=1, stale-while-revalidate=30
cf-cache-status: EXPIRED

And a new timestamp is returned in the response each time, indicating a cache miss.

Expected: With stale-while-revalidate, Cloudflare should serve the stale version while refreshing the JS in the background. (I believe cf-cache-status should be UPDATING in that case.) The timestamp shouldn’t update within the 30-second stale-while-revalidate window.


#2

You’re testing with extremely small values so your testing is probably not an accurate reflection of what is happening. With a max-age of 1 second you need to make 2 requests within the same second to see a cache hit.

for ((i=0;i<=1;i++)); do curl -I "https://www.choiceofgames.com/swr/test.js"; done

HTTP/2 200
date: Fri, 13 Jul 2018 20:58:34 GMT
content-type: text/javascript;charset=UTF-8
set-cookie: __cfduid=d97e675ec601cf8ff78d9ade36aa8726c1531515514; expires=Sat, 13-Jul-19 20:58:34 GMT; path=/; domain=.choiceofgames.com; HttpOnly
cache-control: public, max-age=1, stale-while-revalidate=30
cf-cache-status: EXPIRED
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 439eab1e1ac17f06-SFO-DOG

HTTP/2 200
date: Fri, 13 Jul 2018 20:58:34 GMT
content-type: text/javascript;charset=UTF-8
set-cookie: __cfduid=d54b4ab09d37c490f8dd1aeb0d96641bf1531515514; expires=Sat, 13-Jul-19 20:58:34 GMT; path=/; domain=.choiceofgames.com; HttpOnly
cache-control: public, max-age=1, stale-while-revalidate=30
cf-bgj: minify
cf-polished: origSize=21
cf-cache-status: HIT
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
cf-ray: 439eab204cd88c4c-SFO-DOG

UPDATING is not a cache status we return. When present in an HTTP response, the stale-while-revalidate Cache-Control extension indicates that caches MAY serve the response in which it appears after it becomes stale. It doesn’t mean we can’t go to the origin to get a copy and serve it instead. But if there are other waiting requests for the same content we will serve the stale response i believe.

See Also:




#3

I think the goal was to return a stale response from seconds 1 through
30, so the second request would be sent somewhere in that range
(explicitly not within the 1st second).


#4

Indeed. The whole point of SWR is to return stale content after the max-age expiration but before the SWR expiration.

@cscharff You seem a bit unsure about how SWR works. Can you give an example of using SWR correctly? I’m pretty sure my example is configured correctly, and that SWR does nothing on Cloudflare.


#5

I guess I could be a bit unsure, let’s check the RFC and the documentation links against what I said.

https://tools.ietf.org/html/rfc5861

  1. The stale-while-revalidate Cache-Control Extension

When present in an HTTP response, the stale-while-revalidate Cache-
Control extension indicates that caches MAY serve the response in
which it appears after it becomes stale, up to the indicated number
of seconds.

I do like RFCs for their precision in language. Note the use of the term MAY. For the fist subsequent request for an asset at a POP we will go to the origin to revalidate. If there are subsequent requests for that asset before we have revalidated it we will not make additional trips to the origin or wait on the response from the initial requery, but will instead serve the stale asset to those requests.

From one of the documentation links I provided previously:

Cache an asset and serve the asset while it is being revalidated
Cache-Control: max-age=600, stale-while-revalidate=30

Indicates that it is fresh for 600 seconds, and it may continue to be served stale for up to an additional 30 seconds to parallel requests for the same resource while the initial synchronous revalidation is attempted.

So to demonstrate it working you would need multiple requests for the same asset in the same POP for an object which took long enough to revalidate that between the time we went off to the origin to validate the first request in the stale-while-revalidate window and the time we retrieved the new object those additional requests could be served the stale version.


#6

@cscharff, what you’re saying is not accurate; I can show this with a test case.

SWR Doesn’t Affect Parallel Requests on Cloudflare

You’re quoting the documentation saying that SWR allows stale content to be returned after the content has expired, but only during a slow revalidation. Which is to say, after the content expires, a first request X will get a cache miss and slowly revalidate the content, but a second request Y arriving during the revalidation request X will receive fast, stale content.

That’s not what SWR does. What the documentation describes is, in fact, the default behavior of Cloudflare, even if you don’t pass a SWR setting at all. I believe I can prove this with an additional test case.

I’ve configured a URL at https://www.choiceofgames.com/swr/test2.js, designed to sleep for 30 seconds before replying with this cache header:

cache-control: public, max-age=30

You can curl that URL to get a cache hit from your local POP; subsequent requests in less than 30 seconds will return quickly and show the cached timestamp. Now wait 30 seconds for the data to expire. In two tabs, curl the content again in the first tab, and then curl the content again in the second tab.

If the documentation were right: lacking a SWR header, the second tab’s request should also get a cache miss and revalidate the content, taking at least 30 seconds to return.

Actual behavior: Without a SWR header, the second tab receives fast, stale content, with the old timestamp, and the response header cf-cache-status: UPDATING. (Adding a SWR=30 header has no effect, either, because that’s what Cloudflare already does by default.)

UPDATING is not a cache status we return.

That is absolutely not correct, as my test case shows.

SWR Is for Launching Revalidation Requests in the Background, Not Just for Parallel Requests

The purpose of SWR is to ensure that all requests within the SWR interval receive fast, cached content, while ensuring that the proxy frequently updates its cache in the background.

For example, this is a good and useful Cache-Control header with SWR: cache-control: public, max-age=0, stale-while-revalidate=600. Here’s what that should do:

  1. An initial request X comes in for the content at time T=0; a cache miss. Despite the max-age=0 setting, the content should be stored in the proxy cache for at least 600 seconds, because of the SWR setting.
  2. A subsequent (non-parallel) request Y comes in for the content at time T=590. The proxy immediately returns the stored, stale content from the proxy cache of the X request. Without forcing request Y to wait any longer, the proxy also kicks off its own request in the background to revalidate the content. Now that the content has revalidated, it’s valid for another 600 seconds.
  3. A subsequent request Z comes in at T=610. Since the content was revalidated at T=590, the proxy can now return the stale content from the background request Y until T=1190, so the proxy returns the stored content, and again revalidates the content in the background. It’s now valid for another 600 seconds, until T=1210.
  4. Another request W comes in at T=1220. At last the content is “truly stale” (as the RFC calls it); it’s no longer safe to return this stale content. The content is revalidated while request W waits for a response; once revalidated, the content is stored in the proxy cache, and is now safe to return stale until T=1820.

You may enjoy reading Steve Souders writing about this on Fastly’s blog.

By specifying stale-while-revalidate, users aren’t slowed down when a response cached at the CDN needs to be revalidated.

It’s for all requests during the SWR interval, not just parallel requests.

“MAY” Just Means SWR Is Entirely Optional for Proxies

SWR is an optional extension to Cache-Control; that’s what’s meant by the “MAY” in the RFC. You can be a compliant HTTP proxy while completely ignoring SWR (as, indeed, Cloudflare does).

But in the case that the proxy ignores SWR, it’s not correct to say that Cloudflare “supports” SWR. To honor SWR, that would mean that Cloudflare would return stale content during the SWR interval, even to non-parallel requests.

I recognize that a distributed proxy like Cloudflare cannot and does not guarantee a cache hit, even within the max-age internal, but Cloudflare is not doing what SWR is supposed to do even within a single POP.


#7

@cscharff we have the exact same issue described by @dfabulich
We want to use stale-while-revalidate also for the first request that happens in the SWR time without slowing it down


#8

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.