Varnish

Implementing grace mode in Varnish 5.2

by , , revisited on


One of the advanced features of Varnish is grace mode. This allows it to serve stale cached objects while the backend is down. Let’s review the relevant bits of VCL you need to implement for grace mode work.

First of all, let’s setup the time for grace mode. This is grace time – a period, duration of time for when already expred objects should continue their presence in the cache:

sub vcl_backend_response {
  set beresp.grace = 1h;
}

Now to the actual grace logic:

sub vcl_hit {
    if (obj.ttl >= 0s) {
        // A pure unadulterated hit, deliver it
        return (deliver);
    }
    if (obj.ttl + obj.grace > 0s) {
        // Object is in grace, deliver it
        // Automatically triggers a background fetch
        return (deliver);
    }
    // fetch & deliver once we get the result
    return (fetch);
}

In the code above, we first check whether the cached object is still within its lifetime within the cache. If yes, we simply deliver it.

The rest of the code applies to a case when the object is already expired, but still kept in the cache thanks to our grace time configuration.

If the object is still within the grace period time, we deliver the cached, stale page. Otherwise, we fetch it from the backend.

Grace mode and PURGE

Now the grace mode works. However, what happens if you issue PURGE or BAN request against a page or a set of pages? Those objects will be gone immediately from the cache, and the grace mode will no longer apply to them.

To make sure that grace mode works for PURGE-ed object we need to purge them in a special, soft way. That is, we want to only reset their TTL to 0 while still keeping them in the cache.

Varnish 5.2 comes with purge vmod which exposes the soft purge functionality via, as you guessed it, purge.soft() method. So simply include the vmod at the top of your VCL file with import purge; then add your soft purge logic:

sub vcl_recv {
    if (req.method == "PURGE") {
        if (client.ip !~ purge_acl) {
            return (synth(405));
        }
        return (hash);
    }
}

sub my_purge {
    set req.http.purged = purge.soft();
    if (req.http.purged == "0") {
        return (synth(404));
    }
    else {
        return (synth(200));
    }
}

sub vcl_hit {
    if (req.method == "PURGE") {
        call my_purge;
    }
}

sub vcl_miss {
    if (req.method == "PURGE") {
        call my_purge;
    }
}

sub vcl_synth {
    if (req.method == "PURGE") {
        if (req.http.purged) {
            set resp.http.purged = req.http.purged;
        }
        return (deliver);
    } 
}

Now when your PURGE a cached page, it will still stay in cache for the time that you have defined with grace time.

Grace mode and BAN

What about pages that we have ban()-ed from Varnish cache? The Varnish bans will free objects from the cache immediately. Commonly, you use bans to invalidate the entire site cache. So how can you clear entire cache and benefit from grace mode?

There’s a great alternative to bans which is xkeys. Xkey comes as vmod from varnish-modules. Once you have installed it, here’s what you can do to clear entire site cache while still leveraging grace mode:

1. Mark all of the website pages using a key

First, choose the value for the key. The most straightforward would be to use website name:

sub vcl_backend_response {
  set beresp.http.xkey = "example.com";
}

You are advised to set the xkey header using your backend application. A simple header('xkey: example.com'); in plain PHP will allow to achieve the same effect.

2. Clear entire site using xkey’s softpurge function

First, implement the PURGE logic for xkeys:

sub vcl_recv {
    if (req.method == "PURGE") {
        if (client.ip !~ purge_acl) {
            return (synth(403, "Forbidden"));
        }

        set req.http.n-gone = xkey.softpurge(req.http.xkey-purge)

        return (synth(200, "Invalidated "+req.http.n-gone+" objects"));
    }
}

Now you can invalidate the entire site cache (while keeping the grace mode) by passing xkey-purge = example.com in the HTTP request:

curl -X PURGE -H "xkey-purge: example.com"

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.