GitHub Actions + Xcode로 돌리는 일반적인 iOS 프로젝트에서 iOS CI 빌드가 느린 것을 당연하게 받아들이는 팀이 많습니다. 하지만 핵심은 iOS CI slow ≠ Xcode slow이며, 원인은 파이프라인의 무상태 설계 + 캐시 부재 + 직렬 실행에 있습니다.
이 글은 8인 iOS 팀의 실제 사례(Swift 약 46만 줄, Pods 28개, SPM 14개, 하루 30회 이상 push)를 바탕으로 GitHub Actions macOS CI를 28분에서 9분으로 줄인 iOS CI optimization 전략을 정리합니다.
1. 문제 정의: iOS CI가 왜 느린가
많은 팀이 20~30분 대기를 '어쩔 수 없다'고 여깁니다. 병목은 Xcode가 아니라 CI 파이프라인 구조입니다.
1.1 GitHub Actions CI의 구조적 한계
GitHub Actions macOS runner는 ephemeral environment(일회성 환경)입니다. 매 실행마다 다음이 발생합니다:
- ❌ DerivedData 소실(incremental build 불가)
- ❌ CocoaPods 매번 재설치
- ❌ SPM 매번 재 resolve
- ❌ build context 재사용 불가
결과적으로 매번 cold build가 됩니다.
1.2 베이스라인 시간 분해(28분)
workflow_dispatch로 콜드 빌드 3회 실행 후 중앙값으로 각 단계를 측정했습니다:
| 단계 | 소요 시간 | 유형 |
|---|---|---|
| Checkout | 45s | 고정 |
| pod install | 3m 12s | 네트워크 의존 |
| SPM resolve | 1m 44s | 해석 |
| xcodebuild build | 11m 08s | cold compile |
| xcodebuild test | 6m 22s | CPU |
| xcodebuild archive | 5m 30s | 직렬 블로킹 |
| upload/sign | 1m 05s | 고정 |
| Total | 29m 46s |
핵심 병목: iOS CI가 느린 3가지 이유
- Cold Build(39%)——DerivedData 없이 46만 줄 Swift를 매번 전체 컴파일
- Dependency Re-resolve(17%)——Pods + SPM을 매번 다운로드·해석
- Serial Pipeline(19%)——test와 archive에 의존 관계가 없는데 직렬 실행
2. 최적화 목표와 결과
최적화 결과(iOS CI optimization 개요)
| 단계 | 빌드 시간 |
|---|---|
| baseline | 28 min |
| + cache | 20 min |
| + parallel | 12 min |
| + Apple Silicon | 9 min |
최적화 효과 분해
- Cache: 42%(약 8분 절약)
- Parallelization: 32%(약 6분 절약)
- Apple Silicon: 26%(약 3분 절약)
사례 배경(E-E-A-T)
| 항목 | 값 |
|---|---|
| Swift 코드량 | 약 46만 줄(테스트 포함) |
| CocoaPods / SPM | 28 Pods + 14 packages |
| 베이스라인 Runner | GitHub macos-latest(Intel, 4코어) |
| 팀 / 빈도 | 8인, 하루 30회 이상 push |
| 측정 방법 | 각 step에 타임스탬프, 콜드 빌드 3회 중앙값 |
단일 변수 실험(각 구성 10회 실행 후 중앙값): 캐시만 -29%, 병렬화만 -21%, Apple Silicon만 -18%. 구조 최적화(캐시 + 병렬화)가 74%를 차지하며, 하드웨어는 상한을 올리는 요소이지 1순위가 아닙니다. p95는 33분에서 12분으로, DerivedData 캐시 적중률 80%.
3. 최적화 ①: CI 캐시(최대 효과)
3.1 캐시가 핵심인 이유
iOS CI optimization의 본질은 CI에서 Xcode incremental build를 다시 살리는 것입니다. DerivedData, CocoaPods cache, Swift Package Manager cache 세 디렉터리를 영속화해야 합니다.
3.2 캐시 3요소
- DerivedData:
~/Library/Developer/Xcode/DerivedData - CocoaPods cache:
~/Library/Caches/CocoaPods - SPM cache:
~/.spm-cache
3.3 GitHub Actions 설정
- name: Cache DerivedData
uses: actions/cache@v4
with:
path: ~/Library/Developer/Xcode/DerivedData
key: deriveddata-${{ runner.os }}-${{ hashFiles('**/Podfile.lock','**/Package.resolved') }}
- name: Cache CocoaPods
uses: actions/cache@v4
with:
path: ~/Library/Caches/CocoaPods
key: pods-${{ runner.os }}-${{ hashFiles('**/Podfile.lock') }}
- name: Cache SPM
uses: actions/cache@v4
with:
path: ~/.spm-cache
key: spm-${{ runner.os }}-${{ hashFiles('**/Package.resolved') }}
3.4 캐시 key 설계 포인트
캐시 적중률이 DerivedData cache의 실효성을 결정합니다. key 설계에는 세 가지 대표 패턴이 있습니다:
| key 전략 | 적중률 | 문제 |
|---|---|---|
runner.os만 사용 | ~100% | 오래된 Pods를 참조해 산출물 불일치 가능 |
| 락 파일 해시(권장) | ~80% | 의존성 불변 시 적중, 업데이트 시에만 재구축 |
| commit SHA 포함 | ~0% | push마다 miss, 사실상 캐시 없음 |
Podfile.lock / Package.resolved 해시를 주 key로 쓰고 restore-keys로 접두사 매칭을 설정하세요. 주 key가 miss여도 이전 유효 캐시를 가져와 완전한 cold build를 피할 수 있습니다. CI에서는 pod install --no-repo-update로 lock 파일 변경에 따른 캐시 무효화를 방지하세요.
자세한 내용은 GitHub Actions cache 문서를 참고하세요. macOS runner 대기열 문제는 GitHub Actions macOS runner가 항상 밀리는 이유도 함께 확인해 보세요.
3.5 캐시 효과
| 항목 | 최적화 전 | 최적화 후 |
|---|---|---|
| pod install | 3m 12s | 50s |
| SPM resolve | 1m 44s | 15s |
| build | 11m | 3–4m |
평균 8~10분 절약.
4. 최적화 ②: CI 병렬화(Pipeline optimization)
4.1 문제
기본 CI는 build → test → archive 직렬 구조입니다. 그러나 test와 archive는 의존 관계가 없어 병렬 실행이 가능합니다.
4.2 병렬 Job 설계
jobs:
build-and-test:
runs-on: macos
steps:
- run: xcodebuild build test
archive:
runs-on: macos
if: github.ref == 'refs/heads/main'
steps:
- run: xcodebuild archive
archive는 main 브랜치에서만 실행합니다. 각 job은 독립적으로 캐시를 복원합니다. 인증서는 Fastlane Match로 관리하세요. 고빈도 CI 리소스 분리는 Cloud Mac 3대로 하루 500회 iOS CI 돌리기를 참고하세요.
4.3 효과
| 모드 | 시간 |
|---|---|
| serial | 20 min |
| parallel | 12 min |
5~7분 절약.
5. 최적화 ③: Apple Silicon Runner
5.1 왜 마지막에 하는가
하드웨어는 iOS CI slow의 1순위 해결책이 아닙니다. 캐시 없이 Apple Silicon으로 바꿔도 18% 정도. 구조 최적화 후에 도입해야 12분에서 9분으로 줄일 수 있습니다.
5.2 실측(캐시 + 병렬화 적용 후)
| 단계 | Intel | Apple Silicon |
|---|---|---|
| build | 3m 40s | 1m 55s |
| test | 6m 22s | 3m 48s |
| archive | 5m 30s | 3m 10s |
5.3 결론
추가 3~5분 절약. Apple Silicon은 Swift 컴파일과 링크 단계에서 효과가 가장 크며, Apple incremental build 문서도 참고하세요.
5.4 3가지 macOS Runner 비교
구조 최적화가 끝난 뒤 팀은 보통 다음 세 가지 GitHub Actions self-hosted runner macOS 방안 중에서 고릅니다:
| 방안 | 빌드 시간 | 대기열 | 운영 비용 |
|---|---|---|---|
| GitHub macos-latest | ~20 min | 높음(피크 시 10~20분 대기) | 없음 |
| 자체 Mac mini(Intel) | ~14 min | 없음 | 높음 |
| Cloud Mac(Apple Silicon) | ~9 min | 없음 | 낮음 |
GitHub 호스트 runner는 무료 할당이 있지만 대기열이 병목이 되기 쉽고, 캐시는 repo당 10GB 제한으로 대형 프로젝트는 evict가 잦습니다. 자체 runner는 대기열을 없애지만 Intel에는 한계가 있습니다. 하루 30회 이상 push하는 팀이라면 전용 Apple Silicon 노드의 ROI가 더 높은 경우가 많습니다——구조 최적화는 직접 하고, 하드웨어와 대기열은 별도로 평가하는 것이 현실적입니다.
6. iOS CI optimization 의사결정 트리
CI > 15 min?
↓
cold build > 40%?
→ YES: enable cache (DerivedData + Pods + SPM)
↓
pipeline serial?
→ YES: split jobs (test / archive)
↓
still slow?
→ upgrade Apple Silicon runner
재현 가능한 Runbook(5단계):
- 베이스라인 수립: 각 step에
echo "::notice::$(date -u +%H:%M:%S)"추가, 콜드 빌드 3회 중앙값 - 캐시 적용: DerivedData + Pods + SPM 설정, 10회 연속 적중률 측정(목표 > 60%)
- workflow 분리: test / archive 독립 job, archive는 main만
- 하드웨어 평가: 구조 최적화 후에도 > 12 min이면 Apple Silicon runner 검토
- p95 모니터링: 목표 ≤ 평균의 1.5배, 베이스라인 대비 +20% 시 알림
7. 흔한 오해(iOS CI Optimization Mistakes)
❌ Xcode incremental build in CI works automatically
틀렸습니다. CI에는 DerivedData가 남지 않으므로 명시적 캐시가 필수입니다.
❌ upgrade Mac solves CI slow
틀렸습니다. 캐시 없이는 개선이 제한적입니다(약 18%).
❌ increase -jobs always faster
CPU 코어 수를 넘기면 오히려 느려집니다. M2에서는 -jobs 6~8 권장.
❌ cache key should be exact
commit SHA는 매번 miss입니다. Podfile.lock / Package.resolved 해시를 쓰세요.
8. FAQ(GitHub Actions iOS CI 자주 묻는 질문)
Q1: GitHub Actions iOS CI가 왜 느린가요?
GitHub Actions macOS runner는 stateless ephemeral environment라 DerivedData, Pods, SPM 캐시를 보존하지 않습니다. 로컬에는 핫 캐시가 있어도 CI는 매번 cold build하므로, Mac에서 Xcode build time이 빨라도 CI만 느립니다.
Q2: iOS CI optimization에서 가장 효과적인 방법은?
DerivedData + CocoaPods + SPM 캐시로 42%(평균 8~10분)를 절약합니다. CI 환경에서 Xcode build time optimization의 최우선 과제입니다.
Q3: Apple Silicon만으로 CI slow를 해결할 수 있나요?
단독으로는 불가하며 가속 상한에 해당합니다. 캐시 없이는 18% 정도. 캐시와 병렬화와 함께 써야 12분에서 9분으로 줄일 수 있습니다.
Q4: Pods와 SPM 중 어느 쪽이 더 느린가요?
보통 CocoaPods가 더 느립니다(3m 12s vs 1m 44s). 네트워크 다운로드가 포함되기 때문입니다. 둘 다 캐시하면 효과가 누적됩니다.
Q5: 최적화 순서는?
Cache → Parallelization → Apple Silicon. 세 단계로 68% 단축. 순서를 건너뛰면 효과가 반감됩니다.
9. 결론
iOS CI가 느린 본질은 성능 문제가 아니라:
참고: 구조 최적화 후 전용 Apple Silicon self-hosted runner가 필요하고 하드웨어 운영을 피하고 싶다면 Cloud Mac 플랜(대기열 제로, 캐시 10GB/repo 제한 없음)을 검토해 보세요.