GitHub Actions에서 iOS CI를 돌릴 때 "Mac인데 왜 이렇게 느리지?"라는 말이 자주 나옵니다. 그런데 실제 병목은 CPU보다 큐 대기, 매번 초기화되는 의존성, 캐시 부재인 경우가 많습니다. 그래서 핵심 결론은 하나입니다. 진짜 가속 포인트는 self-hosted runner입니다. 전용 Mac mini에 디스크 상태를 유지하면 같은 저장소에서도 체감이 완전히 달라집니다.
이 글은 hosted macOS와 전용 Mac mini self-hosted를 같은 기준으로 비교해, 왜 전형적인 케이스에서 약 28분 -> 약 9분까지 줄어드는지 설명합니다(여러 프로젝트의 중앙값 샘플이며 보장 수치가 아닙니다). 비용/운영 비교는 Runner TCO 글, Flutter 파이프라인은 Flutter iOS CI, Xcode 자체 병목은 Xcode 빌드 지연 가이드를 같이 보면 더 빠르게 정리됩니다. 큰 맥락은 AI 연산 인프라 경쟁과도 연결됩니다.
1) "Mac인데 느리다"의 진짜 원인
먼저 오해부터 걷어냅니다. 느린 이유가 항상 "M 칩이 약해서"는 아닙니다. hosted macOS는 job이 끝날 때마다 환경이 초기화되기 때문에 pod install, SPM resolve, DerivedData가 계속 콜드 상태로 돌아갑니다. iOS CI가 매번 처음부터 다시 도는 느낌을 주는 이유가 여기 있습니다.
queued와 bootstrap 구간이 길다면 YAML 미세 튜닝보다 runner 운영 모델을 먼저 바꾸는 편이 효과가 큽니다.2) 28분은 어디서 소모되는가 (4구간 분해)
체감이 아니라 구간별로 보면 의사결정이 빨라집니다.
| 구간 | hosted macOS 기준 | 주요 원인 |
|---|---|---|
| queued | 3-12분 | 공용 풀 대기, macOS 동시성 경쟁 |
| bootstrap | 6-10분 | Pods/SPM 재다운로드, 콜드 캐시 |
| build/archive | 8-14분 | DerivedData 부재로 증분 이점 상실 |
| sign/upload | 3-6분 | 서명 재로딩, ASC 업로드 대기 |
핵심은 컴파일만 문제는 아니라는 것입니다. queue/bootstrap만 줄여도 전체 리드타임이 크게 감소합니다.
3) 진짜 가속 포인트: self-hosted runner
self-hosted의 이점은 CPU 스펙보다 같은 Mac, 같은 SSD, 같은 사용자 컨텍스트를 반복 사용한다는 점입니다. 즉 캐시를 재사용할 수 있다는 뜻입니다. 등록 및 운영 기본은 GitHub 공식 문서를 따르면 됩니다.
GitHub Actions iOS CI: self-hosted 구조
lint/test는 Linux로 분리
runs-on: [self-hosted, macos, ios-ci]
대기 최소화 + 영속 캐시
job 간 유지하면 바로 효과나는 캐시
- DerivedData
- ~/Library/Caches/CocoaPods
- SourcePackages/.build
- 서명용 keychain
4) Before / After (중앙값 샘플)
같은 수준의 중형 iOS 저장소에서 흔히 보이는 차이입니다.
| 구간 | hosted macOS | Mac mini self-hosted |
|---|---|---|
| queued | 7분 | 0분 |
| bootstrap | 9분 | 2분 |
| build/archive | 11분 | 4분 |
| sign/upload | 5분 | 3분 |
| 합계 | 약 28분 | 약 9분 |
다시 강조하면, 28->9는 보장 수치가 아니라 샘플 중앙값입니다. 그래도 방향성은 꽤 일관됩니다. queue 소멸 + warm cache가 동시에 작동하기 때문입니다.
5) 캐시 전략 (속도의 재현성 만들기)
self-hosted를 붙여도 매번 캐시를 지우면 hosted와 거의 동일해집니다. 운영 규칙은 간단합니다.
- DerivedData 경로 고정, 일반 job에서 전체 삭제 금지
- CocoaPods 캐시 (
~/Library/Caches/CocoaPods) 영속화 - SPM 캐시 (
SourcePackages,.build) 로컬 유지
Xcode 기본 참조는 Apple Xcode 문서를 확인하고, 팀에서는 "2회차 이후 시간이 실제로 내려가는지"를 필수 지표로 관리하세요.
6) workflow 최소 예시
iOS job을 self-hosted 라벨로 명확히 고정합니다. PR 검증과 릴리스는 분리하는 구성이 안정적입니다.
name: ios-release
on:
push:
branches: [main]
jobs:
archive:
runs-on: [self-hosted, macos, ios-ci]
concurrency:
group: ios-release-${{ github.ref }}
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- name: Build Archive
run: xcodebuild -scheme MyApp -configuration Release archive
7) 언제 전용 Mac이 이득인가
| 상황 | hosted 유지 | self-hosted 권장 |
|---|---|---|
| 월 1-2회 iOS 릴리스 | 가능 | 급하지 않음 |
| 주 1회 이상 릴리스 | 대기 비용 증가 | 도입 효과 큼 |
| PR/릴리스 시간대 충돌 | queue 급증 | 전용 label 분리 |
| Flutter + native 혼합 | 콜드 스타트 부담 큼 | 영속 캐시로 안정화 |
의사결정은 Runner TCO와 함께 보면 훨씬 명확해집니다. 요금은 pricing에서 확인하세요.
8) 48시간 검증 체크리스트
- self-hosted runner 등록 후
ios-cilabel 고정 - 같은 저장소로 3회 연속 실행 (cold 1회 + warm 2회)
- 4구간(queue/bootstrap/build/sign) 시간 전부 기록
- 2회차부터 bootstrap/build 하락 여부 확인
- 하락이 없으면 먼저 캐시 보존 경로부터 점검
이 절차로 효과가 확인되면, 다음 단계(2대 구성, 리전 분산)도 안전하게 진행할 수 있습니다.
9) FAQ (자주 묻는 8가지)
Q1. GitHub Actions iOS CI가 느린 건 Mac 성능 문제인가요?
대부분은 아닙니다. queue와 콜드 캐시가 핵심 원인이라 runner 모델 개선이 먼저입니다.
Q2. self-hosted runner는 보안상 괜찮나요?
private repo 기준으로 운영 가능합니다. fork PR 무조건 실행 금지, Secrets 분리, SSH 키 주기적 교체를 기본으로 두세요.
Q3. 28분에서 9분 단축은 항상 가능한가요?
보장할 수 없습니다. 본문 수치는 샘플 중앙값입니다. 프로젝트 크기와 기존 캐시 상태에 따라 차이는 달라집니다.
Q4. hosted와 self-hosted를 같이 써도 되나요?
네. Linux 검증은 hosted, iOS archive만 self-hosted로 나누는 방식이 가장 운영하기 쉽습니다.
Q5. Flutter 프로젝트에도 동일하게 적용되나요?
적용됩니다. flutter build ipa도 Pods/DerivedData 처리 방식이 속도를 좌우합니다. 자세한 흐름은 Flutter 글을 참고하세요.
Q6. Xcode 자체 최적화는 어디서 확인하나요?
빌드 설정과 의존성 정리가 우선입니다. 빠르게 점검하려면 Xcode 지연 가이드를 보세요.
Q7. 바로 월간으로 가는 게 맞나요?
먼저 48시간 실측을 권장합니다. 개선이 확인된 뒤 월간으로 전환하는 편이 리스크가 낮습니다.
Q8. 팀 단위로는 무엇을 기준으로 판단해야 하나요?
CI 분 단위뿐 아니라 대기 때문에 발생하는 개발자 시간 비용까지 포함한 TCO를 보세요. 큰 관점은 AI 인프라 글이 참고됩니다.
결론
GitHub Actions iOS CI가 Mac에서 느릴 때 먼저 의심할 것은 "Mac 모델"보다 "실행 모델"입니다. self-hosted runner + 영속 캐시를 적용하면 queue와 콜드 스타트를 동시에 줄일 수 있습니다.
가장 실전적인 경로는 전용 Mac mini에서 48시간 A/B 실측 후 본선으로 확장하는 것입니다. 요금과 도입 단계는 pricing을 기준으로 정리하면 됩니다.