Cache Transcoding at Cloudflare: When a One-Time CPU Cost Pays Petabytes Back
Published on 2026-09-02
Cloudflare's Cache Transcoding prototype compressed eligible cache assets 2.834x using Zstandard inside Pingora. We unpack the encode-once/decode-many economics, the 4 KiB threshold that beat hot-only filtering, and the 1M-request validation across 10 cache servers.
Cache Transcoding at Cloudflare: When a One-Time CPU Cost Pays Petabytes Back
A Cloudflare intern's prototype just made the cache roughly three times denser. On September 1, 2026, Aashi Patel published a Cloudflare engineering blog post titled "How we could save petabytes of cache storage with Zstandard and Pingora"1. The headline number: in initial testing, eligible assets compress to about one-third of their original on-disk size — a 2.834x ratio, on average 1. That number is real, it is measured, and it is bounded.
The architecture is called Cache Transcoding. When an eligible response enters the cache, Cloudflare encodes it with Zstandard inside Pingora before writing to disk. The compressed form stays compressed while the asset moves between data centers through Tiered Cache. The body is decoded once, on the hop that faces the client 2. Encoding happens once per fill. Decoding happens once per serve. Storage and inter-data-center bandwidth savings compound every time that asset is reused.
This is an engineering case study, not a fleet-wide savings claim. The author is explicit: "a broader corpus is required before treating the measured compression ratio as a fleet-wide constant" 1. Read the number as a proof that the architecture works under the conditions they tested, not as a guarantee about every text object on the public Internet.
Why compressible text, not "everything"
Cloudflare did not try to compress the whole cache. They picked a slice. In their traffic sample, compressible text — HTML, JSON, CSS, and JavaScript — represented . Image, video, and font traffic represented the opposite shape: . The media slice is already compressed at the origin. Running Zstandard over it burns CPU and saves nothing.
Hot assets are not the same as compressible assets. Cloudflare's finding only holds for objects that arrive at the cache without a Content-Encoding header. Within the compressible text slice, about 71% arrived uncompressed with Content-Encoding unset3. Those are the objects Cache Transcoding touches. The other 29% already carry gzip or Brotli from upstream; transcoding them would waste CPU for zero extra savings.
That 71% inside 22.3% is the actual addressable slice 3. It is small by bytes but dense by request count — and request count is where CPU savings live.
The encode-once math
Compression is never free. The question is whether the byte savings are worth the processing cost. Cloudflare reports:
Encode: 4.31 ns per byte (about 232 MB/s), paid once per cache fill1
Decode: 1.56 ns per byte (about 641 MB/s), paid on every cache serve1
Encoding is roughly 2.76x more expensive per byte than decoding — and assets are served far more often than they are filled. A cache fill writes an object once. A cache serve reads it dozens, hundreds, or thousands of times before it gets evicted. Cloudflare's framing is direct: assets are served "far more often than they are filled" 1. That asymmetry is the entire economic case. Pay the encode cost once, collect the storage and bandwidth win on every subsequent serve.
The unit economics are also why Zstandard is the right algorithm choice. Zstandard was developed by Yann Collet at Facebook and open sourced in 2016 4. In Cloudflare's earlier browser-compression testing, Zstandard was 42% faster than Brotli while producing nearly the same file size, and produced files 11.3% smaller than gzip at a comparable speed 4. For Cache Transcoding, both encode and decode have to stay fast because the system touches a large slice of traffic. Brotli's compression ratio is good, but its encode speed at higher levels is too slow for a fill path. Gzip is fast enough but leaves 11.3% on the table 4. Zstandard sits in the sweet spot for a system that has to encode once and decode many times.
Why "hot-only" lost to "all eligible"
The intuitive policy is to compress only the most popular objects. Hot assets are served more often, so the bandwidth savings per CPU-second spent encoding should be highest there. Cloudflare tried this and it did not help.
The reason is mechanical. Decoding happens on every serve, not every fill. If you only encode hot objects, you cut the encode cost but you still pay the decode cost on every serve of every eligible object, hot or cold. You also forfeit the storage savings on cold objects that are perfectly compressible. Cloudflare's simpler policy transcodes all eligible compressible text at or above 4 KiB and let cache dynamics decide what to evict 5.
The 4 KiB threshold is not arbitrary. It cut out a large number of tiny requests while leaving out only about 1% of the otherwise eligible bytes5. Tiny objects have high per-object metadata overhead in the cache index; their bytes saved do not pay for that overhead. Below 4 KiB, the trade stops working. Above 4 KiB, the trade is favorable.
How Pingora keeps the bytes zstd across tiers
Cache Transcoding is implemented inside Pingora, Cloudflare's internal Rust-based proxy framework. On a cache miss, the Pingora-based proxy encodes the body with Zstandard before writing it to disk. The cache metadata records that the stored representation is zstd and preserves the original content length 2.
The interesting part is what happens on a Tiered Cache hit. The compressed representation is transferred from the upper tier to the lower tier in its compressed form. Decoding only happens on the client-facing hop 2. On a full cache miss where the lower tier does not have the object, the upper tier fetches identity bytes from the origin, encodes them once, stores them as zstd, and transfers them in that compressed form to the lower tier. The lower tier also stores the zstd representation, then decodes for the request path 2. If the lower tier misses but the upper tier already has the object, the origin is not contacted at all — the compressed object moves directly between cache tiers and stays compressed on the wire and on disk until it reaches the client hop 2.
The architectural payoff is that the compression cost is paid once when the asset enters the cache, and the storage and bandwidth savings compound every single time that asset is reused1. This is the design pattern that turns a one-time CPU expense into a multi-terabyte capacity expansion.
A storage encoding marker prevents double-encoding. A cache layer receiving an object from another tier can see that it is already stored using Zstandard and preserve it in that form 2.
The 1M-request validation
Cloudflare exercised the prototype against a controlled test zone. They correlated each request across request logs, Prometheus metrics, and Jaeger traces. The correctness campaign covered cache misses, cache hits, single-hop fills, and Tiered Cache fills, with cache keys varied to force each request down a specific path 1.
One performance campaign sent more than a million requests across 10 cache servers. Half ran with Tiered Cache disabled, the other half with it enabled — so the team could measure local cache behavior separately from transfers between cache tiers 6. The two test assets were approximately 195 KiB and 272 KiB, and both compressed by roughly 2.8 times6.
The team is explicit that this was a deliberately compressible test corpus 6. It gave them a clear signal for validating the architecture. It does not represent every text object on the Internet. The 2.834x ratio applies to the controlled test assets, not to a fleet-wide measurement. That distinction matters for anyone considering porting the architecture.
Why this is a case study, not a deployment
The Cache Transcoding prototype is one architectural experiment. Cloudflare's next steps are explicit: evaluate higher Zstandard levels, test a broader range of content types and object sizes, tune the eligibility criteria, examine range requests, examine pre-compressed origin responses, and study passing the compressed object directly to downstream components that already support it without decoding 1. None of this is in production yet.
What the experiment does prove is that the trade is favorable under the conditions they tested. Encoding once and decoding many times, with the asset compressed between cache tiers, fits inside the CPU budget while delivering petabytes of effective cache capacity 1. The architecture preserved content identity — every byte after decode is identical to the original — and did not require changes to the client-visible behavior of the CDN.
The interesting question is what happens at fleet scale on a corpus that includes things like pre-minified JavaScript bundles (already small), massive monolithic HTML pages (highly compressible), JSON APIs with already-compact integer encodings, and the long tail of misconfigured origins that ship uncompressed CSS that should have been Brotli'd years ago. The prototype measured two test objects at 2.8x 6. The real Internet is wider than that. Until Cloudflare publishes a broader measurement, the honest answer to "how much cache storage will this save" is: in the test, about two-thirds; in production, ask after the next quarter.
The engineering lesson is not "use Zstandard." The lesson is: when the encoding cost is paid once per fill and the savings accumulate across every serve, a moderate per-byte CPU cost becomes the cheapest byte in the system1.
Same source — Pingora-based proxy encodes on miss; compressed form travels through Tiered Cache; decode only on client-facing hop; storage encoding marker prevents double-encoding. ↩↩2↩3↩4↩5↩6
Same source — traffic sample composition: text 67.3% requests / 22.3% bytes; media 21.4% requests / 63.3% bytes; ~71% of text slice arrived uncompressed. ↩↩2↩3↩4
Same source + Facebook Zstandard GitHub — Yann Collet, open-sourced 2016; zstd 42% faster than Brotli, 11.3% smaller than gzip. ↩↩2↩3
Same source — 4 KiB eligibility threshold and zstd level 3 choice; threshold leaves out ~1% of otherwise eligible bytes. ↩↩2
Same source — 1M+ requests over 10 cache servers; assets 195 KiB and 272 KiB; both ~2.8x. ↩↩2↩3↩4