Cloudflare 利用 Zstandard 与 Pingora
How we could save petabytes of cache storage with Zstandard and Pingora
Memory costs are increasing dramatically. Both RAM and hard disk drive prices have exploded over the past year. At Cloudflare, we run several massively distributed storage products (including our famous CDN) that rely on making efficient use of the memory we have deployed so we can continue to serve all of our customers.
内存成本正在急剧上升。在过去一年中,RAM 和硬盘驱动器的价格都大幅飙升。在 Cloudflare,我们运行着几个大规模分布式存储产品(包括我们著名的 CDN),这些产品依赖于高效利用已部署的内存,以便我们能够继续为所有客户提供服务。
With this in mind, we prototyped a way to expand effective cache capacity. By encoding eligible assets with Zstandard inside Pingora, the architecture trades a minor CPU increase for significant storage and cross-data center bandwidth savings.
考虑到这一点,我们原型化了一种扩展有效缓存容量的方法。通过在 Pingora 内部使用 Zstandard 对符合条件的资产进行编码,该架构以轻微的 CPU 增加为代价,换取了显著的存储和跨数据中心带宽节省。
We have been prototyping a system called Cache Transcoding, which I built during my internship at Cloudflare as part of the 1.1.1.1 Intern Program. When an eligible response enters the cache, we encode it using Zstandard, or zstd, before writing it to disk. We keep that compressed form while the asset lives in the cache and moves between data centers via Tiered Cache, then decode it before serving the response to the client.
我们一直在原型化一个名为 Cache Transcoding 的系统,这是我作为 1.1.1.1 Intern Program 实习生在 Cloudflare 实习期间开发的。当符合条件的响应进入缓存时,我们在将其写入磁盘之前使用 Zstandard(或 zstd)对其进行编码。在资产驻留于缓存并通过 Tiered Cache 在不同数据中心之间移动期间,我们保持其压缩形式,然后在向客户端提供响应之前对其进行解码。
In our initial testing, this encoding shrunk eligible assets to ⅓ of their original on-disk size on average. The estimated extra CPU cost in our origin-facing proxy was small, but that is the trade. A small increase in CPU gives Cloudflare petabytes of effective cache capacity and reduces the data transferred between our data centers. The encoding cost is paid once when an asset enters the cache. The storage and bandwidth savings continue every single time that asset is reused.
在我们的初步测试中,这种编码将符合条件的资产平均缩小到原始磁盘大小的三分之一。在我们面向源站的代理中,估计的额外 CPU 成本很小,但这就是权衡之处。CPU 的微小增加为 Cloudflare 带来了 PB 级的有效缓存容量,并减少了我们数据中心之间的数据传输量。编码成本仅在资产进入缓存时支付一次。而每当该资产被重复使用时,存储和带宽节省就会持续发生。
What is Zstandard?
什么是 Zstandard?
Zstandard, or zstd, is a lossless compression algorithm developed by Yann Collet at Facebook and open sourced in 2016. Lossless means that after compressed data is decoded, every byte is identical to the original. We can change how an asset is represented on disk without changing the asset itself.
Zstandard(或 zstd)是由 Yann Collet 在 Facebook 开发的一种无损压缩算法,并于 2016 年开源。无损意味着在压缩数据被解码后,每个字节都与原始数据完全相同。我们可以改变资产在磁盘上的表示方式,而不改变资产本身。
Zstd is designed to balance compression ratio with speed. In our earlier browser compression testing, it compressed data 42% faster than Brotli while producing nearly the same file size, and produced files 11.3% smaller than gzip at a comparable speed. That balance matters because Cache Transcoding would touch a large amount of traffic, so both encoding and decoding need to stay fast.
Zstd 旨在平衡压缩率与速度。在我们早期的浏览器压缩测试中,它的压缩速度比 Brotli 快 42%,同时产生的文件大小几乎相同;并且在与 gzip 相当的速度下,生成的文件比 gzip 小 11.3%。这种平衡很重要,因为 Cache Transcoding 会处理大量流量,因此编码和解码都需要保持快速。
The prototype uses zstd level 3, giving us most of the compression benefit without turning cache fills into a CPU bottleneck.
该原型使用 zstd level 3,这为我们提供了大部分压缩效益,同时不会使缓存填充成为 CPU 瓶颈。
Cloudflare traditionally stores an asset using the content encoding supplied by its origin. If an origin sends an uncompressed response, we store those uncompressed bytes on disk and transfer them between data centers in the same form. Cache Transcoding adds compression inside the cache itself.
Cloudflare 传统上会使用源站提供的内容编码来存储资产。如果源站发送未压缩的响应,我们会将这些未压缩的字节存储在磁盘上,并以相同的形式在数据中心之间传输。Cache Transcoding(缓存转码)功能则在缓存内部进行压缩。
Not everything is worth compressing
并非所有内容都值得压缩
Transcoding does not mean compressing everything. Images, video, and fonts are usually compressed already. In our traffic sample, this media slice represented 21.4% of requests but 63.3% of bytes. Compressing it again would burn CPU for nothing.
转码并不意味着压缩所有内容。图像、视频和字体通常已经过压缩。在我们的流量样本中,这类媒体数据占请求量的 21.4%,但占字节量的 63.3%。再次压缩它们只会白白消耗 CPU 资源。
Compressible text is different. HTML, JSON, CSS, and JavaScript represented 67.3% of requests and 22.3% of bytes. Within that text slice, approximately 71% arrived uncompressed with Content-Encoding unset and it compresses well.
可压缩的文本则不同。HTML、JSON、CSS 和 JavaScript 占请求量的 67.3%,占字节量的 22.3%。在这些文本数据中,约有 71% 是以未压缩形式到达的(Content-Encoding 未设置),且具有良好的压缩效果。
In our controlled test corpus, the eligible assets compressed by roughly 2.8 times.
在我们的受控测试集中,符合条件的资产大约被压缩了 2.8 倍。
Encoding is more expensive per byte, but assets are served far more often than they are filled.
虽然每个字节的编码成本更高,但资产的提供服务次数远多于其填充(写入)次数。
By changing how assets are represented, existing hardware could store more customer content.
通过改变资产的表示方式,现有硬件可以存储更多的客户内容。
Fewer bytes on disk mean each server can retain more objects. This increases cache density and reduces the likelihood that useful content is evicted because an uncompressed representation consumed more space than necessary.
磁盘上的字节数更少意味着每台服务器可以保留更多的对象。这提高了缓存密度,并降低了因未压缩表示法占用过多空间而导致有用内容被驱逐的可能性。
The smaller representation also helps as an asset moves through Tiered Cache because it reduces the data transferred between Cloudflare data centers, making backbone usage more efficient.
较小的表示形式还有助于资产在分层缓存(Tiered Cache)中的流转,因为它减少了 Cloudflare 数据中心之间传输的数据量,从而提高了骨干网的使用效率。
Paying the compression cost once
一次性支付压缩成本
Compression is never free. Encoding and decoding both use CPU, so the important question is whether the byte savings are worth the processing cost.
压缩从来都不是免费的。编码和解码都会消耗 CPU,因此关键问题在于节省的字节量是否值得为此付出处理成本。
At zstd level 3 (often the default balance of speed and compression size output), our model kept the extra CPU cost to a few percent under the traffic and reuse assumptions we tested.
在 zstd level 3(通常是速度与压缩输出大小之间的默认平衡点)下,我们的模型在我们测试的流量和复用假设条件下,将额外的 CPU 成本控制在百分之几以内。
We initially considered limiting transcoding to popular content, since hot assets are reused more, but it did not help. Decoding happens every time an asset is served, so limiting the feature to only the hottest content reduced the storage saving without cutting CPU by the same amount.
我们最初考虑将转码限制为热门内容,因为热资产会被更频繁地复用,但这并没有带来帮助。每次提供资产时都会发生解码,因此仅将该功能限制在最热的内容上,会在减少存储空间节省的同时,未能按同等比例降低 CPU 消耗。
The simpler policy performed better. Transcoding all eligible compressible text at or above 4 kibibytes (KiB) captured nearly all of the measured storage benefit, while remaining within the CPU budget.
更简单的策略表现更好。对所有大于或等于 4 kibibytes (KiB) 的符合条件的可压缩文本进行转码,几乎捕获了所有测量的存储收益,同时保持在 CPU 预算范围内。
How Cache Transcoding works
Cache Transcoding 的工作原理
On a cache miss, our Pingora-based proxy encodes the body using zstd before writing it to disk. The cache metadata records that the stored representation is compressed and preserves the original content length. Before the response leaves the proxy, the body is decoded back to its original identity representation.
在缓存未命中时,我们基于 Pingora 的代理会在将主体写入磁盘之前使用 zstd 对其进行编码。缓存元数据会记录存储的表示形式已压缩,并保留原始内容长度。在响应离开代理之前,主体会被解码回其原始的标识表示形式。
On a cache hit, the stored zstd object is read from disk and decoded. With Tiered Cache, the compressed representation is transferred from the upper tier to the lower tier in the compressed form. Decoding only happens on the client-facing hop.
在缓存命中时,从磁盘读取存储的 zstd 对象并进行解码。通过分层缓存(Tiered Cache),压缩表示形式以压缩形式从上 tier 传输到下 tier。仅在面向客户端的跃点上发生解码。
On a full cache miss, the upper tier fetches identity bytes from the origin. Those bytes are encoded once, stored as zstd, and transferred to the lower tier in their compressed form. The lower tier also stores the zstd representation, then decodes it for the request path.
在完全缓存未命中时,上 tier 从源站获取标识字节。这些字节被编码一次,以 zstd 形式存储,并以压缩形式传输到下 tier。下 tier 也存储 zstd 表示形式,然后为请求路径对其进行解码。
If the lower tier misses but the upper tier already has the object, the origin is not involved. The compressed object moves directly between the cache tiers. It remains compressed on the wire and on disk, then is decoded once at the lower tier.
如果下 tier 未命中但上 tier 已有该对象,则不涉及源站。压缩对象直接在缓存层之间移动。它在网络传输和磁盘上保持压缩状态,然后在低层解码一次。
If the lower tier already has the object, no network transfer or encoding is needed. The lower tier reads the zstd bytes from disk, decodes them, and passes the original asset onward.
如果下 tier 已有该对象,则无需进行网络传输或编码。下 tier 从磁盘读取 zstd 字节,对其进行解码,并将原始资源传递下去。
The storage encoding marker prevents an object from being encoded more than once. A cache layer receiving an object from another tier can see that it is already stored using zstd, and preserve it in that form.
存储编码标记可防止对象被多次编码。从其他层接收对象的缓存层可以看到它已经使用 zstd 存储,并将其保持在该形式中。
Why we only transcode certain text
为什么我们仅对某些文本进行转码
The fastest compression operation is the one we do not need to perform. Cache Transcoding therefore uses a series of eligibility checks to avoid content that is unlikely to benefit.
最快的压缩操作是我们不需要执行的操作。因此,缓存转码使用一系列资格检查来避免不太可能受益的内容。
The prototype only transcodes a 200 OK response when Content-Encoding is unset, the Content-Type is compressible text, and the response has a known Content-Length of at least 4 KiB. Slice subrequests, responses using active upstream compression, range requests, precompressed responses, unknown length bodies, and binary content remain unchanged.
原型仅在 Content-Encoding 未设置、Content-Type 为可压缩文本且响应具有至少 4 KiB 的已知 Content-Length 时对 200 OK 响应进行转码。切片子请求、使用主动上游压缩的响应、范围请求、预压缩响应、未知长度的主体以及二进制内容保持不变。
The 4 KiB threshold removed a large number of tiny requests while leaving out only about 1% of the otherwise eligible bytes. Lowering it would add per-object overhead without saving much more storage.
4 KiB 的阈值消除了大量微小请求,而仅排除了约 1% 的其他合格字节。降低该阈值会增加每个对象的开销,而无法节省更多存储空间。
The threshold and zstd level are both parameters rather than permanent limits. We started with zstd level 3 and a 4 KiB minimum because they gave us a conservative way to measure the architecture. With the initial CPU budget understood, we can test whether higher compression levels improve the ratio enough to justify their additional cost.
阈值和 zstd 级别都是参数,而非永久限制。我们最初采用 zstd level 3 和 4 KiB 的最小值,因为它们为我们提供了一种保守的方式来衡量架构。在初步了解了 CPU 预算后,我们可以测试更高的压缩级别是否足以通过改善压缩比来证明其额外成本的合理性。
Testing over one million requests through the cache
对超过一百万次请求进行缓存测试
We exercised the prototype against a controlled test zone and correlated each request across request logs, Prometheus metrics, and Jaeger traces.
我们在受控测试区域对原型进行了演练,并将每个请求与请求日志、Prometheus 指标和 Jaeger 追踪记录进行了关联分析。
The correctness campaign covered cache misses, cache hits, single-hop fills, Tiered Cache fills, and more. We varied cache keys to make each request follow a specific path and used traces to confirm where encoding and decoding occurred.
正确性验证涵盖了缓存未命中、缓存命中、单跳填充(single-hop fills)、分层缓存填充(Tiered Cache fills)等场景。我们通过改变缓存键使每个请求遵循特定路径,并利用追踪记录确认编码和解码发生的位置。
更进一步:量化金融体系
看懂新闻只是起点——沿量化金融路径,把它变成能交付的工程能力