效能 2026 年 4 月 15 日

2026-04-15:租賃雲端 Mac CI 上的 Xcode 衍生資料、TMPDIR 與 xcresult 隔離

MacXCode 技術團隊 2026 年 4 月 15 日 約 13 分鐘閱讀

行動釋出團隊在香港、東京、首爾、新加坡與美國東岸租賃Mac mini M4建置機時,常把「Xcode 很慢」怪到編譯器,而真正的瓶頸多半是檔案系統爭用:兩條流水線寫入同一段 DerivedData、某個 UI 測試把/tmp塞滿,或.xcresult在沒有配額時膨脹到6 GB以上,讓並行封存搶不到 NVMe 寫入頻寬。本篇以2026-04-15為時間戳,先界定誰需要依作業隔離,整理無頭 SSH 主機上可重現的失敗樣態,用表格比較目錄策略,提供帶數字的八步 Shell 手冊,並串連SwiftPM 快取與註冊表解析自動與手動簽章兩篇姊妹文,讓下一次跳轉仍可預測、可稽核。

在進入細節前,先釐清:Apple Silicon裸機能壓低 CPU 抖動,但 NVMe 寫入吞吐與 inode 壓力仍是硬上限。若所有作業仍共用~/Library/Developer/Xcode/DerivedData,「偶發紅燈建置」在統計上幾乎不可避免。把DERIVED_DATA_PATHTMPDIR-resultBundlePath與 SwiftPM 的-clonedSourcePackagesDirPath綁到同一作業前綴,才能把容量規劃從猜測變成可量測的 IO 預算,並在比較不同區域節點時給出可信依據。

誰該在雲端 Mac 上依作業拆分 DerivedData

當你的佇列在滾動24 小時視窗內,可能出現三條以上並行的xcodebuild程序碰觸同一工作區家族(單體儲存庫、多 scheme、或共享二進位產物)時,隔離就值得投入。主幹式 iOS 團隊與白牌工廠尤其典型:夜間常疊加 UI 測試、封存與靜態分析。若所有作業沿用預設路徑,你會看到SwiftEmitModule隨機失敗、模組圖陳舊、XCTest 附件遺失。把暫存 IO 收斂到可預測的每作業樹,不只讓trap清理變簡單,也能讓平台團隊在評估是否增租第二台 M4 時,先看磁碟曲線而不是靠直覺。

無頭 SSH 建置機上我們反覆看到的故障

  • 邊建邊索引——作業 A 的背景索引寫入與作業 B 正在讀取的索引片衝突,表面症狀是「找不到型別」直到徹底清理。
  • 共享 TMPDIR——SwiftPM 與 clang 會留下大量小檔;另一條流水線的清理腳本若依前綴刪除,可能刪到兄弟程序仍開啟的路徑。
  • xcresult 無配額——UI 測試預設錄影與截圖;三套套件疊加後,單一包體可輕易超過8 GB
  • 封存與測試交錯——夜間先在共享 DerivedData 上封存,又立刻跑整合測試;封存若半途中止,索引與模組快取更容易半寫入。
黃金法則:DERIVED_DATA_PATHTMPDIR、結果包路徑,以及 SwiftPM 的-clonedSourcePackagesDirPath視為同一作業前綴;在入隊時建立,在trap EXIT裡銷毀(長駐代理需先上傳再刪)。

隔離策略比較

方案 優點 缺點 適用情境
/Volumes/builds下為每作業設定DERIVED_DATA_PATH 清理確定;可按流水線配額 無暖快取時首編較慢 共享 Mac mini M4 的並行 CI
唯讀暖快取 + 疊加複製 冷啟動編譯較快 rsync 複雜;純 SSH 主機較難維運 Xcode 小版本一致的釋出列車
拆分 macOS 使用者帳號 簽章身分硬隔離 維運與授權成本較高 受規管的多租戶混跑

NVMe 配額:可直接貼進手冊的數字

產物 典型穩態 UI 測試尖峰
DerivedData(中型 App,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 的八步執行手冊

  1. 打戳——匯出JOB_ID=$(date +%s)-$RANDOMROOT=/Volumes/builds/$JOB_ID;建立"$ROOT/DerivedData" "$ROOT/tmp" "$ROOT/results"
  2. 固定環境——export DERIVED_DATA_PATH="$ROOT/DerivedData"export TMPDIR="$ROOT/tmp",並同步TEMPTMP
  3. trap 清理——確認無其他作業重用同一JOB_ID後註冊trap 'rm -rf "$ROOT"' EXIT;長駐代理需先歸檔結果。
  4. xcodebuild——測試加-resultBundlePath "$ROOT/results/Test-$JOB_ID.xcresult";封存仍把 IPA 推到物件儲存,細節見IPA 匯出與上傳
  5. SwiftPM——傳入-clonedSourcePackagesDirPath "$ROOT/spm",並與上文 SwiftPM 文章的註冊表鑑權一致。
  6. 並行上限——在24 GB記憶體主機上,重型 UI 測試建議≤2路並行;腳本無法約束時由佇列互斥。
  7. 上傳——以ditto -c -k --sequesterRsrc --keepParent壓縮 xcresult,校驗雜湊後再刪本地 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。推送到 Prometheus、CloudWatch,或寫入 syslog 由 Vector 擷取皆可。若 DerivedData 中位數體積週環比上升超過18%,通常代表有人關閉增量編譯或新增重型程式碼產生,而不是立刻需要加機器。

同時把 Xcode 建置號(xcodebuild -version)與JOB_ID打到同一行日誌。同一台主機在16.216.3之間切換時,索引碎片不相容會放大尾部延遲;把升級與指標尖峰對齊,可避免把問題誤判為「雲端供應商不穩」。若你夜間執行xcrun simctl delete unavailable,請避開高峰編譯視窗,以免與作業佇列爭用同一條 NVMe 佇列。

銜接 SwiftPM 與簽章紀律

拆分 DerivedData 不能取代描述檔與憑證治理。若團隊每週輪換憑證,仍應依自動與手動簽章流程操作,避免codesign在無介面環境等待人工。若透過 SwiftPM 引入二進位相依性,請把Package.resolved納入版本控制,並在每作業目錄下鏡像檢出快取——全域SourcePackages與依作業 DerivedData 混用仍可能在鎖檔上競態。

常見問題:租賃 Apple Silicon 上的 DerivedData

問題 實務答案
是否應把 DerivedData 軟連結到 NFS? 除非往返延遲穩定低於2 ms且能接受較慢的索引;更穩妥是本地 NVMe + 產物上傳。
Xcode Server 舊版面是否仍相關? 現代 CI 可忽略/Library/Developer/XcodeServer;務必在腳本中顯式傳路徑。
Rosetta 情境要注意什麼? 避免 x86_64 模擬器與 arm64 前綴混用;依架構拆分目錄,降低胖二進位重建風暴。

為何區域化的 Mac mini M4 仍然重要

目錄隔離解決的是軟體層競態,但網路物理距離仍在:東京工程師從美國東岸拉 Git LFS 或符號表,RTT 會以分鐘級等待呈現。把建置機放在 SCM、測試人員與稽核日誌同一大洲,才能把/Volumes/builds的價值吃滿。MacXCode 在香港/日本/韓國/新加坡/美國的佈局,正是為了讓磁碟與網路同時贴近你的合規邊界。請先閱讀說明中心的 SSH 基線,再在連續14 個夜間視窗磁碟指標都健康後,才放大並行。

結論:依作業的DERIVED_DATA_PATH + TMPDIR + 明確xcresult路徑,把「隨機紅燈建置」變成可量測的 IO 預算;隨後你才能用定價頁的資料決定下季度該租幾台 Mac,而不是靠直覺拍板。

在裸機 M4 上擴展隔離建置池

香港 · 日本 · 韓國 · 新加坡 · 美國 · SSH / VNC