磁碟水位 · DerivedData / CocoaPods / Gradle · 產物三層 · 六步 Runbook · 硬閾值
維運、行動端基礎建設與要為共享 Mac 建置池簽磁碟 SLO 的 Tech Lead常在週五晚上收到同一類告警:Runner 線上但 Job 因「No space left」失敗,DerivedData 佔滿系統磁碟區,CocoaPods 與 Gradle 全域快取無人認領,產物目錄在 rsync 成功後仍堆在本機。本文先界定誰遇到什麼問題:Mac Mesh 多租戶輪換下,磁碟不是「滿了再手工 rm」,而是缺可觀測水位 + 分層回收契約;再給出結論:用 L1 DerivedData / L2 依賴快取 / L3 CI 產物 三層水位線與六步 Runbook,把清理從救火變成可稽核例行;結構上交付五條隱性稅、清理策略對照表、水位腳本欄位、六步落地、三條硬閾值與 FAQ。席位與租約見 並發席位與互斥;映像漂移見 黃金映像清單;產物外送見 rsync 與物件儲存;池型容量見 三池 SLO 矩陣;多分支隔離見 Git worktree 隔離。
2026 年 Mac Mesh 工單裡,磁碟問題很少是「少買了 100GB」這麼簡單。更多是在輪換租戶、快取局部性與產物生命週期之間缺統一契約,導致 APFS 看起來還有空間,Xcode 卻已經在寫暫存檔時失敗。
DerivedData 無界共享:多儲存庫共用 ~/Library/Developer/Xcode/DerivedData,索引與模組快取按分支交錯,一次 clean 誤刪鄰居正在用的 ModuleCache,表現為隨機 link 失敗而非磁碟滿。
CocoaPods / Gradle 全域快取無 TTL:~/Library/Caches/CocoaPods 與 ~/.gradle/caches 只增不減;Pods 版本升級後舊 tarball 仍佔數 GB,且與 worktree 多分支 並行時放大爭用。
產物「已上傳仍留本機」:Job 在物件儲存側已成功,但 $CI_ARTIFACTS_DIR 未掛保留策略,與 rsync 完成鉤子 未綁定,磁碟被 IPA/dSYM 慢慢吃光。
APFS 快照與「可用」誤導:本機快照讓 df 顯示餘量充足,真實可寫空間在編譯峰值時擊穿;缺少按磁碟區、按層的 waterline_used_pct 指標。
清理與席位鎖競態:租約未釋放就掃目錄,或與 席位鎖 TTL 衝突,造成「磁碟清了、建置卻紅」的二次事故。
交付物應是:三層目錄字典、warn/hard 雙水位、按租約結束的 LRU、與黃金映像漂移週檢解耦。缺任一,就不應在共享池上承諾「任意 monorepo 可並行」。下一節用對照表比較三種常見清理哲學,避免「每週五全員 ssh 上去 rm -rf」。
磁碟治理不是越狠越好,而是要在建置命中率、清理可稽核性與租戶隔離之間取平衡。請把下表貼在變更評審頁:每一層(L1/L2/L3)只允許勾選一種預設策略。
| 策略 | L1 DerivedData | L2 Pods/Gradle | L3 產物 | 適合 | 主要風險 |
|---|---|---|---|---|---|
| 手工 cron | 週末 rm 全域目錄 | 偶發 pod cache prune | 按天數 find 刪除 | 極小團隊、低並行 | 誤刪鄰居、不可稽核 |
| 水位守護行程 | 按 workspace 雜湊 LRU | 容量觸線 evict | rsync 成功後 48h | 共享池預設 | 需指標與鎖契約 |
| 映像重置 | 快照回滾清空 | 隨映像刷新 | 磁碟區級替換 | 漂移失控、合規快照 | 冷啟動編譯變慢 |
選型底線:共享池預設應選「水位守護」;映像重置只配合 黃金映像漂移清單 做季度兜底,不能取代日常 LRU。
當 Dedicated 獨占池 與 Shared 輪換並存時,L1 快取鍵必須帶池型標籤,否則獨占機的局部性收益會被共享池清掃腳本誤傷。
L1:/var/mesh/cache/deriveddata/{workspace_hash},綁定 Xcode DERIVED_DATA_DIR。L2:/var/mesh/cache/cocoapods、/var/mesh/cache/gradle,禁止寫回使用者主目錄全域快取。L3:/var/mesh/artifacts/{job_id},上傳成功後僅保留校驗旁路檔案。這樣監控可以按層回報 layer_*_bytes,而不是只有一個模糊的「/ 分割區 85%」。
以下六步假設 Runner 已接入 Mac Mesh 標籤,且席位在 Job 開始前 acquire、結束後 release。順序不要跳:沒有指標的水位線等於盲刪。
凍結三層字典與路徑:把 L1/L2/L3 根目錄、warn(82%)/ hard(92%)閾值寫入儲存庫 mesh-disk-policy.yaml,並在 映像清單 中登記預設掛載點。
部署 disk-waterline 探針:每 60s 採集磁碟區使用率與各層位元組數,上報 Prometheus/OpenTelemetry;hard 觸線時 Runner 進入 drain 並 fail-fast 新 Job。
隔離 DerivedData:CI 注入 DERIVED_DATA_DIR 指向 workspace 雜湊桶;租約結束觸發該桶 LRU,禁止掃全域 DerivedData。
L2 依賴快取 evict:CocoaPods 用 pod cache clean 封裝為「按容量」而非「按時間」;Gradle 啟用 GRADLE_USER_HOME 指向 mesh 目錄並限制 max-cache-size。
產物與 rsync 鉤子:物件儲存 multipart 完成事件回呼刪除本機 L3;失敗重試保留至 7 天,欄位與 產物 Runbook 對齊。
週檢與演練:對照黃金映像 checksum、模擬 90% 水位下 Job 拒絕、記錄清理稽核日誌;與 Burst 溢出 聯動時先清 L3 再接納可中斷 Job。
hostname pool_type volume_mount waterline_used_pct waterline_warn_threshold waterline_hard_threshold layer_l1_deriveddata_bytes layer_l2_cocoapods_bytes layer_l2_gradle_bytes layer_l3_artifacts_bytes seat_lease_id last_cleanup_ts_unix cleanup_evicted_bytes_1h disk_waterline_hard_stop
提示:探針輸出應作為 Grafana 面板的第一行,而不是僅依賴系統告警。把 cleanup_evicted_bytes_1h 與成功建置數同圖,可區分「真清理」與「建置變少所以磁碟看似下降」。
磁碟告警與 佇列 SLO 症狀常重疊。先用下表定位是容量、快取鍵還是產物堆積,再決定清掃範圍。
| 症狀 | layer_* 主導 | 可能根因 | 優先動作 |
|---|---|---|---|
| 僅 Xcode 步驟失敗 | L1 高 | DerivedData 串味或索引損壞 | 按 workspace 雜湊清桶 |
| Android/iOS 混合池慢 | L2 高 | Pods/Gradle 無 evict | 收緊 L2 容量上限 |
| 上傳成功仍滿 | L3 高 | rsync 鉤子未綁 | 補物件儲存回呼 |
| df 正常但寫入失敗 | 快照 | APFS 本機快照 | 減快照保留 + 探針 |
注意:不要在持有 席位鎖 時執行磁碟區級 rm -rf。清理腳本必須檢查 seat_lease_id 為空或租約已過期,否則會把正在編譯的 ModuleCache 一併刪掉。
若 L1 在清桶後 24 小時內再次頂滿,優先審查是否缺少 worktree 隔離 導致同一節點並行多份全量 DerivedData,而不是繼續加碟。
下列數值來自多個 16GB/24GB 共享池現場的折衷,寫入變更單後可作為對外 SLO 的附件;獨占池可把 warn 下調 5 個百分點以換取更穩定的索引熱快取。
waterline_warn_threshold=82 觸發 L3→L2→L1 順序 evict;waterline_hard_threshold=92 拒絕新 Job 並打 disk_waterline_hard_stop=1。在 512GB 系統磁碟區、約 60% 預留給 mesh 的現場,L2 合計建議封頂 80GB(CocoaPods 與 Gradle 各 40GB 軟上限),L3 單 Job 目錄軟上限 12GB(含 dSYM)。把「僅 cron 週末清掃」或「全員 ssh 手工刪快取」當作長期方案時,往往缺少稽核欄位與席位契約,誤刪鄰居、編譯冷啟動激增、產物半寫入會在發布週集中爆發。對於要把 iOS/Android CI 與磁碟 SLO 落在可合約化雲端 Mac Mini 的團隊,VpsMesh 的 Mac Mini 雲端租用通常是更優解;方案見 定價頁面,接入與水位腳本樣例見 說明中心,訂購見 訂購頁面。
預設按 workspace 雜湊分桶 並綁定席位租約,租約結束觸發該桶 LRU。多分支並行見 worktree 隔離文;勿讓全域 ~/Library/Developer/Xcode/DerivedData 無界共享。
Runner 應 fail-fast 並上報 disk_waterline_hard_stop,避免半寫入產物;排程層把 Job 路由到有餘量的節點或觸發 Burst。席位語意見 並發席位文。