A few weeks ago, I wrote and post introducing the concept of Hit-for-Miss and I received a few questions about it because "Hit-for-Pass" is much more common on the interwebz, and it looks like it could do something similar, and I completely omitted it last time. What's up with that?
Let's embark on a short chronological journey explaining how HfM and HfP (as the cool kids call them) came to be, how they are different and why you should care.
I won't go into the details of the previous post, but as a reminder: request coalescing can turn into request serialization and setting a zero or negative TTL is pretty bad. Instead, we want to set a positive TTL to store the object in cache, but mark it as unusable.
Upon seeing that object, client requests will disregard it and go directly to the backend without coalescing requests. Effectively this transforms a "hit" on the unusable object into a "miss", hence the "Hit-for-Miss" moniker.
Did I just condense the 10-minute read from last time into two short paragraphs? Yes, yes I did, you are welcome.
The interesting thing is that the VCL snippet:
sub vcl_backend_response {
# remember that this object marked as private isn't cacheable
# for the next hour
if (beresp.http.cache-control ~ "private") {
set beresp.ttl = 1h;
set beresp.uncacheable = true;
return (deliver);
}
}
triggers a Hit-for-Miss in recent Varnish version, but before Varnish 5.0, it triggered a Hit-for-Pass. The logic is pretty much exactly the same: mark an object as unusable, and if a request hits it, convert it into a miss pass.
HfP did its job, i.e., preventing request coalescing well, but the problem was that it was a bit too overzealous. See, a pass means that the object will never touch the cache, even if we discover in vcl_backend_response that it was indeed cacheable.
It turned out to be an issue for content that was only momentarily uncacheable. Consider this scenario:
The acute reader will notice the irony of the situation as it's the mirror image of the previous post situation where we assumed wrongly the response would be cacheable.
And this is of course why Hit-for-Miss was introduced; it retains the coalescing prevention but by being a miss, it allows Varnish to put an uncacheable-turned-cacheable object into the cache. This is the reason why I generally jack up the TTL for uncacheable objects: the moment the backend changes its mind, I'll start caching again. Easy.
The change from HfP to HfM was made more than four years ago, but the internet never forgets and because Hit-for-Miss and Hit-for-Pass sound and act similar, people will carelessly but relatively safely use one for the other.
If that was the whole story, it would make for too short a blog post, and fortunately, there's a twist in the story!
HfM is superior to HfP pretty much all the time, but there is one corner case where it isn't. It boils down to the fundamentally different approaches of misses and passes to backend requests:
And most of the time, these things are the same, but sometimes, they're not!
Let's Imagine you have marked an object as uncacheable and that a client sends you a conditional request for it. Let's see how both approaches differ.
With a Hit-for-Pass: Because the backend request is a pass, Varnish will just pass (not-pun intended) the request without tweaking it. Notably, the if-modified-since and/or if-none-match headers will be preserved.
If the backend supports it, and if the object hasn't changed, Varnish will receive a 304. It's a body-less response that just says "no update, you're good" to the client. It's pretty neat because it can save a lot of bandwidth by not downloading the same object over and over. It's also something that Varnish does by default.
With a Hit-for-Miss: In this case, Varnish can't afford to receive a 304 because its goal is to put the object in cache and "no update, you're good" is pretty useless if you don't have the original object.
So, here Varnish will strip the conditional headers, retrieve the full object, then realize that it can send a 304 to the client. Meaning it will have downloaded the body from the backend to not send it. Pragmatically, that means that if:
Then Hit-for-Miss really isn't optimal as it will cause some extra backend traffic.
With HfP still being useful, it was reintroduced in 5.1, but with a slightly different syntax:
sub vcl_backend_response {
if (beresp.http.cache-control ~ "private") {
return (pass(1h));
}
}
This one triggers the "true" Hit-for-Pass of yore that becomes useful in the scenario presented above. This being said: you most probably don't need it.
Hit-for-Pass was effectively replaced by Hit-for-Miss because in the vast majority of cases Hit-for-Miss will have the upper hand. Also, remember that both HfP and HfM are basically "catch up" mechanisms to help you walk back a cacheability statement, and you won't need them if you can classify a request from the get-go in vcl_recv.
This post was a bit more abstract and arcane than my usual, but I hope you enjoyed it nonetheless. You are now an expert on all things Hit-for-Miss and Hit-for-Pass, use this knowledge wisely!