VPSSpark CI 큐 진단 표준(클라우드 Mac CI #2). 읽는 순서: Hook → 공식 → 증상 → Failure Model → Hard Rules → Runbook → FAQ. 관련: #1 용량 · #5 스케일 실패 사례 · #3 셀프호스트 TCO · #8 빌드 속도.
1 · Hook: 직관과 반대되는 결론
macOS CI에서 queued가 길어지는 사고 대부분은 “Mac이 부족하다”가 아니라, PR 경로에서 L2(Archive)가 돌고 있기 때문입니다. 현장에서는 이 패턴이 약 9할에 달합니다.
GitHub Actions 타임라인을 열어보면 빨간 PR이 “컴파일이 느리다”로 보고되는 경우가 많습니다. 그러나 Queued와 In progress는 서로 다른 지표입니다. SRE 관점에서는 먼저 “대기가 실행 시간을 압도하는지”를 가르지 않으면 Xcode 업그레이드나 분(minutes) 구매는 엇나간 처방이 됩니다. 이 글은 그 가름용 규격입니다.
2 · 핵심: 단 하나의 공식
아래 모든 절은 이 판정을 펼친 것입니다. 온콜 위키에 그대로 붙여 넣으세요.
마스터 공식
CI queue problem ⇔ wait time >> run time
엔지니어링 해석: macOS runner queued는 러너 풀 포화를 뜻하며, Xcode 빌드 자체가 느린 것이 아닙니다.
공식이 false → 이 글 범위 밖. #8(Xcode/캐시) 확인. true → Failure 3 Layer Model과 CI Hard Rules 적용.
국내 iOS 팀은 호스티드 macos-latest와 셀프호스트를 섞는 경우가 많아 “분은 남는데 Queued”라는 모순이 자주 납니다. 과금 단위(minutes)와 동시 실행 슬롯(concurrency)을 혼동한 결과입니다. 공식이 true일 때만 풀·트리거 설계를 손대세요.
3 · 증상: Queued ≠ Running
GitHub Actions에서 Queued는 워크플로에 들어갔지만 아직 러너 슬롯이 없는 상태입니다. In progress에서 checkout, xcodebuild, 서명이 실행됩니다. 1시간짜리 빨간 PR을 “느린 컴파일”로 보면 타임라인에는 대기 52분 · 실행 11분이 전형적으로 나타납니다.
잘못된 처방: Xcode 올리기, timeout-minutes 늘리기, GitHub 분 구매. Queued 시간은 설정한 타임아웃에 안 잡히는 경우가 많아 타임아웃만 늘려도 대기열은 그대로입니다.
측정은 작업 실행 시간(queue_wait_seconds vs 실행 시간)으로 합니다. 주간 P95를 보면 금요일 머지 러시 전에 Trigger Explosion을 잡을 수 있습니다.
| 대기 시간 | 실행 시간 | |
|---|---|---|
| UI | Queued | In progress |
| 병목 | GitHub Actions macOS runner queue / self-hosted runner queue | Xcode · 서명 · 업로드 |
| 잘못된 대응 | 분 추가 · 새 Xcode | 캐시(→ #8) |
Slack에 “CI 느려요”가 올 때 UI 배지부터 보세요. Queued가 긴데 DerivedData를 의심하는 건 관측이 실행 단계에 치우친 탓입니다. Runbook에는 대기와 실행을 반드시 분리해 기록합니다.
4 · 설명: CI Queue Failure 3 Layer Model
wait time >> run time이면 원인은 아래 SRE형 레이어 중 하나(또는 겹침)입니다.
| 레이어 | 의미 | 신호 | 오늘 할 일 |
|---|---|---|---|
| Capacity Limit | 플랫폼 · macos-latest |
호스티드만 macOS runner queued. 분 ≠ 동시 실행 | fanout 축소. Archive 호스티드 이전 → #3 |
| Pool Misdesign | 아키텍처 · 셀프호스트 | PR에 Archive. fast/archive 공유. 1대 점유 시 전부 Queued | Hard Rules → #1 #6 |
| Trigger Explosion | 부하 · 워크플로 fanout | matrix / paths / 중복 workflow. job > runner | 트리거 조이기 → #5 |
Capacity Limit — 호스티드 macOS 러너와 조직 macOS 동시 실행 상한(한도)에 부딪힙니다. Pool Misdesign — 흔한 근본 원인은 L2가 PR에 있고 풀이 섞인 경우입니다. Trigger Explosion — 하드보다 YAML을 고칩니다. (Failure 레이어 ≠ job tier L0/L1/L2.)
세 레이어는 배타적이지 않습니다. 금요일 저녁 matrix 급증(Trigger) + Archive가 fast 풀 점유(Pool) + 호스티드 한도 소진(Capacity)의 “삼중고”는 20인 팀 사례 #5에서도 자주 재현됩니다. 신호 열부터 가르세요.
5 · 수정: CI Hard Rules (MUST NOT 위반 금지)
Runbook용 규범 문구입니다. 제안이 아니라 위반은 인시던트로 다룹니다.
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) # Mapping PR → L0 + L1 only →macos-fastmain → L2 only →macos-archive
Job tier 참고(Hard Rules 구현용):
| Tier | 작업 | 풀 | 규칙 |
|---|---|---|---|
| L0 | 모듈 빌드, lint, 경량 테스트 | macos-fast |
Rule 1 · PR 허용 |
| L1 | PR 통합, 시뮬레이터 테스트 | macos-fast |
Rule 1 · Archive MUST NOT |
| L2 | Archive, IPA, TestFlight | macos-archive |
Rules 2+3 · PR MUST NOT |
“PR에서 Archive 한 번만”은 예외처럼 보여도 38분 L2가 fast 라벨을 잡아먹으면 리뷰어 전원의 L0/L1이 Queued 됩니다. 예외 운영은 풀 설계를 깨므로 nightly/main 전용 job으로 옮기세요.
그림 1 · Pool Misdesign: L2가 슬롯 점유 → 모든 macOS job queued
jobs: pr-fast: if: github.event_name == 'pull_request' runs-on: [self-hosted, macos-fast] release-archive: if: github.ref == 'refs/heads/main' || github.event_name == 'schedule' runs-on: [self-hosted, macos-archive]
러너 토폴로지: 탄성 풀 vs 상시 온 매트릭스 · #1 사이징 · 큐 정상화 후 → #3.
6 · Runbook: 한 페이지(온콜)
0. CI queue problem ⇔ wait time >> run time ? ─No→ #8
1. Layer: Capacity | Pool Misdesign | Trigger Explosion
2. MUST: Rule1 PR no L2 · Rule2 L2 isolated · Rule3 fast unblocked
3. PR workflow has no archive/export/upload ?
4. wait P95 < 8min → then size runners (#3)
한 줄: macOS CI queued 이슈 대부분은 PR 경로의 L2이지, 하드 부족이 아닙니다. macOS runner queued = 풀 포화. wait >> run → Failure Model → Hard Rules 순으로 적용하세요.
사후 회고에서 “대수만 늘렸다”면 재발합니다. Rule 1~3이 리포에 있는지, PR 템플릿에 Archive job 추가 여부를 체크하면 재발률이 내려갑니다.
7 · FAQ
macOS 러너는 왜 더 자주 큐에 걸리나요
Capacity Limit(동시 실행 상한)과 Pool Misdesign(PR의 L2) 조합입니다. 먼저 wait >> run으로 판정하세요.
큐 문제인가, 느린 빌드인가
CI queue problem ⇔ wait time >> run time. true → 풀. false → #8.
셀프호스트인데도 queued인 이유
Pool Misdesign: self-hosted runner queue는 라벨·풀 배치를 따릅니다. Hard Rules 위반은 클라우드 Mac으로 안 고쳐집니다. YAML을 고치세요.
GitHub Actions 분 vs concurrency
minutes는 과금, concurrency는 동시 슬롯입니다. 분을 늘려도 macOS runner queued는 안 풀립니다.
러너 풀 문제인가요
wait >> run + 셀프호스트 1대 장시간 점유 → Pool Misdesign. macos-latest만 queued → Capacity Limit. Runbook 참고.
큐는 괜찮은데 빌드만 느림
#8(속도). 사이징: #1. 팀 성장: #5.
풀 분리 검증: fast 풀 PoC가 필요하신가요
Hard Rules 적용 후 호스티드 GitHub Actions macOS runner queue에서 L0/L1을 빼려면 macos-fast 러너 1대로 wait time P95 < 8분을 확인한 뒤 macos-archive를 #1에 맞게 추가하세요.
사무실 네트워크 변경 없이 일 단위 분리 fast 풀을 쓰려면 Mac 클라우드 플랜 또는 VPSSpark 홈을 참고하세요(토폴로지 검증용, 규칙 준수는 별도). TCO는 시리즈 #3.