Kristian Lyngstøl's Blog

Caching popular content longer

Posted on 2012-06-19

The following VCL demonstrates how you can tell Varnish to cache popular content longer than less popular content. Discussion follows.

sub vcl_hit {
        if (obj.hits == 500) {
                set obj.ttl = 10w;
        }
}

sub vcl_fetch {
        set beresp.ttl = 20m;
}

In this example all content starts out with a 20 minute TTL (set beresp.ttl = 20m;). If an object is hit exactly 500 times (if (obj.hits == 500) { , we increase the ttl to 10 weeks (set obj.ttl = 10w;).

Warning

You have to use obj.hits == 500, not obj.hits > 500. If you use the latter, you will increase the TTL every time an object is hit once it's been hit 500 times.

This is one of the snippets of VCL I'm quite proud of. Unlike the evil backend hack (https://www.varnish-software.com/static/book/Saving_a_request.html#example-evil-backend-hack) which is also quite clever, this ttl-adjustment is far less hacky.

This allows you to let less requested content expire faster to get more out of a full cache. There's nothing wrong with using this in multiple tiers either:

sub vcl_hit {
        if (obj.hits == 500) {
                set obj.ttl = 3h;
        } elsif (obj.hits == 10000) {
                set obj.ttl = 2d;
        } elsif (obj.hits == 1000000) {
                set obj.ttl = 4w;
        }
}

sub vcl_fetch {
        set beresp.ttl = 20m;
}

I'm not convinced the 4 week part is useful here, but that's up to you to decide.

Of course, setting the ttl to 20 minutes by hand in vcl_fetch is optional. You could just rely on the default mechanisms Varnish offer ( https://www.varnish-software.com/static/book/VCL_Basics.html#the-initial-value-of-beresp-ttl).

These are the things to keep in mind:

  • If you run out of cache, varnish will throw out the content that has been inactive for the longest period of time, not necessarily the oldest content.
  • If your cache is full and you have mixed object sizes, Varnish' LRU logic can struggle. If it's not too hard to avoid, you want to avoid LRU nuking.
  • Finding the right numbers (500 hits? 100 hits? 20 hits? 10000 hits?) will depend heavily on your own setup. Experiment.
  • varnishlog -O -i TxHeader -I X-Hits: 500 combined with sub vcl_deliver { if (obj.hits > 0) { set resp.http.X-Hits = obj.hits; } } will output a line every time an object has been hit 500 times (well, technically it'll output once you've /sent/ an object that has been hit 500 times).
  • 20 minutes is an example. Experiment.

Last, I'd just like to say that this is a solution I came up with for a customer, pretty much on the spot. We had never thought about it until the customer asked, but VCL made it possible and we know how to use it. What I'm trying to say is: Buy our stuff! ;) We are Varnish Software: https://www.varnish-software.com. Really though, if you have a Varnish-question, we can probably help you out.