Skip to content
Foxipa

Suggest an edit

Tell us what should be corrected, clarified, or improved. Submission will be connected to Foxipa’s contribution service later.

No information is sent anywhere yet. Your suggestion is only prepared in your browser.

Learning

15.5 Brotli, Measured

The config was Lesson 9.4's and the build was 15.2/15.3's — this lesson is the wire's turn to talk: 285 bytes became 50 while gzip managed 77, and the fine print (HTTPS-only browser br, CDNs, streaming) turns into runbook material.

Levels: Advanced ~15 min read

Advanced

What you will learn

  1. The receipts: one fixture, four Accept-Encoding offers — 285 bytes became 50 under br, 77 under gzip, 285 uncompressed when nobody asks.

  2. The HTTPS asterisk: browser br rides secure contexts only — what that does to your rollout bet, with Module 6's TLS termination as the prerequisite.

  3. The operational fine print: CDN re-compression, streaming/SSE and brotli_window, and the tiny-JSON cutoff where neither codec pays.

  4. The measurement plan: $gzip_ratio can't tell the brotli story — bytes-out per content-type, golden-diffed pre/post, module pinned per Lesson 15.3.


1. The server under test

The build and load are done — 15.2 compiled the .so pair, 15.3 made it binary-compatible, and 9.4 wrote the canonical config. What follows is the exact server that produced every number below:

nginx
server {
    listen      9701;
    root        www/main;

    brotli            on;
    brotli_comp_level 5;
    brotli_types      text/plain text/css application/json application/javascript image/svg+xml;

    gzip       on;                        # the dual stack
    gzip_types text/plain text/css application/json application/javascript image/svg+xml;
}

Receipt-honesty notes: the server above runs level 5 — inside 9.4's canonical 4–6 knee, one notch under the module's default 6 — with a fixture-short types list; production copies 9.4's block verbatim (level 6, the full list, brotli_static on).

2. Negotiation, on the wire

The receipt table below (real captures — one fixture, four clients):

text
Accept-Encoding: 'br'       -> br        50 bytes
Accept-Encoding: 'gzip'     -> gzip      77 bytes
Accept-Encoding: 'gzip, br' -> br        50 bytes
Accept-Encoding: ''         -> identity  285 bytes

The protocol reading is 9.4's routing table with bytes attached: no offer, no compression; either codec honors its own client; both offered → brotli. What the receipt adds is arithmetic — 50 vs 77 on the identical fixture is 35% under gzip, and 285 → 50 is 82% off the wire, the whole business case on one line. One honesty clause for the runbook: a 285-byte fixture is more repetitive than production payloads — budget 9.4's 15–25% band, then measure your own.

3. The operational fine print

Whether brotli is worth it is 9.4's decision table — unchanged. What the table can't carry is the fine print, and all of it is operational:

  • The HTTPS asterisk: browsers send Accept-Encoding: br only over secure contexts (the spec's restriction on the token) — plain-HTTP browser traffic negotiates gzip, and non-browser clients do as they please. Check your TLS share before betting the rollout; Module 6's termination is the prerequisite.

  • CDNs re-compress at the edge anyway — origin brotli matters most when YOU are the edge.

  • Streaming/SSE: dynamic brotli buffers like gzip did — mind brotli_window (default 512k; the directive accepts powers of two from 1k up to 16m — the format’s full ceiling, no pre-compression tricks needed), and consider identity for event streams.

  • The measurement gap: Module 12's log fields carry $gzip_ratio and nothing brotli-side — track bytes-out per content-type, golden-diffed pre/post rollout.

  • The module stays a pinned dependency — repo + commit ( a71f931) + paired rebuild per nginx bump. Lesson 15.3's rule, one runbook line.

Try it yourself

  • curl -s -o /dev/null -w '%{size_download} bytes\n' -H 'Accept-Encoding: br' http://127.0.0.1:9701/ — then re-run with 'gzip', 'gzip, br', and no header at all: the full negotiation table and its byte sizes are the expected result.

  • Feel the level curve: set brotli_comp_level 11, reload, re-measure the 50 bytes — did it move enough to justify the CPU? (On a 285-byte fixture: barely. On real assets: try www/main/compressible.txt.)

  • Pre-compressed assets: brotli -q 11 a CSS fixture to .br, drop it by the original, enable brotli_static on;, and watch the static module serve it with zero request-path CPU.

Q&A

Q: Does brotli help API JSON or just web assets? A: JSON compresses excellently (repetitive keys) — application/json in the types list is deliberate. For tiny JSON (<~1 KB, Module 9's min-length thinking), skip both codecs: per-response overhead beats the savings.

Q: HTTP/3 + brotli? A: Orthogonal stacks that compose: QUIC transport (Lesson 9.5) + brotli payload (this lesson) is the modern maximal shape. No special config needed beyond both being on.

Takeaways

  • 285 → 50 bytes under br, 77 under gzip, 285 uncompressed when nobody asks — the negotiation table is a receipt you ran, not a diagram from 9.4.

  • One home per story: 9.4 owns the config, 15.2/15.3 own the build — this lesson only spent the capability.

  • Browser br is HTTPS-context traffic — Module 6's TLS termination is the prerequisite; non-browser clients do as they please.

  • The fine print is operational: CDNs re-compress, streaming wants small windows or identity, $gzip_ratio won't tell the brotli story — golden-diff bytes-out, and pin the module (15.3's rule).

Was this page helpful?

Mark this learning resource complete to track your learning progress.