fbpx

Varnish

Varnish and Brotli done right

by , , revisited on


We have by far the largest RPM repository with NGINX module packages and VMODs for Varnish. If you want to install NGINX, Varnish, and lots of useful performance/security software with smooth yum upgrades for production use, this is the repository for you.
Active subscription is required.

What is Brotli

Brotli is the brand new compression method for the Internet. And what’s great about it (besides being superior to Gzip) is that it was developed by Google. (For some people like me – not that great. How many things has Google abandoned? But let’s be positive since Brotli is open source)

Many website owners want to improve website performance by enabling Brotli compression.

Brotli already has wide browser support. Even Microsoft’s IE/Edge started supporting it as recently as this year.

Chrome has been supporting it since early days of Brotli library.

Brotli in Varnish

Varnish doesn’t really support Brotli encoding internally. But being agnostic to whatever goes through it, Varnish actually supports any encoding as long as you add the necessary VCL code.

Varnish software (Reza Naghibi) has posted an article on enabling Brotli with Varnish. However, it lacks some code for production use. The code doesn’t fully account for purging cached objects.

Normally when you clear cache, you want the cache for both gzipped and Brotli versions of the page to be cleared. However, the original code doesn’t account for this.

Let’s introduce the missing bits of VCL for purging. We use original logic and issue additional purge request in vcl_purge. That way we clear cache for both regular and brotli caches of the same page. Let’s go:

sub vcl_recv {
    if(req.http.Accept-Encoding ~ "br" && req.url !~
            "\.(jpg|png|gif|gz|mp3|mov|avi|mpg|mp4|swf|wmf)$") {
        set req.http.X-brotli = "true";
    }
}

# The data on which the hashing will take place
sub vcl_hash {
    if(req.http.X-brotli == "true" && req.http.X-brotli-unhash != "true") {
        hash_data("brotli");
    }
}

sub vcl_backend_fetch {
    if(bereq.http.X-brotli == "true") {
        set bereq.http.Accept-Encoding = "br";
        unset bereq.http.X-brotli;
    }
}

sub vcl_purge {
    # repeat purge for brotli or gzip object 
    # (force hash/no hash on "brotli" while doing another purge)
    # set Accept-Encoding: gzip so that we don't get brotli-encoded response upon purge
    if (req.url !~ "\.(jpg|png|gif|gz|mp3|mov|avi|mpg|mp4|swf|wmf)$" && 
            !req.http.X-brotli-unhash) {
        if (req.http.X-brotli == "true") {
            set req.http.X-brotli-unhash = "true";
            set req.http.Accept-Encoding = "gzip";
        } else {
            set req.http.X-brotli-unhash = "false";
            set req.http.Accept-Encoding = "br";
        }        
        return (restart);
    } 
}

So as you can see, we are doing a VCL trick to achieve our goal (purging cache for both brotli and regular cache).

If the purge request was done by a client that supports Brotli, we repeat purge request via “restart”. While we are doing that, we make sure that the second purge request will clear cache for the regular (gzip) cache entry.

In another case, when purge request is done by a client that doesn’t support Brotli, we are doing “restart” also. But we issue purge request for Brotli cache object of the page this time. The vcl_hash function is modified to account for our conditional purge logic.

We’re also doing a check against special variable req.http.X-brotli-unhash being empty so that we don’t repeat purge request more than once.

Well, that’s about it for production ready Brotli support.

We leave it to reader’s exercise to code VCL for Brotli using Vary approach instead. (That’s what we ended up doing for Citrus servers).

  1. Martin

    After adding this code to varnish config, it removes esi filtering what results in esi block not being handled by varnish and sent over http to client…
    How to change it?

    Reply
    • Danila Vershinin

      When ESI is taken into consideration, you would need to have Varnish see the main content as either gzip or in plain text.
      That is, for it to be able to parse the ESI tags.

      So as a trade-off to still support ESI, you can give up Brotli for the main HTML request.
      And depending on your preference do gzip on either backend or by Varnish itself.

      E.g.:

      sub vcl_backend_fetch {
          # unset accept-encoding for likely HTML request (no dot in URL),
          # so that the backend does not compress the response 
          if (bereq.url !~ "\.") {
              unset bereq.http.accept-encoding;
          }
      }
      
      sub vcl_backend_response {
          if (beresp.http.content-type ~ "text") {
              set beresp.do_esi = true;
              # do gzip on the Varnish side for likely HTML content
              set beresp.do_gzip = true;
          }
      
      Reply
      • Martin

        Thanks Danila for saving a lot of headache!
        My setup is: nginx (with brotli modules) at 443 as SSL termination -> Varnish at 6081 -> nginx vhost at 8080 -> and backend
        I have changed (added exclusion for main html doc.) sub vcl_backend_fetch as follows:

        sub vcl_backend_fetch {
        if(bereq.http.X-brotli == "true") {
        set bereq.http.Accept-Encoding = "br";
        unset bereq.http.X-brotli;
        }
        # unset accept-encoding for likely HTML request (no dot in URL),
        # so that the backend does not compress the response
        if (bereq.url !~ ".") {
        unset bereq.http.accept-encoding;
        }
        }

        And it works but to be 100% sure, I need more time for testing.

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

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