VPSSpark CI Queue Diagnosis Standard(Cloud Mac CI 系列 #2)。下文阅读顺序:Hook → 主公式 → 症状 → Failure Model → Hard Rules → Runbook → FAQ。延伸:#1 容量 · #3 自托管算账 · #5 扩员 · #8 构建提速。
1 · Hook:反直觉结论
99% 的 macOS CI queued 问题,不是资源不足,而是 L2(Archive)被错误放进 PR 路径。
2 · 核心:唯一主公式
全文只认下面这一条。其余章节都是它的展开。
主公式
CI queue problem ⇔ wait time >> run time
工程结论: macOS runner queued = Runner pool 饱和,而不是 build 变慢。
若公式不成立 → 非本篇,转系列 #8(Xcode / Cache)。若成立 → 对号入座 Failure 3 Layer Model,并落实 CI Hard Rules。
3 · 症状:Queued ≠ Running
GitHub Actions 里,Queued 表示 Job 已进 workflow,但还没拿到 Runner 槽位;In progress 之后才是 checkout、xcodebuild、签名。很多 iOS 团队把「PR 红了 1 小时」当成编译慢——打开时间轴才发现:52 分钟在等,真正 run 只有 11 分钟。
这类误判的代价是买错药:升级 Xcode、加 timeout-minutes、加 GitHub 分钟包——Queued 时间往往不计入你以为的构建超时,团队只是在日历上多等一天。
Queued = Job 已进 workflow,尚未拿到 Runner 槽位;In progress 后才是 xcodebuild。实录:wait 52 min · run 11 min——团队误以为是 Xcode 慢。度量见 Job 执行时间(queue_wait_seconds vs run duration)。
| wait time | run time | |
|---|---|---|
| 界面 | Queued | In progress |
| 瓶颈 | GitHub Actions macOS runner queue / self-hosted runner queue | Xcode · 签名 · 上传 |
| 误操作 | 加 minutes · 换 Xcode | 加缓存(→ #8) |
4 · 解释:CI Queue Failure 3 Layer Model
当 wait time >> run time 成立,主因落在下面三层之一(SRE 命名,便于写 incident runbook)。
| Layer | 含义 | 典型信号 | 当天动作 |
|---|---|---|---|
| Capacity Limit | 平台 · macos-latest |
仅托管 macOS runner queued;minutes ≠ concurrency | 减 fanout;Archive 迁出托管 → #3 |
| Pool Misdesign | 架构 · self-hosted pool | Archive 进 PR;fast/archive 未分离;单机 busy 全线 Queued | 落实 Hard Rules → #1 #6 |
| Trigger Explosion | 负载 · workflow fanout | matrix / paths / 重复 workflow;Job 数 > Runner 数 | 收紧触发器 → #5 |
Capacity Limit — 托管 macOS Runner 撞 macOS concurrency cap(limits)。Pool Misdesign — 多数案例主因:Job tier L2 误入 PR、fast/archive 混池。Trigger Explosion — 修法在 YAML,不在加机器。(注:Failure Layer 与下文 Job 层级 L0/L1/L2 是两套命名。)
5 · 解决:CI Hard Rules(MUST NOT violate)
工程规范语言。三条规则不可协商。
Rule 1: PR MUST NOT run L2 Rule 2: L2 MUST run on isolated pool (macos-archive) Rule 3: fast pool MUST remain unblocked (fast ≠ archive) # 落实映射 PR → L0 + L1 only →macos-fastmain → L2 only →macos-archive
L0 / L1 / L2 类型参考(服务于 Hard Rules,非第二套模型):
| 层级 | 内容 | 池 | 与铁律 |
|---|---|---|---|
| L0 | 单模块编译、静态检查、轻量单测 | macos-fast |
Rule 1 · PR 可跑 |
| L1 | PR 集成、模拟器测试 | macos-fast |
Rule 1 · MUST NOT Archive |
| L2 | Archive、IPA、TestFlight | macos-archive |
Rule 2 + 3 · PR MUST NOT |
图 1 · Pool Misdesign(L2):占槽 → 全线 macOS runner queued
jobs: pr-fast: if: github.event_name == 'pull_request' runs-on: [self-hosted, macos-fast] # L0 + L1 only release-archive: if: github.ref == 'refs/heads/main' || github.event_name == 'schedule' runs-on: [self-hosted, macos-archive] # L2 only · dedicated pool
Runner 拓扑延伸:弹性池 vs 常驻 · #1 容量 · queue 止血后加机器 → #3。
6 · Runbook:一页纸(可转发 on-call)
0. CI queue problem ⇔ wait time >> run time ? ─No→ #8
1. Failure layer: Capacity | Pool Misdesign | Trigger Explosion
2. MUST: Rule1 PR no L2 · Rule2 L2 isolated · Rule3 fast unblocked
3. PR workflow 无 archive/export/upload ?
4. wait P95 < 8min → 再评估 Runner (#3)
一句话结论: 99% 的 macOS CI queued 问题,不是资源不足,而是 L2 被错误放进 PR 路径。 macOS runner queued = Runner pool 饱和信号;先 wait >> run,再 Failure Model,再 Hard Rules。
7 · FAQ
为什么 macOS runner 更容易出现 queued?
Capacity Limit(macOS concurrency cap)+ Pool Misdesign(L2 进 PR)。先 wait >> run,再 Failure 3 Layer。
怎么一眼判断是 queue 还是 build 问题?
唯一主公式:CI queue problem ⇔ wait time >> run time。成立 → Runner pool;不成立 → #8。
self-hosted runner 为什么也会 queued?
Pool Misdesign:self-hosted runner queue 由池设计决定。违反 Hard Rules(L2 进 PR、fast/archive 混池)即堵——与是否 Cloud Mac 无关。
GitHub Actions minutes 和 concurrency 有什么区别?
minutes = 计费;concurrency = 占槽。加 minutes 不解决 macOS runner queued(Capacity Limit 层)。
怎么判断是不是 runner pool 问题?
wait >> run + 自托管单机 long busy → Pool Misdesign;仅 macos-latest queued → Capacity Limit。见 Runbook。
queue 正常了但 build 仍慢,读哪篇?
系列 #8(构建提速)。问买几台:系列 #1。问扩员后为何崩:系列 #5。
验证分池:需要一台「快池」试跑时
Hard Rules 落实后,若要把 L0/L1 迁出托管 GitHub Actions macOS runner queue,可用 1 台 macos-fast Runner 做 PoC——验证 wait time P95 < 8 min,再按 #1 补 macos-archive。
若你希望按天拉起一台隔离快池、不改办公室网络,可在 Mac 云主机方案 或 VPSSpark 首页 开通 Cloud Mac 做分池验证——这是拓扑验证手段,不能替代 workflow 规则。系列 #3 将专门算 self-hosted 是否值得。