Compression in Production: Choosing Between gzip, zstd, lz4, and Brotli Like It Matters
Compression is a three-way trade — ratio, speed, and CPU — that most systems set once by default and never revisit. The algorithm landscape, placement decisions across web, storage, and streaming, dictionary compression's quiet superpower, and the measurement discipline.
Compression settings are among the most-copied, least-examined defaults in infrastructure: the web tier gzips because the sample config did, Kafka runs whatever the producer default was, the data lake writes snappy-compressed Parquet because the framework chose, and nobody has measured any of it since. Meanwhile the stakes quietly compounded — egress bills priced per byte, storage tiers priced per GB-month, pipelines whose throughput is bounded by network, and CPU that's either abundant (making heavier compression free money) or the bottleneck (making it a self-inflicted wound). Compression is a placement-specific optimization with a three-axis trade — ratio, compression speed, decompression speed — and an afternoon of deliberate choice routinely beats years of defaults.
The landscape: four names cover almost everything
The modern shortlist collapsed nicely: gzip/zlib — the universal legacy: moderate ratio, moderate speed, supported by everything since the 90s; its remaining role is compatibility floor, because it wins on no other axis. zstd — the modern default, full stop: better ratios than gzip and several times faster at both ends, with a wide level dial (fast levels rivaling lz4, high levels approaching archival ratios) that makes it the one-algorithm answer for most storage, streaming, and internal-transfer jobs; its ecosystem support (Kafka, Parquet/ORC, HTTP via Content-Encoding: zstd in modern clients, package managers, filesystems) crossed the everywhere threshold years ago. lz4 — the speed specialist: modest ratios at gigabytes-per-second per core, for the paths where compression must be nearly invisible — in-memory caches, RocksDB-class storage engine internals, real-time streams where microseconds count. Brotli — the web specialist: best-in-class ratios for text at slow compression speeds, which is exactly right for precompressed static assets (compress once at build time at maximum level, serve a million times — the asymmetry that defines the web tier) and wrong for dynamic responses at high levels (the classic misconfiguration: Brotli-11 on the fly, burning CPU to save bytes the user never notices; dynamic content wants Brotli-4-ish or zstd). Everything else — snappy (lz4's approximate peer, everywhere in older big-data defaults), xz/lzma (max ratio, painful speed — cold archives only) — is a variation on these four's trade-offs.
The selection heuristic that covers 90% of cases: zstd unless you need extreme speed (lz4), maximum static-web ratio (Brotli at build time), or a legacy consumer (gzip).
Placement: the same trade, five different answers
The web tier splits by asset lifecycle: static assets precompressed at build (Brotli max + gzip fallback, served by content negotiation — CPU cost paid once, per the build-vs-serve asymmetry), dynamic API responses compressed on the fly at moderate levels (with a size floor — compressing 200-byte payloads wastes CPU and can grow them — and the security footnote: compression plus attacker-controlled-content plus secrets in the same response enabled the BREACH-class attacks; secrets and reflected input shouldn't share compressed responses). The measurement lens is the web-vitals one: bytes saved matter in proportion to the user's bandwidth, which is why mobile-heavy audiences repay compression attention most.
Streaming and messaging: compression batches beautifully — Kafka compresses per record-batch, so producer batching configuration is compression configuration (bigger batches, better ratios — the throughput tuning and the compression tuning are one knob wearing two names). zstd as default, lz4 where producer CPU is precious; the payoff is tripled — network, broker disk, and replication traffic all shrink together, which in cloud terms means the compression setting is a cross-AZ-traffic line item.
Storage and analytics: columnar formats (Parquet/ORC) compress per column-chunk, where encoding and compression interact — sorted, low-cardinality columns compress dramatically better, so data layout choices are compression choices (the sort-order decision in the lakehouse quietly moves storage bills by multiples). zstd has displaced snappy as the modern default here; higher levels for cold zones, faster levels for hot, per the tiering economics. For backups and archives, the calculus adds restore-time honesty: the maximum-ratio archive that takes hours to decompress is a recovery-time decision masquerading as a storage saving — the DR-rehearsal discipline includes timing the decompress.
Internals and caches: lz4-class speed for anything in the request path (cache values, storage-engine blocks, RPC payloads at high QPS), where the question isn't "does compression help" but "does it fit in the latency budget" — measured, not assumed, because memory-bandwidth-bound services can gain throughput from compression (smaller working set, better cache behavior) while CPU-bound ones lose it.
Dictionary compression: the underused multiplier
The quiet superpower for the small-payload world: compression algorithms learn redundancy within a payload, so small documents (the 2KB JSON event, the API response) compress poorly alone — the redundancy is across payloads, not within them. Dictionary compression (zstd's first-class feature) fixes exactly this: train a dictionary on a sample of representative payloads, share it between compressor and decompressor, and small-payload ratios improve dramatically (multiples, not percentages, on schema-repetitive JSON). The operational shape: dictionaries are versioned artifacts (train, distribute, reference by ID — schema-registry-adjacent governance, because a lost dictionary is unreadable data), retrained as payload shapes drift. Anywhere millions of small, similar payloads flow — event streams, cache stores, log shipping — dictionaries are the difference between "compression barely helps here" and "compression halved this bill."
The discipline: measure in your units
Compression benchmarks lie the way all benchmarks lie — on someone else's data. The evaluation that means something runs on your payloads at your sizes, and reports in decision units: ratio (bytes saved → egress/storage dollars, per the envelope math), compression and decompression throughput per core (→ CPU dollars and latency-budget fit), and the end-to-end effect (did p99 move? did pipeline throughput rise?). Revisit when the inputs change — payload shapes, traffic mix, instance pricing, or algorithm ecosystems (the zstd-everywhere shift stranded a generation of gzip decisions) — which in practice means a lightweight annual review of the estate's five biggest compression surfaces, ranked by bytes moved. It's the least glamorous optimization in this series and among the most reliable: the bytes are already flowing, the algorithms are free, and the difference between the default and the deliberate choice is usually a double-digit percentage of a bill somebody's paying.
Related
SRE Toil: What It Is, How to Measure It, and How to Reduce It
Toil is the manual, repetitive, automatable work that keeps systems running without actually improving them. SRE practice says: measure toil, set a ceiling, and invest engineering effort in eliminating it.
Async Python in Production: What asyncio Buys You, and What It Costs
A practitioner's guide to running asyncio-based services in production — when async actually helps, the blocking-call trap, structured concurrency with TaskGroups, and the operational habits that keep event loops healthy.
The Data Lakehouse: Why It Won, and How to Architect One That Works
Data warehouses couldn't handle unstructured data at scale. Data lakes couldn't enforce schemas or serve BI queries efficiently. The lakehouse combines both — here's the architecture, the open table formats that make it work, and the practical design decisions.
Working on this in production?
We do this work directly alongside engineering teams — architecture review, migration, and hands-on enablement.