Vuncloud 博客
← 返回机房手记专栏

Xcode 缓存共享实战:多节点同步构建数据的技巧

DerivedData · SPM · CocoaPods · 多 Runner 同步约 13 分钟阅读

数据分析仪表盘象征多节点 Xcode 构建缓存监控与同步效率
TL;DR · 三句话
  • 单台 Mac 上的 DerivedData 缓存在第二台 Runner 上默认不存在——多节点 iOS CI 的隐性成本是每台机器各自冷启动
  • 可共享的是构建产物层(DerivedData、SPM 解析结果、Pods/),不是「多台同时写同一目录」;pull → build → push区域缓存枢纽是 2026 年主流做法
  • Cloud Mac 机群上,用 lockfile 哈希 + Xcode 版本做 cache key,配合 rsync/对象存储,warm 构建常见可再省 5–15 分钟(在单机缓存优化之上)

你把 GitHub Actions 的 actions/cache 配好了,warm 构建从 18 分钟降到 9 分钟——然后横向加了三台自托管 M4 Runner,P95 却又回到 16 分钟。每台机器都是「第一次见这个 commit」。

这不是回归原点,而是进入了多节点构建的第二阶段:Xcode 缓存共享。本文不讲「为什么要缓存」(见 CocoaPods / SPM / DerivedData 单机指南),而讲如何在多台 Mac 之间同步构建数据,让任意 Runner 都能吃到队友编译过的模块。

3
层缓存:依赖 · 编译产物 · 索引
5–15 分
多节点 warm 构建常见额外节省
1
条铁律:禁止并发写同一 DerivedData

1. 多节点 CI 为何比单机更慢

负载均衡把 job 随机打到 Runner A/B/C 时,每台机器的磁盘状态彼此独立。Runner A 刚编完的 MyAppKit.swiftmodule,对 Runner B 毫无意义——除非你把产物搬过去

GitHub 托管 runner 用 actions/cache 把 tarball 存到云端,job 开始时 restore。自托管或 Cloud Mac 机群往往没有等价的托管层,团队要么:

  • 接受每台机器各自 warm(浪费算力)
  • 把缓存目录挂 NFS,多台同时写(易爆库)
  • 自建缓存枢纽:构建前拉、成功后推

第三种在 iOS 团队里最常见,也最适合 多区域节点部署:美东三台 M4 共享一个缓存桶,亚太两台共享另一个,跨区只同步 SPM/Pods 层。

2. Xcode 构建数据分哪几层

同步之前先分清「什么值得传、什么不能混传」。

2.1 DerivedData

xcodebuild -derivedDataPath 指向的目录,含编译后的 .o.swiftmodule、链接中间产物、部分索引。体积常达数 GB,是warm 构建加速的最大头,也是对 Xcode 版本最敏感的一层。

推荐在 CI 上固定路径,例如 /Volumes/CI/DerivedData/<scheme>,与同步脚本里的 REMOTE 变量一致——单机缓存文里强调过,路径不一致等于没缓存。

2.2 SPM 与 CocoaPods

SPM:~/Library/Caches/org.swift.swiftpm + 仓库内 .build(若提交了解析结果)。CocoaPods:Pods/~/Library/Caches/CocoaPods

这两层变更频率低于 DerivedData(随 lockfile 变),更适合跨机器、甚至跨 region 同步。很多团队只把 SPM/Pods 推到 S3,DerivedData 留在区域内 rsync。

2.3 ModuleCache 与 Xcode 全局缓存

~/Library/Developer/Xcode/DerivedData/ModuleCache.noindex 与各种 SDKStatCaches。通常随 DerivedData 一并处理;单独同步时务必保证同一 Xcode 小版本

MacBook 上的代码编辑器,象征多节点 Runner 共享同一份 Xcode 编译缓存
共享的是「编过的模块」,不是「同时打开的工程」——每台 Runner 仍用本地 DerivedData 副本构建

3. 四种多节点同步架构

模式做法优点风险
本地-only每台 Runner 独立磁盘,无共享零运维横向扩展不省时间
NFS 共享挂载DerivedData 挂网络盘,多台只读或读写配置简单并发写易损坏;网络延迟拖慢链接
rsync 枢纽中央目录或 leader 机;构建前 pull、成功后 push可控、与 Xcode 兼容性好需写脚本与锁
对象存储 tarball按 cache key 打包上传 S3/R2;job 开始下载解压跨区域、与 GHA cache 模型一致大 DerivedData 上传费时;需压缩

2026 年实践里,同机房 / 同区域 Cloud Mac优先 rsync 枢纽;跨国团队用对象存储同步 SPM/Pods,DerivedData 区域自治。勿把 NFS 当「多写共享 DerivedData」的银弹。

铁律

禁止两台 Runner 同时对同一 DerivedData 树执行 xcodebuild。若必须共享盘,用文件锁单写多读(一台 designated warmer 编完再 rsync 分发)。

4. Cache key 与失效策略

多节点共享的 cache key 应比单机更保守——错命中比冷启动更糟(诡异链接错误、签名失败)。

建议纳入 key 的字段:

  • xcodebuild -versionXCODE_VERSION 环境变量
  • hashFiles('**/Podfile.lock')Package.resolved
  • Scheme 名或 target 集合(多 scheme 仓库分开缓存)
  • arm64(Apple Silicon 专用前缀,避免历史 x86 污染)

不建议把完整 github.sha 作为唯一 key——否则每台机器永远 miss。可用分层 restore-keysdd-arm64-main-<lockhash>dd-arm64-main-dd-arm64-

失效时机:Xcode 升级、lockfile 变更、切换 SWIFT_VERSION 或重大 Build Settings、人为 clean build 后应 bump key 后缀或删远程目录。

5. 实战:rsync pull/push 脚本

下面是一套在 M4 Cloud Mac 机群验证过的最小脚本,缓存枢纽在 cache-leader.internal:/cache/ios/(可以是机群中一台大磁盘机器或 NAS)。

#!/usr/bin/env bash
# cache-sync.sh — 在 xcodebuild 前后调用
set -euo pipefail

ACTION="${1:?pull|push}"
CACHE_ROOT="${CACHE_ROOT:-cache-leader.internal:/cache/ios}"
SCHEME="${SCHEME:-MyApp}"
KEY="${CACHE_KEY:-arm64-$(md5 -q Podfile.lock 2>/dev/null || echo nolock)-$(xcodebuild -version | head -1 | tr ' ' '-')}"
LOCAL_DD="${DERIVED_DATA_PATH:-/Volumes/CI/DerivedData/${SCHEME}}"
REMOTE="${CACHE_ROOT}/${KEY}/${SCHEME}/DerivedData"
LOCAL_SPM="${HOME}/Library/Caches/org.swift.swiftpm"
REMOTE_SPM="${CACHE_ROOT}/${KEY}/swiftpm"

rsync_opts=(-az --delete-delay --contimeout=10)

case "$ACTION" in
  pull)
    rsync "${rsync_opts[@]}" "${REMOTE}/" "${LOCAL_DD}/" || true
    rsync "${rsync_opts[@]}" "${REMOTE_SPM}/" "${LOCAL_SPM}/" || true
    ;;
  push)
    # 仅成功构建后调用
    rsync "${rsync_opts[@]}" "${LOCAL_DD}/" "${REMOTE}/"
    rsync "${rsync_opts[@]}" "${LOCAL_SPM}/" "${REMOTE_SPM}/"
    ;;
  *) echo "usage: $0 pull|push" >&2; exit 1 ;;
esac

CI 流程:

  1. Job 开始 → cache-sync.sh pull
  2. xcodebuild -scheme "$SCHEME" -derivedDataPath "$LOCAL_DD" build
  3. 仅当 build 成功 → cache-sync.sh push

多台并行时,push 可能竞态——用 flock 或「最后写入 wins」接受即可;DerivedData 在同 lockfile + 同 Xcode 下内容应兼容。若 push 极频繁,可改为异步上传(构建结束后后台 rsync,不阻塞 pipeline)。

6. 跨区域 Cloud Mac 缓存枢纽

美东、美西、亚太各放 Runner 时,跨洋 rsync 全量 DerivedData 往往不划算(延迟 + 出口流量)。推荐:

  • 每区域一个 cache bucket(S3 / R2 / 提供商对象存储)
  • 同步内容:Podfile.lock 哈希对应的 Pods/ tarball + Package.resolved 对应的 SPM 缓存
  • DerivedData 仅在区域内 rsync;首个 job 冷启动,后续 job warm
  • 镜像版本与 黄金镜像 tag 对齐,避免 Xcode 漂移导致全库失效

上传前用 tar czf 压缩 SPM 缓存,常能减 40%–60% 体积;DerivedData 内部已是大量小文件,tar + 并行上传比裸 rsync 跨区更稳。

7. 接入 GitHub Actions / 自托管 Runner

自托管 runner 上可在 workflow 里包一层:

- name: Restore shared Xcode cache
  run: ./scripts/cache-sync.sh pull
  env:
    CACHE_ROOT: ${{ secrets.IOS_CACHE_SSH }}
    SCHEME: MyApp
    DERIVED_DATA_PATH: /Volumes/CI/DerivedData/MyApp

- name: Build
  run: xcodebuild -scheme MyApp -derivedDataPath /Volumes/CI/DerivedData/MyApp build

- name: Save shared Xcode cache
  if: success()
  run: ./scripts/cache-sync.sh push

若仍用 GitHub 托管 actions/cache,自托管机群可双轨:GHA cache 作兜底,rsync 枢纽作同区域低延迟层。注意两套 key 命名空间分开,避免互相覆盖。

监控建议:记录 pull/push 耗时、传输字节、build 是否 incremental(看 xcodebuild 日志里 CompileSwift 数量)。P95 变慢时先查 cache miss,再查机器负载——与 构建忽快忽慢排查同一套方法论。

8. 踩坑清单

  • 并发写 DerivedData:随机 stat cache file corrupted → 改 pull/push
  • Xcode 小版本不一致:模块 ABI 对不上 → key 含版本,镜像统一 tag
  • 路径不一致:缓存到 A 路径,编译用 B 路径 → 全量重编
  • clean 后仍 push:把空目录推上去冲掉好缓存 → 仅 success 且非 clean 时 push
  • 签名产物进缓存:Provisioning 变更后沿用旧签名 → 缓存层排除 Build/Products 或按配置分 key
  • 磁盘满:DerivedData 膨胀 → 定期按 LRU 清远程 ${KEY} 目录,保留最近 N 个 lockhash

9. FAQ

多台 Mac 能直接共享同一个 DerivedData 目录吗?

不建议同时写入。只读挂载或单写者多读者可以,但链接阶段仍可能踩锁。生产环境优先本地 DerivedData + rsync。

Xcode 缓存共享需要统一 Xcode 版本吗?

必须。升级 Xcode 后应使用新 cache key,旧目录可异步删除。

和 Bazel / Tuist 远程缓存比怎么样?

Bazel/Tuist 远程缓存粒度更细(action 级),适合超大 monorepo。标准 Xcode 工程用 DerivedData 同步改造成本更低,与现有 xcodebuild 流程兼容。

多台 Cloud Mac 需要多大磁盘?

每台本地至少预留 50–100GB 给 DerivedData + SPM;枢纽机或对象存储按「并发 scheme 数 × 单 scheme 缓存体积 × 保留版本数」估算,M4 + 2TB 扩展盘对中型团队通常够用。

结语

Xcode 缓存共享解决的是多 Runner 时代的「第二次冷启动」:单机缓存优化之后,横向加机器不应再付出全额编译税。记住三层分离(Pods / SPM / DerivedData)、一种同步纪律(pull-build-push)、一条铁律(勿并发写 DerivedData)。

缓存是分布式编译里最便宜的算力倍增器——比再买两台 M4 更快见效。

从一台 cache leader 和 cache-sync.sh 开始;用 lockfile 哈希命名远程目录;build 绿了再 push。下周 P95 曲线会告诉你值不值。

为多节点 CI 预留磁盘的 Cloud Mac

Vuncloud Mac mini M4:美东、美西、亚太 SSH 就绪,适合挂载大盘 DerivedData、部署 rsync 缓存枢纽与自托管 Runner 机群。

查看 Cloud Mac 方案 · M4 CI Runner 成本模型

Apple 工具链行为以官方发布为准;缓存脚本请按团队安全策略审计 SSH 与对象存储凭据。最后更新:2026 年 7 月 27 日。

机房手记 · 构建加速

DerivedData 同步 · 多 Runner · Cloud Mac

rsync 枢纽 · cache key · 区域缓存桶

查看 Cloud Mac 方案
限时优惠 查看方案