VPSSpark 블로그
← 개발 일기로

iOS 팀이 20명이 되면 CI/CD는 어디서 먼저 무너질까

서버 노트 · 2026.06.04 · 약 16분

iOS CI 빌드 대기열과 스케일링 장애를 검토하는 개발 팀
중형 iOS 팀 CI 리뷰: queue time vs 컴파일 시간

이 글은 iOS CI/CD Scaling Failure Model(확장 시 붕괴 모델)이며, 용량 계산기가 아닙니다. 인수인계 전, 팀은 14개월 만에 8명에서 20명으로 늘었지만 Runner 토폴로지는 거의 그대로였습니다. 그 결과 릴리스 주에는 PR이 전부 빨갛게 뜨고, 빌드는 오랫동안 Queued 상태에 머물렀으며, TestFlight는 이틀 늦어졌습니다. 아래에서는 “인원이 늘면 CI가 왜 느려지는지”, 병목이 pipeline 어디에 걸리는지를 정리합니다.

“하루 500회 빌드에 Mac 몇 대가 필요한가”가 궁금하다면 자매 글 Cloud Mac 3대로 하루 500회 iOS 빌드 지원을 보세요. 그쪽은 인수인계 후 구현 이야기이고, 본문은 인수인계 전에 왜 무너졌는지에 초점을 둡니다.

결론부터: 20명 규모에서 CI가 무너지는 이유를 “Mac이 부족하다” 한 마디로 정리하기 어렵습니다. 본 사례에서는 다섯 병목이 순서대로 겹쳤습니다. 동시 실행 한도 도달 → Runner와 개발 환경 경합 → PR에서 Archive 실행 → matrix Job 급증 → 서명 경합. 12~16명 구간에서 이미 징후가 있었고, 20명이 되어서도 큐를 나누지 않으면 Failure Zone에 들어갑니다.

한눈에 보기: CI가 “인원 증가”에서 Failure Zone으로 미끄러지는 방식

그림 1은 글 전체의 메커니즘 개요입니다. pipeline 세부보다 postmortem 도입이나 팀 onboarding에 적합합니다. 붕괴는 단일 지점이 아니라 아래 체인 위의 중첩입니다.

그림 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, 공증, 업로드. 해당 고객은 L1 컴파일이 아니라 Queue와 Runner 혼합 풀에서 무너졌습니다. matrix 팽창은 종종 “Xcode가 느려졌다”로 오독됩니다.

출발점: 8명일 때 “충분했던” CI가 왜 따라가지 못했나

2024년 중반 구성은 전형적이었습니다.

CTO 말은 “CI는 병목이 아니다. 여기에 시간 쓰지 마라”였습니다. 8명이면 성립합니다. 인원이 두 배가 되어도 CI는 보이지 않는 세금이 되었고, 용량 계획은 없이 feature team만 workflow를 쌓았습니다.

Healthy baseline: 무엇을 “정상”과 대조할까

20명 붕괴 주만 보면 “팀이 커지면 Mac을 많이 사야 한다”고 오판하기 쉽습니다. 여기서는 건강 기선 vs 붕괴 주 대조표를 보강합니다. 수치는 해당 고객 8명 구간 실측과, 당사가 맡았던 중형 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 1회 · macOS Job P99 < 6 19
macOS Job / 일 80~180 500+(릴리스 주)
Runner 토폴로지 L1/L2 라벨로 풀 분리 hosted + 사무실 mini 혼합

핵심 대조: 붕괴 주 run duration은 거의 그대로인데 queue time만 약 10배입니다. 문제는 컴파일 능력이 아니라 큐와 workflow에 있습니다. “CI가 느리다”는 표현이 팀을 가장 잘 오도하는 지점입니다.

타임라인: 세 번의 경보, 세 번 모두 “우연”으로 치부

1차(12명, iOS +4명): PR 머지 빈도가 하루 평균 6회에서 14회로. 호스팅 Runner의 build queue가 오후에 25~40분 대기. 팀 반응은 “오늘 GitHub가 이상한가”. queue_wait_seconds 곡선은 아무도 그리지 않았습니다.

2차(16명, 같은 repo를 쓰는 백엔드 +2명): 사무실에 두 번째 Mac mini 추가. 둘 다 self-hosted 등록, 라벨로 큐 분리 없음. Archive와 PR Build가 같은 풀을 다투며, 가벼운 Job이 8분에서 35분으로, 무거운 Job 공증이 무작위 실패. 반응은 “HDD 하나 더 사보자”.

3차(20명, 2 App 대형 버전 충돌): 릴리스 주 macOS Job이 하루 평균 120회에서 280+로. Slack #ios-ci가 24시간 멘션 체제. 반응은 “Mac mini 8대 조달 진행 중”——이것이 하루 500회 사례에서 본 8대안입니다. 당시 Job 항목별 계산은 없었습니다.

그림 3 · 팀 규모 vs macOS Job/일 vs 위험 구간(해당 고객 2024–2025 실측)

팀 인원 macOS Job / 일 GitHub Actions queue time(L1 P95) 위험 구간 단계 메모
8명 ~100 < 5분 Baseline 사무실 mini 1대로 충분
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명은 2 App 릴리스 주 피크. Job량은 인원보다 빠르게 증가하며, 주된 원인은 matrix 팽창과 PR Archive이지 단순 “인원 증가”가 아닙니다.

표의 두 가지: 16→20명에서 Job량 급증, 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가 개발에 점유되면 분모가 순간 할인됩니다. 아래 임의 2개가 동시에 성립하면 “Mac mini 한 대 더” 대신 pipeline 재설계를 권합니다.

  • queue_wait_seconds P95 > 30분 이고 Archive류 Job 비율 > 30% → L1/L2 분리 필수, PR 전량 Archive 중단
  • push 1회로 트리거되는 macOS Job 수 P99 > 15 → matrix/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가 느린 게 아니라 빈 슬롯 대기입니다. UI의 Queued 스피너는 네트워크나 Xcode 문제와 혼동되기 쉽습니다. 조사 시 Job 실행 시간과 큐 대기로 wait와 run을 나누세요.

12명 구간 queue P95는 이미 25~40분. 당시 fast/archive를 라벨로 분풀했다면( mini 2대 혼합이 아니라) 이후 서명 경합은 늦출 수 있었을 것입니다. 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가 “예비 개발기”화

엔지니어가 SSH하는 머신에 Runner를 올리면 결국 누군가 로컬에서 xcodebuild debug를 돌립니다. Runner와 일상 개발이 CPU/디스크를 다투며 타임아웃이나 간헐 compile error가 납니다. 16명 때 mini 2대는 “금요일 전멸, 월요일 회복”이 있었고, 원인은 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 matrix Job 지수 증가

20명 때 push 1회 최대 22개 macOS Job——인원은 2.5배, Job은 4배. 제품 페이스 아래 가장 잘 놓치는 붕괴 지점입니다.

⑤ Crypto / signing contention · 릴리스일 Keychain과 서명 경합

Self-hosted Mac 2대가 동시 Archive면 16GB에서도 간헐은 버팁니다. Match 해제, 공증, TestFlight 업로드가 겹치면 Keychain 잠금과 codesign 경합으로 같은 오류 코드가 무작위로 뜹니다. 릴리스 주 “인증서 없음” 4연속, 로컬 Mac 재실행은 성공——전형적인 다중 Job 서명 환경 경합이며, 인증서 만료가 아닙니다. 설정은 Apple Xcode Signing and Capabilities 참고.

시도한 “수정”이 왜 상황을 악화시켰나

GitHub 분수 패키지 추가 구매: 호스팅 큐만 완화, Archive 비율 불변. 비용은 들고 P95는 여전히 60분 초과.

사무실 세 번째 Mac mini: 여전히 fast/archive 라벨 없음, 3대 혼합으로 서명 실패율 상승.

금요일 머지 금지: PR 빈도를 인위 억제, 릴리스 주에 집중 폭발, 피크가 더 뾰족해짐.

각 Job에 timeout-minutes: 180: Queued 시간은 timeout에 포함되지 않아 더 많은 Job이 오래 슬롯 점유.

유일하게 유효했으나 미완인 시도: Release를 별도 머신으로——그러나 여전히 사무실 mini, 야간 Keychain 미해제, 월요일 L2 큐 적체.

모니터링에서 봐야 하는, 아무도 보지 않던 세 수치

붕괴 전 Grafana는 “성공률”과 “평균 시간”만 있었습니다. 인수인계 1주차에 추가한 세 지표로 80%를 특정(queue_wait_seconds 분해는 앞 절 GitHub 공식 모니터링 참고):

  • queue_wait_seconds P95(L0/L1/L2 버킷)——“컴파일 느림”과 “큐 대기” 분리
  • Archive류 Job 비율(주간)——workflow 설계 통제 불능 감지
  • push 1회 트리거 macOS Job 수 P99——matrix 지수 팽창

20명 붕괴 주: L1 queue_wait P95 47분, Archive 비율 38%, push 트리거 Job P99 19. 셋 중 둘 초과면 조달보다 토폴로지 변경을 먼저.

지혈 순서: 토폴로지를 고친 뒤 Mac 대수 논의

인수인계 후 2주는 신규 구매 없이 네 가지만 수행했습니다.

  • PR workflow에서 Archive 제거, L2는 nightly + release 브랜치만
  • path filter 강화, README/백엔드 변경이 iOS matrix를 트리거하지 않음
  • 사무실 mini에서 Runner 제거, 개발과 경합 회피
  • 호스팅 Runner는 L1 피크만, L2는 전부 전용 노드로

이 네 가지만으로 릴리스 주 queue_wait P95가 47분에서 22분으로——아직 부족하지만 붕괴 주원인은 토폴로지와 workflow이지 “Mac 대수 신비 공식”이 아님을 증명했습니다. 이후 Cloud Mac fast/release 분풀과 용량 검증. 탄력 vs 상주는 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 matrix 트리거
  • 릴리스 주 Job 피크와 조달/토폴로지 변경 미연동

Fixes tried(무효 또는 악화)

  • GitHub 분수 패키지 추가——hosted 큐만 완화, Archive 비율 불변
  • 사무실 세 번째 mini 혼합——서명 실패율 상승
  • 금요일 머지 금지——피크가 릴리스 주로 이동
  • timeout-minutes: 180——Queued는 timeout 대상 아님

What worked(인수인계 후 2주)

  • 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 재설계 우선.

확장 시 먼저 장비를 늘릴까, 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 1회 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 1회 실행 후 그림 3 건강 기선으로 queue P95가 < 8분 구간으로 돌아오는지 확인.

대수와 fast/release 분풀 알고리즘은 Cloud Mac 3대로 하루 500회 iOS 빌드; Runner 탄력 vs 상주는 GitHub Actions 자체 호스팅 macOS Runner 의사결정 매트릭스.

일 단위 PoC로 release 전용 노드 1대로 L2 isolation을 검증하려면 Mac 클라우드 호스트 요금제 또는 VPSSpark 홈에서 Archive 큐를 분리한 Cloud Mac을 사용할 수 있습니다. 이는 토폴로지 검증이며, 처음부터 풀 클러스터를 사는 단계가 아닙니다.

한정 혜택

20명 iOS CI 붕괴? 큐부터 분리

build queue · Archive 격리 · Cloud Mac Runner

홈으로
한정 혜택 요금제 보기