A tömörítési defaultok ellened dolgoznak: gzip, brotli és zstd, megmérve

A böngésződ 2024 óta minden kéréssel felajánlja a zstd tömörítést, idén februártól már a Safari is érti. Közben az nginx 1-es szinten gzipel, a Node brotlija alapból maximumon ég, a Cloudflare-nél pedig az Enterprise csomaghoz jár a gzip. Megmértem, mit tudnak a tömörítők – a defaultok nem a barátaid.

Lekértem curl-lel a saját blogom főoldalát, és a válaszban egy olyan fejléc jött vissza, amit sosem állítottam be: content-encoding: zstd. Rövid nyomozás után kiderült, hogy a Cloudflare ingyenes csomagja már alapból Zstandarddal tömörít. A poén az árlista túlsó végén van: a Pro és a Business ügyfelek brotlit kapnak alapértelmezésként, az Enterprise pedig – komolyan – gzipet. Minél többet fizetsz, annál patinásabb az algoritmus.

Ez elég jó ürügy volt, hogy megnézzem, hol tart a webes válasz-tömörítés 2026-ban. A rövid verzió: a zstd idén februárban vált ténylegesen univerzálissá a böngészőkben, a defaultok pedig – az nginx-től a Node-on át a CDN-ekig – szinte mindenhol mást csinálnak, mint amire számítanál.

Illusztráció három tömörítési sávval: a gzip, a brotli és a zstd különböző méretűre tömöríti ugyanazt az adatblokkot, eltérő hosszúságú sebességjelző csíkokkal

A harmadik tömörítő

A Zstandardot a Meta fejlesztette (Yann Collet munkája, akit az lz4-ért is szerethetsz), és előbb a saját infrastruktúrájukban terítették szét mindenhová; az RFC 8878 2021-ben szabványosította. A webre viszont csak 2024-ben ért oda: a Chrome 123 2024 márciusában, a Firefox 126 két hónappal később kapcsolta be a Content-Encoding: zstd támogatását. A sort a WebKit zárta: a Safari 26.3 2026 februárjában – azzal a tipikus Apple-lábjegyzettel, hogy csak iOS 26.3-on és macOS Tahoe-n; a régebbi macOS-eken futó Safari kimarad.

A böngésződ tehát jó eséllyel ezt küldi minden egyes kéréssel: Accept-Encoding: gzip, deflate, br, zstd. Hogy érkezik-e rá zstd válasz, az már a szerveren múlik. A Meta értelemszerűen a saját főztjét eszi:

$ curl -sI -H 'Accept-Encoding: zstd' https://www.facebook.com/ | grep -i content-encoding
content-encoding: zstd

A defaultok, amikkel valójában élsz

Mielőtt mérnénk, érdemes tisztázni, mit csinálnak az eszközeid, ha nem szólsz rájuk. Az nginx gzip_comp_level defaultja nem a zlib-féle 6, hanem 1 – a lehető leggyengébb arány. A Node ezzel szemben a másik irányba lő túl: a zlib.brotliCompressSync() alapértelmezett minősége a BROTLI_DEFAULT_QUALITY, ami történetesen 11, vagyis a maximum. A zstd defaultja a szolid 3.

$ node -e "const z = require('node:zlib').constants; \
  console.log(z.BROTLI_DEFAULT_QUALITY, z.ZSTD_CLEVEL_DEFAULT)"
11 3

Az Express világában népszerű compression middleware szerzői pontosan tudják, mit jelent ez: a kód kézzel állítja 4-re a brotli minőségét, egy sokatmondó kommenttel („set the default level to a reasonable value with balanced speed/ratio”). Ha viszont valaha írtál saját brotli-tömörítést Node-ban, és a defaultokra hagyatkoztál, akkor kérésidőben, maximális minőségen tömörítettél. Mindjárt látszik, az mennyire fáj.

Megmértem

Node 22.22.3, natív node:zlib, két valós payload: ennek a blognak a főoldala (103 KiB HTML) és a react-dom production bundle-je (503 KiB minifikált JS). Több futás mediánját mértem; a táblázat a react-dom számait mutatja:

kódolásméretaránytömörítéskicsomagolás
gzip -1 (nginx default)114,5 KiB22,8%2,9 ms0,88 ms
gzip -6 (zlib default)90,3 KiB17,9%9,6 ms0,81 ms
gzip -989,6 KiB17,8%38,0 ms0,79 ms
brotli 493,6 KiB18,6%4,8 ms0,81 ms
brotli 11 (Node default)73,2 KiB14,6%717 ms0,77 ms
zstd 3 (default)98,7 KiB19,6%2,1 ms0,51 ms
zstd 1283,0 KiB16,5%14,3 ms0,45 ms
zstd 1975,4 KiB15,0%153 ms0,46 ms

A brotli 11 sora a kedvencem: 717 ms CPU-idő egyetlen fél megás válaszért. Kérésenként. Ezt csinálja a Node defaultja, és ezért állítja át kézzel a compression middleware. A gzip -9 ugyanez kicsiben: négyszer annyi munka a -6-hoz képest 0,7 KiB nyereségért.

A statikus előtömörítés oldalán a brotli 11 továbbra is a méret királya, de a zstd 19 majdnem hozza ugyanazt az arányt ötödannyi időből. On-the-fly viszont a zstd 3 a mezőny legjobbja: az 500 kB-os bundle-ön gyorsabb volt, mint a gzip a legalacsonyabb szintjén, jobb aránnyal – és a kicsomagolása is 0,5 ms-ból megvan ott, ahol a gzipnek és a brotlinak 0,8 kell. A böngészőben ez mikroszekundumos tétel, de egy proxy vagy CDN, ami másodpercenként több ezer választ csomagol ki és újra, pontosan ezért szereti a zstd-t.

És egy részlet, amire nem számítottam: a brotli 4 a HTML-en megveri a gzip -6-ot (18,0% vs. 18,2%), a minifikált JS-en viszont kikap tőle (18,6% vs. 17,9%). Nem véletlen: a brotli magával hord egy 122 784 bájtos beépített szótárat (az RFC 7932 Appendix A-ja), amely webes szövegkorpuszból készült, és minden szavához 121 transzformációs alak tartozik. HTML-en ez alacsony szinten is sokat ad, minifikált kódon viszont alig – ott a brotli előnye csak a magas, drága szinteken jön vissza.

A 8 megás plafon

A zstd egyik nagy dobása az akár gigabájtos ablakméret – a weben ebből majdnem baleset lett. A Chromium 8 MiB-nál maximálta a dekódolási ablakot, és ha egy frame ennél többet kér, a letöltés ERR_ZSTD_WINDOW_SIZE_TOO_BIG hibával elszáll. A Kanidm felhasználói így kaptak üres oldalt a Chrome 123 után: a Rust-os tower-http régi verziója gond nélkül gyártott ekkora frame-eket, a projekt végül inkább kidobta a zstd-t. Azóta az RFC 9659 ezt szabállyá emelte: HTTP-ben az encoder nem adhat ki 8 MB-nál nagyobb ablakot igénylő frame-et. A szintekhez tartozó alapértelmezett ablakméretek beleférnek; a kézzel felcsavart windowLog és a CLI --long módja viszont pontosan ezt a hibát termeli.

Jó, de mivel szolgáljam ki?

Itt jön a hidegzuhany: az nginx-nek 2026-ban sincs hivatalos zstd modulja (közösségi létezik, ahogy a brotlihoz is külön modul kell). A Caddy natívan tudja az encode zstd gzip direktívával. Node-ban a 22.15.0 óta ott van a node:zlib-ben (zstdCompressSync és társai, egyelőre experimental jelöléssel). CDN-nél pedig jellemzően egy kapcsoló vagy compression rule az egész – ott tényleg nincs kifogás.

Hogy én mit csinálnék éles rendszerben? Ha CDN mögött ülsz, valószínűleg semmit: a tömörítés az edge dolga, és mint a saját blogom példája mutatja, már azelőtt zstd-t szolgálsz ki, hogy tudnál róla. Saját originnél statikus assetekre marad a build-time brotli 11 precompress gzip fallbackkel – a CPU-t ott egyszer fizeted meg, a méret meg a király –, és a zstd 19 akkor kerül képbe, ha a build ideje kezd fájni. Dinamikus válaszoknál (API, SSR HTML) viszont ma már a zstd 3 vagy a brotli 4 az értelmes default. Csak a Node-féle gyári brotlit ne: ha egy API-d hirtelen fél másodperces válaszidőket produkál, és valaki múlt héten „bekapcsolta a tömörítést”, most már tudod, hol kezdd a keresést. A gzip pedig marad, ami mindig is volt: a fallback, ami sosem hal meg – már csak az Enterprise-ügyfelek kedvéért sem.

Források

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük