304 Response at Fastly
Periodically, I’ll get tickets in from customers who are concerned because an object in cache doesn’t behave the way they’re expecting. When they purge the object, everything looks great, then, when the object’s TTL expires, some weird things might happen. They might get a cache HIT after the TTL has expired on an object. Or the TTL might change in an unexpected way. What’s happening here?
It’s not every case, but sometimes, if a response from origin contains a Last-Modified or ETag header, the culprit turns out to be a 304 response. Let’s explore why.
Let’s start with the standard request process to make sure we’re all on the same page. When a request is made to Fastly, we’ll check our cache to see if we have the object stored. If we do, we just return it to the client. If we don’t, we’ll go back to the origin and retrieve a copy of the object. We’ll cache the object (if we’re told to do so) and we’ll send a response back to the client.
Now let’s look at what the standard expected behavior is when we hit an object’s TTL. When we cache the object, we determine how long to hold onto the object. We’ll calculate this value based on headers set at the origin or within your VCL or through variables such as beresp.ttl. When that TTL is reached, we will no longer serve the object from cache. So, the next request comes in asking for that object, we we see that the TTL has been reached, and we go back to the origin to request the object.
So far, pretty easy. But! When an object cached on Fastly’s servers contains a Last-Modified or ETag header, rather than going to the origin and simply requesting a brand new object, we’ll include an If-Modified-Since or If-None-Match header. This allows the origin to check if the object has changed and return a simple 304 response rather than the much larger object.
Let’s take the example of an object that has a TTL of 2 hours. A request comes in 10 minutes after the object is cached. We respond with the object in cache. Neat! Another request comes in 2 hours and 2 minutes after the object was cached. The TTL has been exceeded, so we can see that we can’t use the object in cache. We send a request back to the origin.
This is where those headers come into play. If the object was cached with a Last-Modified or ETag header, Fastly sends a request to the origin with an If-Modified-Since or If-None-Match header. This tells the origin, “hey, check to see if this object has changed since we last got it please!” The origin will check the contents of the If-Modified-Since or If-None-Match header against the Last-Modified or ETag contents of the object. If the object hasn’t been updated, the origin will return a simple 304 Not Modified response. It’s an incredibly small response, just the headers, no response body, so it saves on egress traffic from origin. Neat!
But! What does Fastly do with that response? Well, when we get the 304, we re-up the TTL of the object we have in cache… HOWEVER, the way we do it is different than the way we process a 200 response from origin. There is no VCL modification of the object because we don't receive the object from origin. If there’s no cache-control header on a 304, it will just refresh the object’s existing TTL accordingly. Otherwise, we will use the cache-control header on the 304 to set the objects new TTL.
It’s important to note here that we do NOT get another object from the origin when we get a 304 response. We just keep the object we already have in cache and renew the TTL on it. So, if you change your VCL and expect a new header to be added to the object, you won’t see that until the object has either been purged, or the origin returns a non-304 status code, or we evict the object because it has become a least recently used object.
As a final note, when Fastly receives a 304 response from origin, because of the way request is processed going through Fastly, there’s a significant chance that we’ll deliver a 200 back to the client. So this can be a little obfuscated when troubleshooting! The key identifiers to look for are those Last-Modified or ETag headers and a response that doesn’t behave as expected after the TTL is expired. As always, if you’re seeing responses from Fastly behave in a way you aren’t expecting, contact support. We’re happy to dig into your questions with you and use every tool in our arsenal to help you find a solution.
If you have any followup questions, please don’t hesitate to comment below or reach out to support at support@fastly.com.
-
Hello,
Thanks for sharing ! It's a very interesting case indeed ! I never got but I am now aware of it :)
I have two questions :
1. Why at the end Fastly return a 200 and not 304 ? (If the browser request with an Etag it should return a 304 no ?)
2. I have never read anything about that TTL of 120s for 304 not modified (when Fastly contact the backend with If-Modified-Since or If-None-Match header), is it documented somewhere ?
If i am understanding well, if the TTL of an object is 1 day, after that day if the backend return a 304 without cache control, you will increase the TTL of that object for 2 minutes (meaning we will have a a request to the backend every 2 minutes (only a 304). Is it correct ?
Thanks,
Alex
-
Hi Alexandre,
Excellent questions.
1. Why at the end Fastly return a 200 and not 304 ? (If the browser request with an Etag it should return a 304 no ?)
So, there are several different places where a 304 could occur:
- Between the browser and Fastly
- Between the edge POP and the shield POP
- Between Fastly and your origin server
This article was directed at addressing what happens with a 304 response from your origin to Fastly and how that can impact cached objects on our servers.
That said, you are absolutely correct. If Fastly receives a request with a If-Modified-Since or If-None-Match header, and we can respond with a simple 304, we absolutely will. But, if a request comes in from a browser that does not yet have the object cached and the object Fastly has in cache is expired, we will send the request to your origin with a If-Modified-Since or If-None-Match header. If the response from origin is a 304, we'll respond to the client request with 200.
2. I have never read anything about that TTL of 120s for 304 not modified (when Fastly contact the backend with If-Modified-Since or If-None-Match header), is it documented somewhere ?
...
So... Ummm... This was something I misunderstood. I have since corrected the article above.
If there’s no cache-control header on a 304, it will just refresh the object’s existing TTL accordingly. Otherwise, we will use the cache-control header on the 304 to set the objects new TTL.
My apologies for any confusion. Thanks for asking questions that helped me correct the statement!
-
An extra piece of information about this working at Fastly, specially for the end-user. If you need this to work in the user browser too but you are getting a 200 instead of a 304, please make sure that you are sending the
Last Modified
header with the following date format:<day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
It will only work if your date complies with the RFC 1123 time format.
Please sign in to leave a comment.
Comments
4 comments