Astro cache miss and bypass

Hi all.

I’ve used this guide to try and put together a quick proof-of-concept for a stale-while-revalidate cache strategy, leveraging Netlify’s great ISR/cache model.

I have a demo site here (SSR with the Netlify adapter): https://astro-relume-netlify.in-beta.link/.

In the front-matter of the index route, I have:

Astro.response.headers.set(
  "Cache-Control",
  "public, max-age=0, must-revalidate"
);

Astro.response.headers.set(
  "Netlify-CDN-Cache-Control",
  "public, durable, s-maxage=300, stale-while-revalidate=604800"
);

What I expect to happen is that the first visitor after a deploy would get a miss and then subsequent visit with the would receive a hit or a stale copy.

What I see is that every new visitor gets an edge miss and a durable bypass, with the response headers looking like this:

cache-control: public,max-age=0,must-revalidate
cache-status: "Netlify Durable"; fwd=bypass
cache-status: "Netlify Edge"; fwd=miss

The page doesn’t use any cookies or query strings etc. and to rule anything specific to that page out, I’ve added a test route of Cache Test which only contains the following in a single index.astro file:

---
Astro.response.headers.set(
  "Cache-Control",
  "public, max-age=0, must-revalidate"
);
Astro.response.headers.set(
  "Netlify-CDN-Cache-Control",
  "public, durable, s-maxage=300, stale-while-revalidate=604800"
);
---

<html>
  <head><title>Cache Test</title></head>
  <body>
    <h1>Cache Test Page</h1>
    <p>Testing Netlify caching: {new Date().toISOString()}</p>
  </body>
</html>

But the response headers come back the same and you can see a fresh time stamp with each refresh.

Any ideas what I’m doing wrong here?

Some kind of progress, although this has also confused me slightly more…

I’ve made a second test page, with everything else stripped out with these headers:

Astro.response.headers.set(
  "Cache-Control",
  "public, max-age=0, must-revalidate"
);

Astro.response.headers.set(
  "CDN-Cache-Control",
  "public, durable, s-maxage=60, stale-while-revalidate=604800"
);

The main change here is changing "Netlify-CDN-Cache-Control" to "CDN-Cache-Control". If I keep the Netlify prefix then no caching seems to happen at all.

Now the caching seems to kind of work. What I mean by that is I can see the time output remain the same for the 60 seconds, then update, which is what I would expect with the headers. However, it doesn’t look like the “durable” aspect is working. If you refresh the page a bunch of time, you mostly see:

cache-control: public,max-age=0,must-revalidate
cache-status: "Netlify Edge"; hit
cdn-cache-control: public, durable, s-maxage=300, stale-while-revalidate=604800

However, every now and again but before the s-maxage has expired, you get a much longer response time and the following response headers:

cache-control: public,max-age=0,must-revalidate
cache-status: "Netlify Durable"; hit; ttl=20
cache-status: "Netlify Edge"; fwd=miss
cdn-cache-control: public, durable, s-maxage=60, stale-while-revalidate=604800

This time there seems to be a durable hit and an edge miss. But then refresh the page again and you go back to an edge hit and no mention of durable. The difference in response time is many fold; the edge hits have a response time of ~20ms while the durable hits have a response time of ~450ms.

Finally, I would also expect the stale cache to be served while a fresh copy was generated. However, once the s-maxage passes and I see a stale cache like this:

age: 814
cache-control: public,max-age=0,must-revalidate
cache-status: "Netlify Edge"; hit; fwd=stale
cdn-cache-control: public, durable, s-maxage=60, stale-while-revalidate=604800

The response time is high, around ~400ms. I’d expect to see times that are the same as a cache hit.

On that route, we’re skipping durable cache because of invalid vary. netlify-vary is set to none for some reason, which is invalid. Not sure where it’s coming from, but that’s the reason for the issue.

What’s the URL for the second page?

Hi @hrishikesh. Thanks for the reply.

I was trying to rule out any possibility of the page skipping the cache with netlify-vary set to none. I’ve now removed this.

On the index route, I’ve removed the netlify-vary header. I do now see cache hits, although it’s very strange as I only added that header after I was getting only cache misses before but now it seems to work by removing it and having not changed anything else.

What I’m typically seeing now:

cache-status: "Netlify Edge"; hit

With a very low response time of ~20ms.

But when the revalidation time passes (it’s set to 60s for now), the response headers are:

cache-status: "Netlify Durable"; hit; ttl=200
cache-status: "Netlify Edge"; fwd=miss

With a higher response time ~450ms.

Apologies for missing the second link, it is: here but now the behaviour is similar between the home page / index and the this new link.

Is what’s happening here that the majority of the time I’m getting an edge cache that already exists on a node close to me because my previous visits triggered them but when the s-maxage time passes , I get the stale copy instead from the durable cache, which appears to have much higher response times?

I can confirm I’m seeing the behaviour similar to your report. I’ve asked the devs, if these timings or expected or not.

1 Like

Any update @hrishikesh?

Sorry about the delay. Based on the discussion, the performance here is expected. The devs mentioned that the timings that you’re seeing fall within the acceptable range from the infra’s point of view.

As far as cache not being hit, that seems to be fixed. Are you still running into an issue? I trid to reproduce on your links and it seemed to work.

Hi again @hrishikesh and thanks for follow up.

Although the response times aren’t horribly high, around 300ms higher than a cache hit for me, I was just surprised that this is the case specifically with stale hits; I had thought stale hits would have the same response time as regular cache hits but I think you are saying that’s the expected behaviour? I’d love to understand the technical reason there.

I am still seeing misses too. Hard to narrow down the exact circumstances but it seems to be for sessions shortly after the page is newly cached, almost like the stale cache has been evicted but the new cache hasn’t fully propagated yet. It’s not all visits after the cache is regenerated, so perhaps propagation across different nodes? Just a theory!

See this screenshot for an example of a cache miss (you can see the short age):

Between these two details, it makes it hard to buy into the stale-while-revalidate model if you’re using a short cache duration. If you’re caching pages for an hour, a few short windows of longer response time for a few seconds here and there don’t matter too much but if you’re using a short cache of a minute for example, there will be significant chunks of time where response times are noticeably higher.