缓存系统的瓶颈不一定是命中率,也可能是磁盘空间。Cloudflare 最近介绍了一项名为 Cache Transcoding 的原型方案:对象进入磁盘缓存前,先对符合条件的内容进行压缩,读取时再解压返回。目标很直接,在不增加物理磁盘的情况下,让现有缓存承载更多有效内容。
这项技术主要面向原本没有压缩的文本内容,例如 HTML、JSON、CSS 和 JavaScript。Cloudflare 估计,若方案在更大范围内验证成功,可能带来 PB 级的额外有效缓存容量。不过目前仍处于原型和测试阶段,实际收益取决于内容类型、压缩比、CPU 消耗以及缓存访问模式。
缓存转码解决的是什么问题
传统缓存通常把源站响应直接写入磁盘。对于图片、视频等已经经过编码或压缩的数据,这种做法较为合理;但对于未压缩文本,磁盘中保存的内容可能远大于网络传输所需的体积。
例如,一份 1 MB 的 JSON 文件可能通过 gzip 或 Brotli 传输后只有几百 KB,但缓存层仍可能把 1 MB 原文写入磁盘。如果缓存规模达到数十亿个对象,原文和压缩内容之间的差额就会变成可观的存储成本。
Cache Transcoding 的基本路径可以概括为:
- 请求返回内容进入缓存流程。
- 系统判断对象是否属于适合压缩的类型。
- 对 HTML、JSON、CSS、JavaScript 等文本执行 Zstandard 压缩。
- 将压缩后的对象写入磁盘,并保存必要的元数据。
- 命中缓存时读取压缩对象,解压后提供给后续响应处理。
这里的关键点是“缓存内部格式”和“客户端响应格式”可以分离。缓存不必永久保存源站返回的原始字节,只要能够可靠地恢复响应内容以及相关元数据即可。
为什么选择 Zstandard
Zstandard,简称 zstd,是一种面向通用数据压缩的算法,通常在压缩率和解压速度之间提供较好的平衡。对于缓存系统而言,解压路径尤其重要,因为热点对象可能被频繁读取,解压延迟和 CPU 占用会直接影响请求处理能力。
与传统的磁盘压缩相比,面向缓存的转码还需要处理更多工程问题:
- 内容资格判断:已经压缩的 JPEG、PNG、MP4、ZIP 等对象通常没有必要再次压缩。
- 响应兼容性:压缩是缓存内部行为,不能错误地改变
Content-Encoding、Content-Length或范围请求语义。 - 版本与回滚:缓存格式变化后,旧缓存对象是否可读、如何失效,都需要明确策略。
- CPU 预算:压缩发生在写入路径,解压发生在读取路径,两者都需要纳入节点资源规划。
- 失败处理:压缩失败、元数据损坏或格式版本不兼容时,应能回退或丢弃对象,而不是影响正常请求。
因此,这并不是简单地给缓存目录执行一次压缩命令。它更接近一种带有对象格式、索引和生命周期管理的存储层改造。
可以怎样在本地验证收益
下面的命令可以用于一个小型实验。它假设当前目录中有待评估的文本文件,并且系统已经安装了 zstd。这不是 Cloudflare 的生产配置,而是帮助团队估算压缩率和 CPU 成本的可复制方法。
先创建测试文件并压缩:
# macOS: brew install zstd
# Debian/Ubuntu: sudo apt-get install zstd
mkdir -p cache-lab
cp ./response.json cache-lab/response.json
/usr/bin/time -f 'compress: %e sec, %M KB max RSS' \
zstd -q -3 --long=27 \
-o cache-lab/response.json.zst \
cache-lab/response.json
ls -lh cache-lab/response.json cache-lab/response.json.zst
检查压缩文件是否可以无损恢复:
zstd -q -d \
-o cache-lab/response.restored.json \
cache-lab/response.json.zst
cmp cache-lab/response.json cache-lab/response.restored.json
printf 'restore verification: OK\n'
还可以批量估算一组缓存对象的收益:
find ./cache-samples -type f \
\( -name '*.html' -o -name '*.json' -o -name '*.css' -o -name '*.js' \) \
-print0 | while IFS= read -r -d '' file; do
zstd -q -3 -c -- "$file" > "$file.zst"
done
du -sb ./cache-samples > /tmp/cache-original.bytes
find ./cache-samples -type f -name '*.zst' -print0 \
| xargs -0 du -cb \
| tail -n 1
实验时不要只记录压缩率,还应同时记录压缩和解压耗时、峰值内存、对象大小分布以及不同内容类型的结果。小文件尤其值得单独观察,因为压缩帧和索引元数据可能抵消部分收益。
落地时需要建立的边界
Cache Transcoding 的价值取决于缓存命中对象是否足够多、未压缩文本比例是否足够高,以及节点是否有可用 CPU。对于已经使用 Brotli、gzip 或媒体编码的内容,重复压缩往往收益有限,甚至可能增加处理成本。
一个稳妥的采用路径可以是:
- 只选择明确的文本 MIME 类型,并排除小对象。
- 在低压缩级别开始,优先控制延迟和 CPU。
- 为压缩前后大小、压缩耗时、解压耗时和缓存命中率增加指标。
- 采用版本化的缓存对象格式,允许逐步失效旧对象。
- 在故障时允许跳过压缩或直接删除无法读取的缓存对象。
- 对范围请求、条件请求、内容校验和缓存变体进行专项测试。
这项原型的启发并不是“所有缓存都应该压缩”,而是缓存层可以像数据库存储引擎一样,针对访问模式和数据形态选择内部表示。若压缩带来的有效容量提升能够覆盖额外 CPU 和实现复杂度,那么它可能成为扩展缓存规模的一种方式;在更广泛的测试完成前,仍应把它视为需要验证的工程方案,而不是已经普遍可用的特性。