毁掉 Apple Silicon CI 吞吐的最快方式,就是把缓存命名成Git 分支。分支键会在静默变更了 Package.resolved、Podfile.lock 或 Gemfile.lock 的提交之间复用图,带来“随机”红构建,甚至更糟的幽灵绿。到 2026 年,请把依赖缓存当作按内容寻址的存储:对 lockfile 求哈希、固定工具链构建号,并发布策略让每个工程师明白——缓存未命中是健康的信号。
运维上下文:磁盘与产物保留、工作树与干净克隆,以及 Runner 亲和性。若在香港、日本、韩国、新加坡或美国需要更隔离的 Mac mini M4 主机,请对比 定价,并参阅 帮助 中的 SSH/VNC 接入模式。
为何 lockfile 哈希胜过分支标签
分支标签暗示“这条开发线上的所有东西共享状态”。依赖图并不沿分支走——它们遵循精确的解析结果。一次合入可能在未触碰应用源码的情况下抬升传递依赖版本,使二进制失效而分支名不变。对 lockfile 哈希让缓存命中成为证明义务:若哈希一致,你就有证据表明解析器输出在模工具链意义下相同。
- 可复现性:审计人员可把绿构建与特定 lockfile 摘要关联。
- 并行 PR:同名分支上的两个 PR(fork 工作流)不再悄悄撞车。
- 磁盘经济:在哈希上做 LRU 比“删掉整个分支文件夹”更能封顶增长。
矩阵 A:按生态的键控配方
| 生态 | 锁定输入 | 建议路径后缀 |
|---|---|---|
| SwiftPM + Xcode | Package.resolved + Xcode build |
spm/<sha256(resolved)>/xc<build> |
| CocoaPods | Podfile.lock + Ruby 版本文件 |
pods/<sha256(lock)>/ruby<ver> |
| Bundler | Gemfile.lock |
bundle/<sha256(lock)> |
| npm / pnpm | package-lock.json 或 pnpm-lock.yaml |
js/<sha256(lock)>/node<major> |
矩阵 B:共享缓存仍不安全的场景
即便 lockfile 哈希完美,若在恢复阶段执行 post-install 脚本,仍无法救场。除非对脚本执行做沙箱并对恢复产物校验,否则把这些作业视为冷路径。
| 信号 | 策略 | 缓解 |
|---|---|---|
post_install 钩子改写 Pods |
不要在作业间共享恢复 | 每次构建都跑钩子;仅缓存下载 blob |
| 未校验的二进制 Pod | 视为不可信输入 | 在仓库中以带内容哈希的 vendor 固化 |
| Fork PR 构建 | 独立缓存命名空间 | 绝不复用内部单体仓库缓存路径 |
经得起审计的数值默认
- 哈希算法:对规范化 lockfile 字节做 SHA-256(在 CI 中统一剥离 CR/LF 差异)。
- LRU 宽度:每台主机每个生态保留最近 12 个哈希。
- 最长保留:无论 LRU,14 天无命中即驱逐。
- 告警:当缓存卷超过专用 CI 磁盘的 70% 时分页。
安全提示:缓存属于供应链的一环。若被篡改的 lockfile 可能毒化共享 tarball 缓存,优先选择临时下载并校验,而非盲目复用。
八步上线
- 清点哪些仓库缺少已提交的 lockfile——在补齐前禁止缓存。
- 输出摘要作为每次构建的 CI 产物以便事后关联。
- 挂载缓存到快速 APFS 卷而非网络共享,用于 SPM 解析。
- 接入指标:缓存命中率、恢复秒数、驱逐次数。
- 文档化 CONTRIBUTING.md 中的冷缓存预期。
- 显式测试 fork 工作流——按信任层级划分缓存命名空间。
- 与检出策略配对(见工作树指南)避免双重共享。
- 扩展硬件——当驱逐引发抖动时,NodeMac 可按区域增加专用 M4 节点而无需采购周期。
常见问题
为何分支命名缓存会导致幽灵绿?
它们在 lockfile 变更间复用二进制。对 lockfile 求哈希,使缓存命中意味着解析器输出一致。
SPM 缓存应与 DerivedData 共用键吗?
否——关注点分离。SPM 键属于解析;DerivedData 属于编译索引,应包含作业或工具链维度。
多久修剪一次?
在上限内做 LRU,并配合最长保留驱逐;始终绑定磁盘告警。