성능 2026년 4월 15일

2026-04-15 임대 클라우드 Mac CI에서 Xcode DerivedData·TMPDIR·xcresult 격리

MacXCode 엔지니어링 팀 2026년 4월 15일 약 13분

홍콩·도쿄·서울·싱가포르·미국 동부에서 Mac mini M4 빌더를 임대하는 모바일 릴리스 팀은 “Xcode가 느리다”고 컴파일러를 의심하기 쉽지만, 실제 병목은 파일 시스템 경합인 경우가 많습니다. 두 파이프라인이 같은 DerivedData 조각에 쓰거나, UI 테스트가 /tmp를 채우거나, .xcresult6 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
여유: 잡 볼륨은 최소 35% 여유 유지. 여유가 50 GB 미만이면 신규 잡을 멈추고 36시간보다 오래된 접두사만, 실행 PID와 무관하게 청소하세요.

임대 클라우드 Mac 8단계 런북

  1. 스탬프JOB_ID=$(date +%s)-$RANDOM, ROOT=/Volumes/builds/$JOB_ID, 디렉터리 생성.
  2. 환경 고정DERIVED_DATA_PATH, TMPDIR, TEMP, TMP 정렬.
  3. trapJOB_ID 충돌 없음 확인 후 trap 'rm -rf "$ROOT"' EXIT.
  4. xcodebuild — 테스트는 -resultBundlePath "$ROOT/results/Test-$JOB_ID.xcresult". IPA는 객체 스토리지(IPA보내기).
  5. SwiftPM-clonedSourcePackagesDirPath "$ROOT/spm"와 레지스트리 인증 일치.
  6. 병렬 상한24 GB RAM에서는 무거운 UI 테스트 ≤2, 큐 뮤텍스.
  7. 업로드ditto -c -k --sequesterRsrc --keepParent로 zip, 체크섬 후 삭제.
  8. 텔레메트리 — 전후 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 -versionJOB_ID와 같은 줄에 남기고 16.216.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 예산으로 바꾸고 가격 결정을 데이터로 내립니다.

베어메탈 M4에서 격리 빌더 확장

HK · JP · KR · SG · US · SSH / VNC