2026-04-15 임대 클라우드 Mac CI에서 Xcode DerivedData·TMPDIR·xcresult 격리
홍콩·도쿄·서울·싱가포르·미국 동부에서 Mac mini M4 빌더를 임대하는 모바일 릴리스 팀은 “Xcode가 느리다”고 컴파일러를 의심하기 쉽지만, 실제 병목은 파일 시스템 경합인 경우가 많습니다. 두 파이프라인이 같은 DerivedData 조각에 쓰거나, UI 테스트가 /tmp를 채우거나, .xcresult가 6 GB를 넘어 병렬 아카이브를 굶기는 식입니다. 본문(2026-04-15)은 누가 잡 단위 격리가 필요한지, 헤드리스 SSH 빌더의 실패 모드를 표로 비교하고, 숫자가 붙은 8단계 셸 런북을 제공하며 SwiftPM 레지스트리·캐시와 자동/수동 서명으로 연결합니다.
Apple Silicon 베어메탈은 CPU 지터를 줄이지만 NVMe 쓰기 처리량은 한정됩니다. 여전히 ~/Library/Developer/Xcode/DerivedData를 공유하면 SwiftEmitModule 실패와 오래된 모듈 맵이 통계적으로 발생합니다. DERIVED_DATA_PATH·TMPDIR·-resultBundlePath·-clonedSourcePackagesDirPath를 하나의 잡 접두사로 엮으면 용량 계획이 감이 아닌 IO 예산이 되고 리전별 노드 비교도 설명 가능해집니다.
잡별 DerivedData가 필요한 팀
롤링 24시간 안에 세 개 이상의 xcodebuild가 같은 워크스페이스 계열을 건드릴 수 있다면(트렁크 기반 iOS, 화이트라벨 공장) 격리의 ROI가 큽니다. 기본 경로를 공유하면 XCTest 첨부 손실과 인덱스 불일치가 주기 없이 나타납니다. 스크래치 IO를 예측 가능한 트리로 가두면 두 번째 M4를 늘릴지 디스크 곡선으로 판단할 수 있습니다.
헤드리스 SSH 빌더에서 보는 장애
- 빌드 중 인덱싱 — 잡 A의 백그라운드 인덱스가 잡 B가 읽는 샤드를 변형해 클린 전까지 타입 미발견.
- 공유 TMPDIR — SwiftPM/clang이 수많은 소파일을 남기고 형제 잡의 정리 스크립트가 열린 경로를 삭제.
- 쿼터 없는 xcresult — UI 테스트 동영상+스크린샷으로 세 스위트가 8 GB 초과.
- 아카이브+테스트 인터리브 — 같은 DerivedData에서 야간 아카이브 직후 통합 테스트 시 중단 시 손상 위험 증폭.
DERIVED_DATA_PATH, TMPDIR, 결과 번들 경로, -clonedSourcePackagesDirPath를 큐 시각에 만든 단일 잡 접두사로 취급하고 trap으로 삭제(상주 에이전트는 업로드 후).
격리 전략 비교
| 접근 | 이점 | 단점 | 언제 |
|---|---|---|---|
/Volumes/builds 아래 잡별 DERIVED_DATA_PATH |
결정적 정리, 파이프라인별 쿼터 | 웜 캐시 없으면 첫 빌드 느림 | 공유 Mac mini M4 병렬 CI |
| 읽기 전용 웜 + 오버레이 | 콜드 컴파일 가속 | rsync 복잡, SSH 전용 호스트에 어려움 | Xcode 마이너가 맞는 릴리스 트레인 |
| 별도 macOS 사용자 | 서명 신원의 강한 분리 | 운영·라이선스 비용 증가 | 규제 멀티테넌트 |
NVMe 예산(그대로 붙여넣기 가능)
| 산출물 | 전형적 정상 | UI 테스트 스파이크 |
|---|---|---|
| DerivedData(중형 Debug) | 6–14 GB | +3 GB 스크래치 |
| SwiftPM 체크아웃+빌드 | 1–4 GB | 새 태그 해석 시 +2 GB |
| xcresult 번들 | 단위 400–900 MB | 동영상 3–10 GB |
임대 클라우드 Mac 8단계 런북
- 스탬프 —
JOB_ID=$(date +%s)-$RANDOM,ROOT=/Volumes/builds/$JOB_ID, 디렉터리 생성. - 환경 고정 —
DERIVED_DATA_PATH,TMPDIR,TEMP,TMP정렬. - trap —
JOB_ID충돌 없음 확인 후trap 'rm -rf "$ROOT"' EXIT. - xcodebuild — 테스트는
-resultBundlePath "$ROOT/results/Test-$JOB_ID.xcresult". IPA는 객체 스토리지(IPA보내기). - SwiftPM —
-clonedSourcePackagesDirPath "$ROOT/spm"와 레지스트리 인증 일치. - 병렬 상한 — 24 GB RAM에서는 무거운 UI 테스트 ≤2, 큐 뮤텍스.
- 업로드 —
ditto -c -k --sequesterRsrc --keepParent로 zip, 체크섬 후 삭제. - 텔레메트리 — 전후
df -h. 벽시계 >42분이고 디스크 >90%면 첨부 폭주 의심.
export JOB_ROOT=/Volumes/builds/ci-$CI_PIPELINE_ID
mkdir -p "$JOB_ROOT"/{dd,tmp,res,spm}
export DERIVED_DATA_PATH="$JOB_ROOT/dd" TMPDIR="$JOB_ROOT/tmp" TEMP="$JOB_ROOT/tmp" TMP="$JOB_ROOT/tmp"
반나절이면 추가 가능한 메트릭
격리를 관측 없이 운용하면 의미가 없습니다. (1) 컴파일 후 du -sk "$ROOT", (2) xcodebuild archive 초, (3) /Volumes/builds 여유 GB를 반드시 남기세요. DerivedData 중앙값이 주간으로 18% 이상 증가하면 대개 증분 비활성화나 코드 생성 추가입니다. xcodebuild -version을 JOB_ID와 같은 줄에 남기고 16.2↔16.3 전환과 상관시키세요. xcrun simctl delete unavailable는 피크 밖으로.
SwiftPM과 서명 규율 연결
DerivedData 분리는 프로비저닝 위생을 대체하지 않습니다. 주간 인증서 교체 팀은 자동/수동 서명 절차를 따르고 codesign이 UI 대기에 멈추지 않게 하세요. Package.resolved를 버전 관리하고 전역 SourcePackages와 per-job DerivedData를 섞지 마세요.
FAQ: 임대 Apple Silicon의 DerivedData
| 질문 | 실무 답 |
|---|---|
| DerivedData를 NFS로 심볼릭 링크? | RTT가 2 ms 미만이 아니면 피하세요. 로컬 NVMe + 아티팩트 업로드가 안전. |
| 레거시 Xcode Server 레이아웃? | 현대 CI는 /Library/Developer/XcodeServer 무시, 항상 명시 경로. |
| Rosetta? | x86_64 시뮬레이터와 arm64 접두사 혼합 금지, 아키텍처별 분리. |
리전별 Mac mini M4가 여전히 중요한 이유
디렉터리 격리는 소프트웨어 경쟁을 제거하지만, 도쿄에서 미동부로 LFS를 당기는 RTT는 분 단위로 드러납니다. SCM·테스터·감사 로그와 같은 대륙에 /Volumes/builds를 두세요. 도움말의 SSH 기준을 읽고 디스크 텔레메트리가 14밤 연속 녹색일 때 병렬을 키우세요.
결론: per-job DERIVED_DATA_PATH + TMPDIR + 명시적 xcresult로 “무작위 빨강”을 IO 예산으로 바꾸고 가격 결정을 데이터로 내립니다.