Object lifetime: TTL, Grace, Keep

Introduction

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 cached content. Every copy of the content (aka object) stored in cache has a lifetime that 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:

    1. TTL - Time To live
    1. Grace
    1. Keep

An object lives in cache until TTL + Grace + Keep elapse, after which 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 TTLs, Grace and Keep

Setting the right lifetime durations is fundamental for a healthy cache, avoiding the waste of resources, such as cache storage and making sure users have a good quality of experience.

It’s important to remember that 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 to 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, 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, Varnish will apply its own default TTLs.


1. Set via VCL

The flexibility of VCL gives us the freedom to set, rewrite, adjust any header or lifetime we want.

The right place to set TTLs is the vcl_backend_response subroutine, which is the subroutine where the origin server response is processed by Varnish. It is where object 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 VoD
	# content is very unlikely to change. This allows us to cache
	# the most-requested content:

	if (beresp.url ~ "/vod") {
		set beresp.ttl = 30m;
		set beresp.grace = 24h;
	}

	# For live content we use a very small TTLs and an even smaller grace period
	# 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 use cases:

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;
        }
}

2. Set via CLI

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 runtime. Among these 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 two minutes and a Grace of ten seconds. These default values are supposed to work fine for most of the Varnish setups, but best practice is always to make sure you adjust the TTL durations to your consumers and business-specific needs.

The default values can be changed temporarily or persistently.

Temporarily via varnishadm

Before applying permanent changes, it’s advisable to test them and carefully roll them into production. Setting parameters using varnishadm is great for testing as you can set values that won’t survive through restarts.

varnishadm param.set <param> <value> will do the trick, i.e., varnishadm param.set default_ttl 1.000 will set the default TTL to one second.

Furthermore, using varnishadm param.reset <param> you can quickly set the parameters to their default values without having an impact on Varnish performance.

Persistently as startup parameters

Once you have completed your tests using varnishadm and you are satisfied with the results, you can think about setting the new TTL values as startup parameters. To do so the 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

3. Set via HTTP headers

As mentioned in the introduction, an 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

The Cache-Control header specifies directives that must be respected by all caching mechanisms. Cache-Control: public, max-age=36000 means the response can be cached by any cache and will be considered fresh for 36000 seconds Cache control has many directives that 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

The Etag, Last-Modified, If-Modified-Since, If-None-Match headers are conditional headers and must be used when sending a cache validation request. The scope of these headers is to indicate if a cache has the most up-to-date version of the cached content or if fresher content can be served by the origin server. For example, if a client sends 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 GMT

RFC: 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 explained in point 1.