2026-04-15 レンタル クラウド Mac CI における Xcode DerivedData・TMPDIR・xcresult の分離
香港・東京・ソウル・シンガポール・米東部でMac mini M4ビルダーを借りるモバイルリリースチームは、「Xcode が遅い」と Compiler を疑いがちですが、実際はファイルシステム競合が主因であることが多いです。2 本のパイプラインが同じ 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 時間で3 本以上のxcodebuildが同一ワークスペース系列に触れ得る場合(トランクベース iOS やホワイトレーベル工場)、隔離は投資対効果が高いです。既定パスを共有すると、XCTest 添付の欠落やインデックス不整合が周期性なく現れます。予測可能なツリーへスクラッチ IO を閉じ込めれば、第二台 M4 を増やすべきかをディスク曲線から判断できます。
ヘッドレス SSH ビルダーで見る故障
- ビルド中インデックス — ジョブ A のバックグラウンド索引がジョブ B の読み取り中に変異し、「型が見つからない」がクリーンまで続く。
- TMPDIR 共有 — SwiftPM/clang が大量の小ファイルを残し、兄弟ジョブの掃除が参照中プレフィックスを削除する。
- xcresult 無制限 — UI テストの動画とスクリーンショットで 3 スイートが8 GB超へ。
- アーカイブとテストのインタリーブ — 同一 DerivedData で夜間アーカイブ直後に統合テストを走らせると中断時の破損リスクが増幅する。
DERIVED_DATA_PATH、TMPDIR、結果バンドル、-clonedSourcePackagesDirPathをキュー投入時に作る単一のジョブプレフィックスとして扱い、trapで削除する(常駐エージェントはアップロード後)。
隔離戦略の比較
| アプローチ | 利点 | 欠点 | 用途 |
|---|---|---|---|
/Volumes/builds配下の per-job DERIVED_DATA_PATH |
決定的なクリーンアップ、パイプライン別クォータ | ウォームキャッシュ無しでは初回が遅い | 共有 Mac mini M4 の並列 CI |
| 読み取り専用ウォーム + オーバーレイ | コールドコンパイル高速化 | rsync が複雑、SSH のみでは運用が難しい | Xcode マイナーが揃うリリース列車 |
| macOS ユーザー分離 | 署名アイデンティティの硬い分離 | 運用コストとライセンス増 | 規制下のマルチテナント |
NVMe 予算(そのまま貼れる数値)
| 成果物 | 定常 | UI テストスパイク |
|---|---|---|
| DerivedData(中規模 Debug) | 6–14 GB | +3 GB スクラッチ |
| SwiftPM checkout + ビルド | 1–4 GB | 新タグ解決で +2 GB |
| xcresult | 単体 400–900 MB | 動画あり 3–10 GB |
レンタル クラウド Mac の 8 ステップ
- スタンプ —
JOB_ID=$(date +%s)-$RANDOM、ROOT=/Volumes/builds/$JOB_ID、必要ディレクトリをmkdir -p。 - 環境固定 —
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 プレフィックスを混ぜない。アーキテクチャ別に隔離。 |
リージョナル M4 が依然重要な理由
ディレクトリ分離はソフト競合を潰しますが、東京から米東へ LFS を引く RTT は分単位で効きます。SCM・テスター・監査ログと同一大陸に/Volumes/buildsを置いてください。ヘルプの SSH 基準を読み、ディスクテレメトリが14夜連続で緑なら並列を上げましょう。
まとめ: per-job DERIVED_DATA_PATH + TMPDIR + 明示xcresultで「ランダム赤」を IO 予算へ変換し、料金判断をデータ駆動にします。