nginxbrotlingx-brotli

Nginx: cache Brotli compressed proxied upstream responses


I enabled Brotli compression in Nginx for a dynamically generated, but rarely changing resource.

My expectation was that when Nginx caches upstream responses, it will also cache the compression result. Thus, I assumed the CPU cost of enabling Brotli would be negligible. Instead, I see a performance impact, confirmed by perf top to be related to Brotli.

I verified that caching to the upstream server works. However, Nginx stores in its cache only the uncompressed upstream requests. Because of that, it will have to run the costly Brotli compression for each request. That is the problem.

There are sources (relating to gzip compression) recommending to compress either in upstream, or if that is not an option to create a second Nginx to proxy the request through, which takes the role of upstream and does the compression. Both solutions are not very elegant.

Is there a way to make Nginx cache not only the uncompressed upstream requests, but the result of the compression as well?

Maybe I am overlooking some. Here is a simplified config:

proxy_cache_path /var/cache/nginx levels=1 keys_zone=my_config_cache:8M
                 inactive=60m use_temp_path=off;

server {
  location = /foo {

    proxy_pass http://test-upstream;
    proxy_http_version 1.1;
    proxy_set_header Connection "";

    proxy_ignore_headers Expires;
    proxy_ignore_headers Cache-Control;

    brotli on;
    brotli_comp_level 11;

    proxy_cache my_config_cache;
    proxy_cache_valid 10s;
    proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

    expires 60s;
  }
}

Solution

  • brotli_comp_level 11;

    It is too high. The recommendation is 4 for dynamic content.

    You cannot do what you want with the current setup.

    If you can just set up your upstream to be brotli-capable instead, then you can cache compressed responses from it, by putting $http_accept_encoding as part of the cache key. That alone, however, will not be good enough, because you will have to normalize its value (think, all possible Accept-Encoding incoming headers, will result in a bloated and highly inefficient cache).

    If you really care about Brotli enabled clients (now that most of the browsers supporting Brotli anyway), and highest compression level possible, then you can enforce compression to the brotli-capable upstream by supplying Accept-Encoding: br while proxy-passing, which would result in caches always having brotli encoded response. (you don't need to adjust your cache key then). However, this requires a feature, currently unavailable, e.g. I call it unbrotli.

    The idea is that everything goes to upstream saying "I want Brotli encoded response". The upstream delivers Brotli-ed response (where applicable, of course, e.g. for text responses). But for clients that only support gzip or no compression at all, things should be dynamically uncompressed from Brotli (very low CPU impact). This isn't so great, but there is a declining number of Brotli incapable clients.