VPSSpark 博客
← 返回开发日记

iOS 团队扩张到 20 人后,CI/CD 最先崩在哪?

机房手记 · 2026.06.04 · 约 16 分钟阅读

开发团队在会议室讨论 iOS CI 构建排队与基础设施扩容

这篇是一份 iOS CI/CD 扩员崩溃模型(Scaling Failure Model)——不是容量计算器。我们接手前,团队十四个月从 8 人扩到 20 人,Runner 拓扑几乎没动:发版周 PR 全红、构建长时间停在 Queued、TestFlight 晚两天。下文回答「为什么人一多 CI 就慢」,以及瓶颈在 pipeline 哪一段。

若你关心「500 次/天要买几台 Mac」,请看姊妹篇 3 台 Cloud Mac 支撑 500 次 iOS 构建——那是接手后的落地;本篇是接手前为什么崩

先给结论:20 人团队 CI 崩溃,很少是「Mac 不够」一句话。案例里五个瓶颈按顺序叠加:并发顶满 → Runner 与开发争用 → PR 跑 Archive → 矩阵 Job 暴涨 → 签名互抢。12~16 人已有征兆;拖到 20 人还不拆队列,就会进入 Failure Zone。

一眼看懂:CI 如何从「人多」滑向 Failure Zone

图 1 是整篇的总览机制图——比 pipeline 细节更适合作 postmortem 开场或团队 onboarding。崩溃 rarely 是单点,而是下面这条链路上的叠加。

图 1 · CI 崩溃机制总览(Scaling Failure Cascade)

PR 合并频率上升team scaling · 非线性起点
Matrix / Job 数爆炸monorepo · path filter 失效
Queue 积压queue_wait_seconds ↑
Runner 池饱和hosted 并发 · mini 被占用
Archive 过载L2 灌入 PR 队列
Signing / Keychain 互抢codesign contention
Failure Zonequeue P95 > 30min · Archive > 30%

该客户 12 人卡在 Queue,16 人在 Runner/Archive 分叉同时亮灯,20 人落入底部红区。与 healthy baseline 的对照见下文。

iOS CI/CD 架构图:从 PR 到 TestFlight 的瓶颈地图

图 2 是一次 macOS Job 的执行路径(L0/L1/L2),标注该案例里两处最先打满的位置。它能帮你区分:是编译慢,还是 Job 根本没在跑。

图 2 · iOS CI/CD Pipeline:L0 / L1 / L2 分层与典型瓶颈(该客户 2024–2025)

PR Pushpath filter · matrix
Queuequeue_wait_seconds
Runner Poolhosted + self-hosted
L0/L1 Build集成 · 单测
L2 Archivexcodebuild archive
SigningKeychain · Match
UploadTestFlight
↑ Bottleneck · Queue(组织 macOS 并发顶满) ↑ Contention · Runner Pool + Signing(办公室 Mac 与 Keychain 互抢)

L0:lint / 轻量检查;L1:PR 集成编译与 Simulator 测试;L2:Archive、公证、上传。该客户崩在 Queue 与 Runner 混池,而非 L1 编译本身——矩阵膨胀常被误读成「Xcode 变慢」。

起点:8 人时「够用」的 CI,为什么没跟着长

2024 年中,团队配置很典型:

CTO 的原话是:「CI 不是瓶颈,别在这上面花时间。」——8 人时成立;人效翻倍后 CI 成了隐形税,因为没人做容量规划,只有 feature team 在堆 workflow。

Healthy baseline:对照什么算「正常」?

只看 20 人崩溃周,容易误判「团队一大就必须买很多 Mac」。我们补一张健康基线 vs 崩溃周对照——数字来自该客户 8 人档实测,以及我们经手过的 mid-size iOS 团队经验区间(非行业标准,但足够做内部 review)。

指标 Healthy baseline(8~12 人 · 拓扑合理) 该客户 · 20 人崩溃周
L1 queue_wait P95 < 8 分钟 47 分钟
L1 run duration P95 12~18 分钟 14 分钟(编译并未变慢)
Archive 类 Job 占比 < 15% 38%
单次 push · macOS Job P99 < 6 19
macOS Job / 天 80~180 500+(发版周)
Runner 拓扑 L1/L2 标签分池 hosted + 办公室 mini 混跑

关键对照:崩溃周 run duration 几乎没变,queue time 飙了近 10 倍——说明问题在排队与 workflow,不在 Xcode 编译能力。这也是「CI 变慢」一词最容易误导团队的地方。

时间线:三次预警,三次被当成「偶发」

第一次(12 人,+4 名 iOS):PR 合并频率从日均 6 次涨到 14 次。托管 Runner 的 build queue 在下午出现 25~40 分钟等待。团队反应:「GitHub 今天抽风吧。」没人拉 queue_wait_seconds 曲线。

第二次(16 人,又招 2 后端共用同一 repo):他们在办公室加了第二台 Mac mini,两台都注册成 self-hosted没有标签分队列。Archive 和 PR Build 抢同一池子,表现是:轻 Job 偶发 8 分钟变 35 分钟,重 Job 公证随机失败。反应:「再买个硬盘试试。」

第三次(20 人,双 App 大版本撞车):发版周 macOS Job 从日均 120 次冲到 单日 280+。Slack 频道 #ios-ci 开始 24 小时有人 @ 值班。反应:「采购在走 8 台 Mac mini 流程了。」——这就是我们在 500 次/天案例 里看到的那个 8 台方案;当时还没人算过 Job 分项。

图 3 · 团队规模 vs macOS Job/天 vs 风险档位(该客户 2024–2025 实测)

团队人数 macOS Job / 天 GitHub Actions queue time(L1 P95) 风险档位 阶段备注
8 人 ~100 < 5 分钟 Baseline 单台办公室 mini 够用
12 人 ~180 25~40 分钟 Warning Zone 排队首次持续超标
16 人 ~260 ~35 分钟 Structural Debt 混跑 Archive,签名偶发失败
20 人 500+ 47 分钟 Failure Zone 发版周峰值,CI 实质崩溃
Job / 天 0 200 400 600 100 180 260 500+ 8人 12人 16人 20人

数据来源:GitHub Actions workflow 运行日志(14 天滑动窗口);20 人取双 App 发版周峰值。Job 量增长快于人数,主因是 matrix 膨胀与 PR Archive,而非单纯「人多」。

表里有两个细节:Job 量在 16→20 人段陡增,Archive 占比从约 18% 升到 38%;queue time 与 Job 量非线性——人头没翻倍,排队先翻倍。这正是扩容预算最容易算错的地方。

失控阈值模型:12 人 Warning / 16 人 Structural Debt / 20 人 Failure Zone

我们把案例收敛成 weekly review 可用的框架——不是精确公式,但足够判断要不要动 Runner 拓扑:

CI 崩溃风险 ∝ (PR 频率 × Matrix 宽度 × Archive 占比) ÷ 有效 Runner 池容量

分子三项都可从 GitHub Actions 日志算出来;分母「有效 Runner 池」不是注册台数,而是按标签可分流的并发 Mac 槽位——办公室 mini 被开发占用时,分母会瞬间打折。当下面任意两条同时成立,我们建议停止讨论「再加一台 Mac mini」,先重构 pipeline:

  • queue_wait_seconds P95 > 30 分钟Archive 类 Job 占比 > 30% → 必须拆 L1/L2,停止 PR 全量 Archive
  • 单次 push 触发 macOS Job 数 P99 > 15 → 矩阵/path filter 失控,先减 Job 再谈加机
  • L2 连续 3 天 queue P95 > 40 分钟 且缓存命中已 > 60% → 进入结构性扩容,考虑专属 release 池

该客户 20 人崩溃当周三条全中——因此属于 Failure Zone,而非「再买分钟包」能解决的 Warning。

GitHub Actions queue time:12 人后第一个失控指标

很多团队把「CI 变慢」理解成 compile 变慢,去调 DerivedData。在这家客户日志里,真正失控的是 queue time——Job 长时间停在 Queued。GitHub 把 queue_wait_seconds 与 run duration 分开统计;若只看后者,会误修编译,而瓶颈在组织并发与 Runner 分池。

组织级 macOS 并发见 GitHub Actions usage limits。12 人以后下午 PR 风暴时,不是 Runner 慢,是 Job 在等空位——界面显示 Queued 转圈,常被当成网络或 Xcode 问题。排查时对照 Job 执行时间与排队耗时,把 wait 与 run 拆开。

12 人档 queue P95 已到 25~40 分钟。若当时用标签把 fast/archive 分池(而非两台 mini 混跑),本可推迟后面的签名互抢。Runner 拓扑选型见 弹性池 vs 常驻节点决策矩阵

五个扩容瓶颈(Scaling Taxonomy)

下面五类是我们内部的 mobile CI 故障标签——与图 2 pipeline 对应,便于写 postmortem。

① Capacity bottleneck · 组织 macOS 并发顶满

托管 Runner 并发打满时,轻 Job 与 Archive 一起排队,快队列被慢 Job 堵住——表现与上一节 GitHub Actions queue time 曲线一致。除加分钟包外,更稳的是把 L2 迁出托管池;否则组织级并发永远是第一个天花板。

② Resource contention · 办公室 Mac mini 被当「备用开发机」

Runner 挂在工程师能 SSH 的机器上,迟早有人本地跑 xcodebuild debug。Runner 与日常开发争 CPU/磁盘,表现为超时或偶发 compile error。16 人时两台 mini 曾「周五全挂、周一又好」,根因是 Remote Desktop 改分支。

③ Workflow design debt · 每个 PR 跑全量 Archive

早期 8 人时,「PR 绿 = 能发版」的偷懒写法:每个 pull request workflow 末尾加 Archive + 上传。人数少时一天 6 次还能忍;20 人时Archive 类 Job 占比从 10% 被 workflow 设计抬到 35%+,直接把慢 Job 灌进本已拥挤的队列。正确做法是把 L1 集成构建与 L2 Archive 拆开——这在接手后的改造里才落地,详见姊妹篇的 Job 分层章节。

④ Scaling explosion · Monorepo 矩阵 Job 指数增长

20 人时单次 push 最高触发 22 个 macOS Job——人多了 2.5 倍,Job 多了 4 倍。这是产品节奏下最容易被忽略的崩点。

⑤ Crypto / signing contention · 发版日 Keychain 与签名互抢

两台 Self-hosted Mac 同时跑 Archive,16GB 内存上偶发还能撑;一旦叠加 Match 解锁、公证、TestFlight 上传,Keychain 锁与 codesign 竞争 会变成「同一错误码随机出现」。发版周他们出现过连续 4 次「证书找不到」,本地 Mac 重跑却成功——典型多 Job 抢签名环境,而非证书真过期。配置与能力说明见 Apple Xcode Signing and Capabilities 文档

他们试过的「修复」,为什么让情况更糟

加买 GitHub 分钟包:只缓解托管队列,不改变 Archive 占比,钱花了,P95 仍超 60 分钟。

办公室第三台 Mac mini:仍无 fast/archive 标签,三台混跑,签名失败率反而上升。

禁止周五合并:人为压制 PR 频率,发版周集中爆发,尖峰更高。

每个 Job 加 timeout-minutes: 180Queued 时间不算 timeout,只会让更多 Job 长时间占坑。

唯一有效但未做完的尝试:把 Release 迁到单独机器——可机器仍是办公室 mini,夜间无人解锁 Keychain,L2 队列周一积压。

监控里该看、但没人看的三个数

崩溃前他们的 Grafana 只有「成功率」和「平均耗时」。接手后我们第一周只加三个指标,就定位了 80% 问题(queue_wait_seconds 的拆分方式见上一节 GitHub 官方监控文档):

  • queue_wait_seconds P95(按 L0/L1/L2 分桶)——区分「编译慢」与「在排队」
  • Archive 类 Job 占比(每周)——workflow 设计是否失控
  • 单次 push 触发的 macOS Job 数 P99——矩阵是否指数膨胀

20 人崩溃当周的数据:L1 的 queue_wait P95 是 47 分钟,Archive 占比 38%,push 触发 Job P99 是 19。三个数任意两个超标,就该动拓扑,而不是先开采购。

止血顺序:先改拓扑,再谈买几台 Mac

我们接手后头两周没买新机器,只做四件事:

  • PR workflow 去掉 Archive,L2 仅 nightly + release 分支
  • 收紧 path filter,README/后端目录不再触发 iOS 矩阵
  • 办公室 mini 下线 Runner,避免与开发争用
  • 托管 Runner 只顶 L1 尖峰,L2 全部迁专属节点

仅这四项,发版周 queue_wait P95 从 47 分钟降到 22 分钟——仍然不够,但证明了崩溃主因是拓扑与 workflow,不是「Mac 台数神秘公式」。之后才做 Cloud Mac 的 fast/release 分池与容量验证;弹性池与常驻节点怎么选,见 GitHub Actions 自托管 macOS Runner:弹性池与常驻节点决策矩阵

给正在扩团队的三个信号

若你正在从 12 人往 20 人走,出现以下任一情况,建议当周开 30 分钟 CI 专项,而不是等发版撞墙:

  • 开发者开始问「能不能跳过 CI 先 merge」
  • 办公室 Mac 上出现「请勿重启,Runner 在跑」的便签
  • TestFlight 构建成功但「排队等上传窗口」成为常态

这三个信号背后,通常已经踩中了上文五个崩点里的至少两个。

Postmortem Summary(可引用)

Root cause

团队扩员时未同步扩容 CI 拓扑:PR 频率与 matrix 宽度上升,但 Runner 仍为「hosted + 办公室 mini 混池」,且 PR workflow 持续跑 L2 Archive,导致 queue 与 signing 双重饱和。

Contributing factors

  • queue_wait_seconds 监控,把 Queued 误判为 compile 慢
  • 第二台 Mac mini 无 fast/archive 标签,Archive 与 PR 抢同一池
  • Monorepo path filter 过宽,README 改动触发 iOS 矩阵
  • 发版周 Job 尖峰未与采购/拓扑变更联动

Fixes tried(无效或加重)

  • 加买 GitHub 分钟包——只缓解 hosted 队列,Archive 占比不变
  • 第三台办公室 mini 混跑——签名失败率上升
  • 禁止周五合并——尖峰转移到发版周
  • timeout-minutes: 180——Queued 时间不计入 timeout

What worked(接手后前两周)

  • PR 去 Archive;L2 仅 nightly + release 分支
  • 收紧 path filter;办公室 mini 下线 Runner
  • L2 迁专属 release 节点——queue P95 从 47min → 22min

FAQ

GitHub Actions 一直 Queued、不是编译慢怎么办?

先看 queue_wait_seconds 与 run duration。Queued 占比高说明瓶颈在排队或 Runner 池,而非编译。对照图 2 的 Queue 节点与图 3 健康基线排查。

iOS 团队多少人时 CI 最容易崩?

本案例三档:12 Warning / 16 Structural Debt / 20 Failure Zone。更可靠的是指标:queue P95 > 30 分钟且 Archive > 30% 时,优先重构 pipeline。

团队扩张时应该先加机器还是先改流水线?

先改 Job 分层、停 PR Archive、收紧 matrix。否则加机器只是推迟崩溃,queue 尖峰会在下一次发版周回来。

这和「500 次/天要几台 Mac」是一篇文章吗?

不是。本篇是为何崩(Failure Model);姊妹篇是崩后容量与台数(Sizing)。建议先读本篇 + 诊断,再读容量篇。

Failure Zone 处方:先 L2 隔离,再验证 Runner 池

若同时满足以下三条,说明已进入 Failure Zone(与图 1 底部一致)

  • queue_wait_seconds P95 > 30 分钟
  • Archive 类 Job 占比 > 30%
  • 单次 push 触发 macOS Job > 15 个

工程侧优先动作(先于采购)

1. L2 isolation——PR 去掉 Archive;release/nightly 独占 macos-archive 标签。

2. Dedicated Mac pool——L2 迁出办公室开发与 hosted 混池;L1 可继续 hosted 或弹性 self-hosted 顶尖峰。

3. 验证——用隔离的 release 节点跑一轮发版 workflow,对照图 3 健康基线看 queue P95 是否回到 < 8 分钟档。

台数与 fast/release 分池算法见 3 台 Cloud Mac 支撑 500 次 iOS 构建;Runner 弹性 vs 常驻见 GitHub Actions 自托管 macOS Runner 决策矩阵

若需要按日 PoC 一台 release 专用节点做 L2 isolation 验证,可在 Mac 云主机方案VPSSpark 首页 开隔离 Archive 队列的 Cloud Mac——这是验证拓扑,不是第一步就买满集群。

限时特惠

20 人 iOS 团队 CI 崩了?先拆队列

build queue · Archive 隔离 · Cloud Mac Runner

返回首页
限时优惠 点击查看套餐