If you landed on this page it is very likely that you are familiar with Varnish and you know it improves content delivery performance by storing a copy of your content in cache, and every request thereafter is fulfilled by the cached content. Every copy of the content (aka object) stored in cache has a lifetime which defines how long an object can be considered fresh, or live, within the cache.
The lifetime of a cached object is represented by the above timeline. The life of an object starts at the t_origin which is the time when the object was inserted in cache.
On top of this Timestamp we have three duration attributes:
An object lives in cache until TTL + Grace + Keep elapses, after that time the object is removed by the Varnish daemon. Objects within the TTL are considered fresh objects, while stale objects are those whose lifetime is between TTL and Grace, this is called grace period. Object lifetime between t_origin and keep is used for conditional requests using the HTTP Header field If-Modified-Since.
Setting the right lifetime durations is fundamental for a healthy cache, avoid the waste of resources such as cache storage and make sure the users have a great quality of experience.
This is how you can set TTLs, grace and keep:
TTL | Grace | Keep | |
---|---|---|---|
VCL | Yes | Yes | Yes |
CLI | Yes | Yes | Yes |
HTTP headers | Yes | Yes | Yes |
It’s important to remember a Varnish object is a local store of a HTTP response message, therefore TTL, Grace and Keep can be set via HTTP headers either by the origin servers or by Varnish itself.
Before diving in and understand lifetime settings using examples, let’s clarify how Varnish handles TTLs/Grace/Keep. Varnish, when fetching the content to be cached, will first check if any TTL-related header has been set by the origin server. If it has been set by the origin server, then Varnish will honor it, but we could still change it either via VCL or Varnish parameters if we need to apply a modification. If no TTLs have been set by the origin server, then Varnish will apply its own default TTLs.
Note: from here on when we refer to TTLs we will refer to one of the three lifetime durations, while we will use TTL (in the singular) version when we refer specifically to Time To Live.
The flexibility of VCL gives us the freedom to set, rewrite, adjust any header/lifetime we need/want.
The right place to set TTLs is the vcl_backend_response function, that’s the subroutine where the origin server response is processed by Varnish and that’s where objects attributes can be changed before the content is stored in cache. Once an object is in cache it can’t be modified anymore.
An example for video content delivery:
sub vcl_backend_response {
# We first set TTLs valid for most of the content we need to cache
set beresp.ttl = 10m;
set beresp.grace = 2h;
# We can now set specific TTLs based on the content we need to cache
# For VoD content we set a medium-long TTL and a long grace as the VoD
# content is very unlikely to change. This allow us to use the cache
# for the most-requested content
if (beresp.url ~ "/vod") {
set beresp.ttl = 30m;
set beresp.grace = 24h;
}
# For live content we use a very small TTL and a even smaller grace period
# that's because live content is no longer *live* once it is consumed
if (beresp.url ~ "/url") {
set beresp.ttl = 10s;
set beresp.grace = 2s;
}
# We expand the *keep* duration for IMS
if (bereq.http.If-Modified-Since) {
set beresp.keep = 10m;
}
}
An example for e-commerce and web&API:
sub vcl_backend_response {
# We first set TTLs for most of the content we need to cache
set beresp.ttl = 2h;
set beresp.grace = 24h;
# For static content we set a long TTL and grace as it is
# unlikely to change over time
if (beresp.url ~ "\.(png|gif|jpg|swf|css|js)$") {
set beresp.ttl = 1w;
set beresp.grace = 24h;
}
# Dynamic content can either change over time, or it can be personalized
# for each user, or, more in general, it can change under specific conditions
if (beresp.url ~ "/user_personalization") {
set beresp.ttl = 30m;
set beresp.grace = 10m;
}
if (beresp.url ~ "/time_based") {
set beresp.ttl = 30m;
set beresp.ttl = 10m;
set beresp.keep = 5m;
}
}
VCL is by far the most flexible method we have for setting these TTLs, but we can also rely on the CLI or on startup parameters to set default TTLs.
varnishadm param.show
returns the parameters the Varnish daemon is using at run time, among those you can find:
default_grace 10.000 [seconds] (default)
default_keep 0.000 [seconds] (default)
default_ttl 120.000 [seconds] (default)
If TTLs are not set by the origin server or defined via VCL, Varnish will apply the default values: a TTL of 2 minutes and a Grace of 10 seconds. These default values are supposed to work fine for most of the Varnish setups, but the best practice is always to make sure you adjust the TTLs durations to your consumers and business specific needs.
The default values can be changed temporarily or persistently.
varnishadm
:Before applying permanent changes, it’s advisable to test them and carefully roll them in production. Setting parameters using varnishadm is great for testing as you can set values which won’t survive over restarts.
varnishadm param.set <param> <value>
will do the trick, i.e. varnishadm param.set default_ttl 1.000
will set the default TTL(Time To Live) to 1 second.
Furthermore, using varnishadm param.reset <param>
you can quickly set the parameters to their default values without having an impact on
Varnish performances.
Once you have completed your tests using varnishadm
and you are satisfied with the results you can think of setting the new TTLs
values as startup parameters, to do so the unite varnish.service file must be edited as following:
sudo systemctl edit --full varnish.service
This will open the already existing varnish.service file allowing you to enter modifications for the service.
Below is an example, adding a default_ttl
of 1 second to the defaults:
[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd -a :6081 -f /etc/varnish/default.vcl -s malloc,256m -p default_ttl=1
Save the file and restart Varnish to apply permanent changes:
sudo systemctl restart varnish.service
As mentioned in the introduction, a HTTP caching strategy should start from the origin server(s), where the content is created, therefore it is really important to understand that we have a full set of HTTP headers we can use to define TTLs.
The most commonly used are:
Cache-Control
Expires
Age
Etag
Last-Modified
If-Modified-Since
If-None-Match
Cache-Control
header specifies directives which must be respected by all caching mechanisms. Cache-Control: public, max-age=36000
means the response can be cached by any cache and it will be considered fresh for 36000 seconds
Cache control has many directives which can be request or response specific. Check the RFC for more details.
RFC: https://tools.ietf.org/html/rfc7234#section-5.2
The Expires
header field gives the date/time after which the response is considered stale. For example Expires: Thu, 01 Dec 2020 16:00:00 GMT
The Age
header field conveys the sender’s estimate of the amount of time since the response was generated or successfully validated at the origin server.
RFC: https://tools.ietf.org/html/rfc7234#section-5.1
Etag
, Last-Modified
, If-Modified-Since
, If-None-Match
these are conditional headers and must be used when sending cache validation request.
The scope of those headers is to indicate if a cache has the most-up-to date version of the cached content or if fresher content can be server by the origin server.
In example, if a client send a GET or a HEAD request with a If-Modified-Since: Tue, 29 Oct 2019 19:43:31 GMT
, Varnish will revalidate the available content in cache
by sending a conditional request to the origin server to check if the request content has been modified after Sat, 29 Oct 2019 19:43:31 GMTRFC: https://tools.ietf.org/html/rfc7232#section-3
Setting HTTP headers at the origin server is advisable, but if somehow those headers are not defined when the content is created you can always rely on VCL as explain at point 1.