构建队列越来越长,开发机之间的 Bazel action 却经常互相命不中缓存。

最快解法:不要直接全量启用 Bazel 9 远程缓存,先用固定 Mac 基线池做只读试点;只有可复现、跨节点重复率足够且网络稳定时,才扩大缓存写入范围。

先用三个症状决定是否试点

这篇文章适合已经使用 Bazel 构建大型 iOS 工程、希望降低重复编译时间的研发效能负责人;也适合需要在新增 Mac 节点和共享缓存之间做预算取舍的企业 IT、FinOps 负责人。

如果你负责缓存写入权限、构建产物隔离或供应链安全,下面的权限矩阵和污染验证步骤也可以直接纳入评审材料。

<
现场症状优先动作暂缓动作
同一提交在固定 Mac 上重复构建,action 结果稳定,跨节点也有明显重复启用只读缓存试点,再验证受控写入不要直接让所有开发机写入
构建经常受环境变量、Xcode、SDK 或外部工具差异影响先固定工具链和执行环境暂不根据缓存命中率做采购结论
队列主要消耗在签名、归档、模拟器测试或发布验证优先测算 Mac 节点容量不要把远程缓存当成远程执行
缓存服务与 Mac 节点跨地域,下载耗时和失败重试明显先做同地域缓存对照不要仅凭“缓存可共享”判断值得部署
命中率不稳定,且出现异常产物或无法解释的 action 差异关闭写入、隔离节点、保留证据不要继续扩大缓存范围
Bazel 远程缓存复用的是 **action 的输入、ActionResult 和输出文件**,并不会让非 Mac 节点执行 Xcode、iOS Simulator、签名或归档任务。Bazel 官方文档说明,其 HTTP 缓存协议通过 PUTGET 传输数据,并区分 /ac/ 的 action result 与 /cas/ 的输出对象;这意味着缓存层和真正承担 Apple 工具链任务的 Mac 构建层必须分开核算。 [Bazel 远程缓存协议与对象路径说明](https://bazel.build/remote/caching)

你应先收集四类证据:重复构建比例、队列等待、干净构建频率、跨节点任务重合度。没有这些记录时,所谓“构建变快”很可能只是本地缓存、增量状态或某次提交规模变化造成的假象。

按可复现性检查 Bazel 9、rules_apple 与 Xcode

远程缓存最怕的不是没有缓存,而是不同节点生成相同 action key,却实际使用了不同工具链。Bazel 官方排查文档把非 Hermetic 构建、环境差异、命令行参数和不可缓存 action 都列为跨机器命中失败的重要排查方向;执行日志可用于比较两次运行的 action 是否真的一致。 Bazel 跨机器缓存命中排查文档

截至 2026 年 9 月 1 日rules_apple 仓库的版本说明列出 Bazel 9.x 的支持范围为 rules_apple 2.x 起至当前版本,但这是版本层面的支持信息,不等于你的 Xcode、SDK、macOS 和构建参数组合已经具备生产可用性。rules_swift 的仓库说明则列出 Bazel 9.x 的最低支持版本为 3.5.0,升级时仍需以对应 Release Notes 和团队验证结果为准。 rules_apple 支持矩阵 · rules_swift 支持矩阵与工具链说明

建议把兼容性拆成三层,而不是简单写成“支持 Bazel 9”:

<
组合层级可以写入生产基线吗验证重点
官方 Release Notes 明确支持的 Bazel、rules_apple、rules_swift 组合可以进入候选基线同一提交、同一目标、跨 Mac 重复执行
官方支持,但团队更换了 Xcode、SDK 或 macOS先进入验证池编译、链接、测试、归档和签名结果
开发分支、预发布版本或未合并修复只能隔离试验是否出现错误复用、action key 变化和产物差异
Apple 工具链本身还会限制你的基线设计。Apple 的系统要求页面显示,Xcode 27 beta 4 至少要求 macOS Tahoe 26.4;Xcode 26.6 对应 macOS Tahoe 26.2 至 26.x。你不能只锁定 Bazel 和 rules_apple,却让不同 Mac 自由升级 Xcode 或系统。 [Apple Xcode SDK 与系统要求](https://developer.apple.com/xcode/system-requirements/)

重点检查以下输入是否被稳定管理:

  • DEVELOPER_DIRPATHSDKROOTTOOLCHAINS 等环境变量;
  • Xcode、Command Line Tools、SDK 和 Swift 编译器版本;
  • action 使用的外部脚本、生成器、代码签名工具及其绝对路径;
  • CPU 架构、目标平台、最低部署版本和构建模式;
  • 是否存在构建过程中修改源文件、生成文件或依赖目录的行为。
rules_swift 官方文档还特别说明,macOS 节点默认使用 Xcode 提供的工具链,也可以通过 TOOLCHAINS 指定自定义 Swift 工具链。对企业 CI 来说,这意味着工具链选择必须进入节点镜像、启动检查和审计记录,而不能依赖某台 Mac 当前的用户环境。 [rules_swift 工具链配置说明](https://github.com/bazelbuild/rules_swift)

⚠️ 经验提醒:版本表只能回答“这两个版本理论上能协同工作”,不能回答“你的生产流水线能否安全复用缓存”。生产准入至少还要覆盖测试、归档、签名和失败恢复。

用命中、传输和失败率判断缓存收益

不要用单次构建总时长作为唯一指标。你需要把构建拆成远程命中、远程下载、远程上传、本地执行、排队等待和失败重试几个部分。

Bazel 的标准输出会区分 remote cache hit 与实际执行的 action;官方排查流程还建议先清理本地缓存,再重复同一构建,以免本地命中掩盖远程缓存效果。对于跨节点验证,则应比较执行日志中的 action 是否一致,再判断是 action key 不同、缓存读取未开启,还是缓存服务本身没有返回对象。 Bazel 命中率与执行日志排查方法

建议至少记录以下字段:

  1. 同一提交的 action 总数,以及远程命中 action 数;
  2. 命中对象下载耗时、未命中 action 执行耗时;
  3. 上传耗时、传输失败次数和重试次数;
  4. 缓存对象数量、增长速度、删除后的冷启动表现;
  5. CI 队列等待与 Mac 节点实际占用;
  6. 无缓存、只读缓存、读写缓存三组构建的产物摘要和测试结果。
网络位置要单独对照。缓存节点与 Mac 构建节点同地域时,传输路径较短,适合建立基线;跨地域时,缓存命中不一定等于更快,因为大对象下载、失败重试和并发竞争可能抵消 action 复用带来的收益;远程团队通过 VNC、SSH 或网页控制台接入远程 Mac 时,还要把交互延迟与 CI 数据传输分开看。MACGPU 的 [远程 Mac 方案入口](https://macgpu.com/zh/index.html) 可以作为短期基准节点或容量试验的资源入口,但不能替代你自己的日志验证。

按权限边界控制缓存写入与产物安全

企业不应默认允许所有开发机写入共享缓存。写入者一旦使用了未封版的 Xcode、错误的环境变量或被篡改的外部工具,就可能把不应复用的结果放入共享命名空间。

Bazel 官方文档指出,HTTP Basic Authentication 应配合 HTTPS 使用;用于对象存储的凭证如果具备读写权限,泄露后可能被用于读取或写入缓存数据。因此,缓存凭证应放在专用 CI 节点的安全存储中,不能通过普通开发机配置文件或公开日志传播。 Bazel 远程缓存认证与 HTTPS 说明

<
节点类型可执行任务缓存权限审计证据异常动作
固定 CI 写入节点标准编译、单元测试读写节点身份、工具链摘要、提交号立即撤销写入凭证并隔离
固定 CI 只读节点重复构建、验证、部分测试只读命中记录、产物摘要保留日志并复测
开发机本地开发与调试默认只读或禁用写入用户、分支、命令行发现差异时禁止继续复用
临时远程 Mac试点、峰值构建、环境验证只读,必要时短时授权租赁周期、节点、任务范围到期回收凭证并清理工作区
签名与发布节点归档、签名、发布按 Target 禁止或严格隔离签名身份、归档摘要、审批记录产物异常时停止发布链路
你还要区分“缓存可读”与“产物可发布”。即使某个 action 能从缓存返回,也不代表它可以直接进入 App Store Connect 发布流程。签名、归档、发布凭证和含敏感输入的 Target,最好使用隔离节点、独立命名空间和更严格的审计策略。

验证缓存没有污染产物时,按以下步骤执行:

  1. 固定提交、Bazel、rules_applerules_swift、Xcode、SDK 和构建参数;
  2. 在基线 Mac 上执行无缓存构建,保存执行日志、产物摘要和测试结果;
  3. 在第二台 Mac 上执行只读缓存构建,比较 action key、产物摘要和测试结果;
  4. 使用受控写入节点生成新结果,再由只读节点复用;
  5. 人为改变一个工具链输入,确认缓存不会错误返回旧产物;
  6. 发现输出差异时,停止写入,隔离异常节点,保留缓存访问日志和执行日志。

把缓存、Mac 节点与运营成本放进同一模型

远程缓存不是“增加一台服务器就能少买几台 Mac”。它只优化可复用 action,正式归档、签名、模拟器测试、设备连接、Xcode 工具链验证和未命中 action 仍然需要真实 Mac。

用以下变量核算,不要在没有企业日志的情况下预填节省比例:

  • Ccache:缓存服务、对象存储、备份和生命周期管理成本;
  • Cmac:Mac 构建节点采购或租赁成本;
  • Cops:工具链升级、凭证管理、故障处理和审计工时;
  • Cnet:缓存与 Mac 节点之间的传输成本;
  • Cqueue:排队造成的研发等待和发布延迟成本;
  • Cfail:缓存不可用、错误产物和节点重建带来的恢复成本。
<
方案能解决的问题不能解决的问题适合的证据条件
只增加 Mac 节点并行执行能力、签名、归档、模拟器任务重复编译仍会重复发生队列等待持续来自并发不足
只部署远程缓存复用可缓存且可复现的 actionXcode、模拟器、签名和物理设备任务跨节点命中稳定,传输失败可控
远程缓存+固定 Mac 基线池同时控制复用与工具链一致性不能消除所有峰值容量需求有明确的只读、写入和回退策略
远程缓存+弹性远程 Mac应对短期峰值和隔离试验长期重负载未必比自购更优峰值具有周期性,且需要快速扩容
如果你需要异地建立一组临时基准节点,可以参考 MACGPU 的 [M 系列远程 Mac 配置页面](https://macgpu.com/zh/m4-dinggou.html) 了解可用的远程访问形态;但具体节点、周期、地域和交付能力仍应以实际可用性与采购确认结果为准,不能把页面展示当成你的容量实测。

用 A/B 试点形成生产准入结论

试点不要按“部署前、部署中、上线后”的流水线叙事推进,而应围绕同一组指标完成三路对照:

  • A:无缓存。清理本地和远程缓存,固定 Mac 配置,记录完整构建、测试、归档和签名结果;
  • B:只读缓存。由经过验证的写入节点预热,测试节点只能读取,观察命中、下载、失败与产物一致性;
  • C:受控读写缓存。只开放给固定 CI 写入节点,开发机和临时远程 Mac 保持只读,比较缓存增长、污染风险和恢复动作。
每组测试都使用同一提交、同一目标和相同构建参数。不要只跑一个“最容易命中”的目标,也不要把本地增量构建和干净构建混在同一张结果表里。

条件式决策清单

  • 若跨节点 action key 一致、只读缓存的产物摘要与无缓存基线一致,且传输失败不会明显拉长队列,则进入受控写入试点;
  • 若命中率看似不错,但签名、归档或模拟器任务仍占据主要队列时间,则优先扩容 Mac,而不是继续扩大缓存;
  • 若相同 action key 产生不同输出,立即停止共享写入,隔离节点并修复工具链或环境输入;
  • 若缓存服务不可用时构建可以安全回退到本地执行,则保留缓存作为加速层;否则先补齐故障降级;
  • 若跨地域下载耗时高于未命中 action 的本地执行成本,则改为同地域缓存,或停止该地域部署;
  • rules_applerules_swift、Bazel 或 Xcode 升级后 action key 大面积变化,先完成新基线验证,再决定是否清理对应缓存命名空间;
  • 若试点期间排队主要来自缓存未覆盖的签名、归档、模拟器和设备任务,则把预算转向 Mac 节点容量。
你还应安排三项故障演练:缓存不可用时的回退、异常产物出现后的隔离与删除、Mac 节点重建后的工具链恢复。Bazel 9.1.0 的 Release Notes 已记录远程缓存相关的实验性能力,例如缓存对象被驱逐后的输入恢复和大对象分块传输;这类能力应视为需要单独验证的选项,不要因为版本发布说明出现了功能,就直接纳入生产承诺。 [Bazel 9.1.0 Release Notes](https://github.com/bazelbuild/bazel/releases)

最后再决定缓存投入还是 Mac 容量

如果你的当前方案是“每台开发机自行编译+少量固定 Mac 打包机”,真实缺点通常有三类:工具链容易漂移,重复 action 无法稳定复用;峰值提交时队列集中在有限 Mac 节点;开发机写入共享产物后,异常结果和敏感信息更难追溯。

如果你的当前方案是“直接购买更多 Mac”,缺点则是容量按峰值建设,低峰期利用率可能不足;节点升级、磁盘、证书和故障恢复都由团队承担;当需求只是短期试点或发布高峰时,长期采购会把临时容量变成固定资产。

因此,完成 A/B 试点后,先把峰值队列、签名任务、缓存未覆盖任务和恢复演练结果换算成真实 Mac 容量。若现有节点无法提供隔离试验或短期峰值资源,再评估按周期启用 MACGPU 的远程 Mac 作为基准节点或弹性构建池;在缺少命中和队列数据前,不建议直接长期采购缓存基础设施或一次性扩充 Mac 集群。