Hotdry.

Article

Bazel 内容定义分块远程缓存实战:参数配置与效果调优

BuildBuddy 在 Bazel 中引入内容定义分块(CDC),通过 Rabin fingerprint 变长切分文件使增量构建跳过未修改数据块,降低远程缓存失效率。

2026-05-16systems

在大型代码库的日常开发中,一次小幅源代码修改往往引发级联效应:链接器、打包器、归档工具将大量传递依赖合并为最终的二进制文件或安装包,导致远端缓存看到的是一个全新的摘要(digest),而实际上文件的大部分字节与前一版本完全相同。传统远端缓存只能将整个 Blob 重新上传和存储,即使其中仅有个别字节发生了变化。BuildBuddy 与 Bazel 团队合作推出的内容定义分块(Content-Defined Chunking,CDC)正是为解决这一痛点而设计。本文从原理出发,给出可在真实 CI 环境中落地的参数配置、监控指标与调优建议。

背景:传递动作与缓存失效的真实代价

Bazel 等现代构建系统已实现了「变化驱动构建」—— 仅当输入的摘要发生改变时才重新执行构建动作。然而在编译之后,链接、打包、归档等传递动作(transitive actions)接收的是所有依赖的累积输出,单次源代码修改可能让最终二进制文件的摘要完全改变,即使该二进制 99% 的字节与上一版本毫无差别。

以 Go 测试二进制为例:go_library 中的实现修改会触发 GoCompilePkg 重新编译,但下游的 GoLink 动作消费的是所有传递依赖的 .a 归档文件 —— 一个 .a 文件的改变会导致数十甚至数百个测试二进制的摘要全部更新。每个测试二进制在无 CDC 时均被当作独立完整 Blob 传输和存储,网络带宽与磁盘空间的浪费随着团队规模和 CI 并发数线性增长。

核心原理:Rabin Fingerprint 与 FastCDC

内容定义分块的核心思想极为直观:对文件内容运行滚动哈希(rolling hash),当哈希值匹配预设的稀有模式时在该位置切分为块。与固定偏移分块不同,内容定义分块保证「相同内容必定产生相同边界」这一确定性属性,从而实现跨版本复用。

具体而言,CDC 算法维护一个固定宽度的滑动窗口,逐字节推进并计算 Rabin fingerprint。当 fingerprint 匹配「约每 512 KiB 出现一次」的概率模式时,在该位置插入分边界。窗口内插入少量字节仅影响附近块;一旦滑动窗口越过变更区域并再次遇到未改变的字节序列,后续分块边界与之前完全一致。FastCDC(USENIX ATC 2016)在保证准确率的前提下将计算复杂度降至接近线性,是当前工业实现的主流算法。

BuildBuddy 采用的分块阈值目前为 2 MiB—— 仅对超过此大小的 Blob 启用 CDC 实测表明,在 BuildBuddy 自身的仓库中,约 4.2% 的对象超过此阈值,但这些对象贡献了大部分传输字节,因此收益高度集中。

端到端架构:SplitBlob / SpliceBlob 与 Bazel Combined Cache

CDC 在远程缓存场景中落地需要三方协同修改:

  • Remote Execution API:新增 SplitBlob(读路径)和 SpliceBlob(写路径)两个 RPC,分别用于查询给定摘要的块布局以及注册块的重组元数据。块本身以标准 CAS 条目存储,键为块摘要;重组元数据则以原 Blob 摘要为键单独存储。
  • Bazel:在 combined cache 层(即本地磁盘缓存与远端缓存协调层)实现 CDC 路径。当远端缓存通告支持分块时,Bazel 为大于阈值的 Blob 启用分块上传和下载。关键实现细节:Bazel 无需在内存中保留每个块的副本,而是直接从原始输出文件流式读取所需的字节范围上传。
  • BuildBuddy:服务端在 cache 写入和读取路径中验证块摘要一致性,并将缺失块并行传输;同时在 BuildBuddy Executor v2.261.0+ 中支持直接以块形式上传 action 输出,无需 executor 侧做完整副本。

可落地参数配置

在 Bazel 8.7 或 9.1+ 环境中启用 CDC 的最低配置仅需两步。将以下内容追加至项目根目录的 .bazelrc

common --experimental_remote_cache_chunking

若希望同时在下载路径获得本地复用收益(即本地磁盘缓存也参与分块去重),还应配置本地磁盘缓存:

common --disk_cache <本地缓存目录路径>
common --experimental_disk_cache_gc_max_age 3h

--experimental_disk_cache_gc_max_age 设置值建议低于远端缓存的 TTL 生命周期 —— 例如远端 TTL 为 7 天时设置为 3h1d,可确保本地磁盘缓存及时淘汰过期块而保留热点块。BuildBuddy 官方建议对于超过 2 MiB 的输出自动启用 CDC,无需额外的 executor 配置。

生产效果参考指标

根据 BuildBuddy 公开的生产数据(2026 年 4 月 changelog):

  • 在 BuildBuddy 自身仓库 50 次提交的 benchmark 中,上传数据量减少约 40%,磁盘缓存体积缩小约 40%。
  • 在生产环境的可分块对象(>2 MiB)中,约 85% 的写入字节通过 CDC 实现去重 —— 即大部分块在之前某次上传中已经存在。
  • 两周观测窗口内,CDC 累计跳过上传约 300 TiB 重复块数据,峰值小时节省超过 4 TiB(未包含下载路径节省)。
  • 按全部缓存流量加权,典型项目的端到端节省区间为 20%–40%。

压缩格式(如 .tar.gz、Docker 镜像层)因小输入变化导致压缩熵重新分布,分块复用率显著下降,建议按文件扩展名或 MIME 类型排除在 CDC 范围外。

监控与调优建议

启用 CDC 后,建议在 BuildBuddy 控制台或 Prometheus 端点关注以下指标:

指标名称 含义 调优参考
remote_cache_write_deduplication_ratio 写入字节中被去重的比例 低于 50% 需排查块大小阈值是否偏高
remote_cache_bytes_saved_per_hour 每小时因 CDC 跳过的上传字节数 峰值接近网络带宽上限时考虑扩展节点带宽
cache_chunk_count 当前存储的独立块总数 增长过快需检查 GC 策略是否有效
disk_cache_chunk_hit_rate 本地磁盘缓存中块的复用命中率 结合 --disk_cache 使用,低于 30% 可考虑扩大本地缓存配额

若发现预期节省未达 20%,优先排查:① 构建输出中大于 2 MiB 的文件占比是否确实较低(可用 bazel query 统计);② 网络是否已经成为主要瓶颈(CDC 节省的是传输字节而非延迟,RPC 并行度同样关键);③ 是否使用了压缩打包格式导致分块失效率上升。

资料来源

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com