RefreshHit from cloudfront even with cache-control: max-age=0, no-store

Viewed 1273

Cloudfront is getting a RefreshHit for a request that is not supposed to be cached at all.

enter image description here

It shouldn't be cached because:

  1. It has cache-control: max-age=0, no-store;
  2. The Minimum TTL is 0; and
  3. I've created multiple invalidations (on /*) so this cached resource isn't from some historical deploy

enter image description here

Any idea why I'm getting RefreshHits?

I also tried modifying Cache-Control to be cache-control no-store, stale-if-error=0, creating a new invalidation on /* and now I'm seeing a cache hit (this time in Firefox):

enter image description here

2 Answers

After talking extensively with support, they explained what's going on.

So, if you have no-store and a Minimum TTL of 0, then CloudFront will indeed not store your resources. However, if your Origin is taking a long time to respond (so likely under heavy load), while CloudFront waits for the response to the request, if it gets another identical request (identical with respect to the cache key), then it'll send the one response to both requests. This is in order to lighten the load on the server. (see docs)

Support was calling these "collapse hits" although I don't see that in the docs.

So, it seems you can't have a single Behavior serving some pages that must have a unique response per request while serving other pages that are cached. Support said:

I just confirmed that, with min TTL 0 and cache-control: no-store, we cannot disable collapse hit. If you do need to fully disable cloudfront cache, you can use cache policy CachingDisabled

We'll be making a behavior for every path prefix that we need caching on. It seems there was no better way than this for our use-case (transitioning our website one page at a time from a non-cacheable, backend-rendered jinja2/jQuery to a cacheable, client-side rendered React/Next.js).

It's probably too late for OP's project, but I would personally handle this with a simple origin-response Lambda@Edge function, and a single cache behavior for /* and cache policy. You can write all of the filtering/caching logic in the origin-response function. That way you only manage one bit of function code in one place, instead of a bunch of individual cache behaviors (and possibly a bunch of cache policies).

For example, an origin-response function that looks for a cache-control response header coming from your origin. If it exists, pass it back to the client. However if it doesn't exist (or if you want to overwrite it with something else) then you can create the response header there. The edge doesn't care if the cache-control header came from your origin, or from an origin-response Lambda. To the edge, it is all the same.

Related