December 4, 2020
5 min read time

The difference between Hit-for-Miss and Hit-for-Pass

The difference between Hit-for-Miss and Hit-for-Pass

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.

20200103000219-photo-1534665482403-a909d0d97c673

Been there, done that

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.

Same code, different results

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:

  1. the page requested was a temporary version, so you mark it as uncacheable for an hour, so the new page is made available as soon as it comes online.
  2. five minutes later, the very cacheable and final version of the page is pushed on the backend.
  3. Sadly, for the next fifty-five minutes, you only get passes to this page, and you are unable to cache it, hammering the backend needlessly.

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.

Pass bad, miss good

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.

Miss bad, pass good

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:

  • pass: Varnish wants to get what you asked from the backend
  • miss: Varnish wants to get something cacheable into its cache

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:

  • the uncacheable object is large
  • and it's receiving a lot of user requests
  • and those user requests are conditional (i.e. Varnish won't send the response body back)
  • and the backend does support conditional request (i.e. Varnish could get away with not receiving the response body)

Then Hit-for-Miss really isn't optimal as it will cause some extra backend traffic.

Solve it, offer both

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.

All done, we are

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!

 

reach_people_faster