全量 vs affected · 缓存键单调性 · 扇出只读消费 · 六步门禁与回滚到全量条件
维护大型 Monorepo的团队在多台远程 Mac Mesh节点上跑 CI 时,常被「一改就全量编译」拖垮队列:缓存键漂移、affected 图与 Runner 标签不对齐、消费节点误拉半成品缓存。本文给出何时全量、何时 affected、何时拆专用重编译节点的决策矩阵,说明可复现输入边界与扇出只读消费职责,并附六步可复现门禁与回滚条件。可与跨区产物分发决策矩阵、Merge Queue 与 Runner 标签交叉阅读。
单仓多包时,影响范围构建依赖变更图、锁文件与工具链指纹三者一致;任一字段在跨区节点间不同步,就会出现「本地绿、远端红」或更糟的假绿:编译通过但产物与主干语义偏离。Mesh 场景下,交互式会话与无人值守流水线共享同一存储前缀时,幽灵依赖与手工拷贝的 DerivedData 会污染缓存键,使 affected 结果膨胀为全量却难以被指标捕获。
与缓存就近与 artifacts 对照长文的分工是:该文侧重字节路径与 rsync 或对象存储取舍;本文侧重计算图裁剪与触发规则。若尚未拆分 ci-merge 与常规 PR Runner,请先阅读 Merge Queue 文,否则 affected 优化会被合并验证饥饿抵消。
变更图与标签漂移:同一仓库在不同 Runner 标签上解析出不同 workspace 根路径,导致 affected 子集不一致。
锁文件未进键:package 解析结果变化但缓存键仍命中旧 tarball,表现为偶发链接错误而非稳定失败。
工具链指纹缺席:Xcode 小版本或 Swift 编译器补丁未写入 manifest,消费节点 silent mismatch。
扇出与计算混线:只读消费者被赋予写缓存前缀权限,半同步写入与 affected 判定竞态。
全量回滚条件缺失:当核心包或生成代码脚本变更时仍强制 affected,CI 绿但集成测爆。
下列维度面向多地区远程 Mac 共享池上的 Monorepo 流水线,而非单机 Xcode。目标是让「触发」成为可审计字段,而不是工程师口头的「这次应该没事」。
| 维度 | 优先全量 | 优先 affected | 拆专用重编译节点 |
|---|---|---|---|
| 变更类型 | 生成代码、构建脚本、共享 native 模块接口 | 局限在单应用包内的 UI 与文案 | 全仓 heavy 模块夜间批量与日间 PR 抢同一池 |
| 队列健康信号 | 连续两次 affected 失败或集成测随机红 | 变更图稳定且锁文件校验一致 | CPU 空闲但 wall time 高、交互用户投诉编译抖动 |
| 缓存键策略 | 提升工具链段权重,临时禁用跨节点复用 | 锁文件 + 变更哈希 + Runner OS 层版本 | 重编译节点使用独立前缀,扇出只读镜像 |
| 跨区一致性 | 全节点统一 manifest 世代再开编译 | 允许区域级从库镜像延迟但禁止写回主前缀 | 主区写缓存、卫星区只读挂载或拉取 |
| 回滚到全量 | 主干保护分支合并后首个绿构建前 | 每周定时一次对照全量以防漂移 | 大版本 Xcode 切换窗口强制全量周 |
先冻结「可复现输入」再谈 affected;顺序反了会把 flaky 从编译器搬进调度器。
以下步骤假设 Runner 已能稳定 SSH 到各节点并完成共享池互信;若产物仍需跨洋扇出,请同步落实产物分发中的租约与 manifest 字段。
冻结输入三元组:将 commit、锁文件摘要、xcodebuild -version 指纹写入流水线头;任一变化自动 bump 缓存世代。
生成变更图:在固定 Runner 标签上执行单点解析,输出受影响包列表 artifact,禁止各节点各自推断。
判定全量门槛:若命中生成代码、共享 kernel 或 toolchain 升级清单,短路为全量并记录审计原因码。
写主缓存前缀:仅主构建节点写入;卫星节点使用只读凭证拉取,禁止回写 DerivedData 到共享前缀。
扇出校验:对缓存归档执行哈希与大小双字段校验后再切换指针;失败则阻断消费节点编译。
重试与回滚:网络类错误指数退避;校验类错误打开 incident 并强制下一提交全量直至绿。
CACHE_KEY="${CI_COMMIT_SHORT_SHA}:${LOCKFILE_SHA256}:$(xcodebuild -version | shasum -a 256 | cut -c1-12)"
export TURBO_REMOTE_CACHE_SIGNATURE="${CACHE_KEY}"
echo "affected=$(npx turbo run build --dry=json | jq -r '.packages | length')" > "${CI_PROJECT_DIR}/affected.meta"
提示:示例命令中的工具名可按团队栈替换为 Nx、Bazel 或自定义脚本;关键是单一权威变更图与单调缓存键,不要在每台消费机重复解析。
Monorepo 的典型冲突不是 Git 合并冲突,而是两个 PR 同时 bump 共享包导致缓存前缀交错写入。Mesh 上应把「写缓存」与「读缓存 + 编译」拆成不同身份:写身份仅 CI 主节点持有,读身份可分发到多地。若消费侧仍混用交互式 xcodebuild,请避免把本地 DerivedData 同步回共享前缀。
与并发席位与互斥配合使用时,把席位锁的 lease id 写入缓存路径,可在复盘时对齐「谁占用编译核」与「谁写入缓存世代」。任务链可观测中的 envelope 字段同样可携带 affected 包数量与全量短路标记,便于告警路由。
注意:在未关闭写权限前打开跨区扇出,会把半同步问题放大为「多地同时假绿」;先完成只读挂载与指针切换门禁。
互斥写:同一缓存前缀同一时刻仅允许一个 lease 写入;其他作业排队或改走独立 stage。
消费门槛:消费节点仅在被 manifest 世代标记为 current 后启动编译。
人工介入:连续两次校验失败后冻结 affected,直至负责人签出全量绿构建。
下列数值为评审与容量规划起点,须用你们仓库真实变更分布与构建时长直方图替换;不得直接宣称为对外 SLA。落地时应要求负责人同时给出 affected 命中率、全量短路次数与跨区拉缓存耗时三条序列。
当夜间批量 affected 与交互式本地编译争用同一节点的磁盘子系统时,仅观察 CPU 利用率会误判健康;需要把缓存 tarball 展开写与 IDE 索引写放在同一监控面板讨论。
| 团队规模信号 | 推荐起步策略 | 与产物扇出关系 |
|---|---|---|
| 个位数贡献者 | 单区域主构建 + 本地 affected | 扇出需求低,优先简化键 |
| 多地区并行 PR | 主区写缓存 + 卫星只读 + manifest | 与 rsync 或对象存储扇出强耦合 |
| AI Agent 夜间任务 | 专用重编译节点与交互池隔离 | 避免 Agent 批量任务打爆共享键空间 |
依赖个人笔记本做远程编译入口,往往在休眠、锁屏与带宽波动上同时欠账;自建固定资产又被采购与折旧节奏绑架。对要把 Monorepo 持续交付与多节点协作放在同一容量故事里的团队,自建拼凑方案在观测与权限边界上持续失血。
相较之下,需要合同化节点、可复核带宽与区域可选的团队,把关键构建与缓存扇出跑在可订购的云 Mac 上更利于落地门禁;对同时承担 iOS 持续交付与 AI Agent 夜间负载的场景,VpsMesh 的 Mac Mini 云端租赁通常是更优解:节点角色可拆分、链路可审计,让 affected 命中率与队列深度同样可验收。