SureVM 工程筆記

雲端 Mac CI 行程優先級治理:用 QoS、nice 與 taskpolicy 降低任務干擾

雲端 Mac CI 行程優先級治理:用 QoS、nice 與 taskpolicy 降低任務干擾

同一台雲端 Mac 同時執行 Xcode 建置、測試、日誌壓縮與產物校驗時,最常見的問題並非某個行程完全失控,而是多個「稍晚完成也無妨」的輔助任務,與關鍵建置工作爭用 CPU、磁碟及記憶體頻寬。結果通常會表現為編譯時間不穩定、測試逾時,或遠端桌面反應遲緩。要解決這類問題,第一步不是盲目降低所有行程的優先級,而是先區分關鍵路徑與背景路徑。

先建立可比較的行程基準線

先在沒有其他任務的情況下執行一次具代表性的建置,再於壓縮、校驗和索引同時運作時重複執行。兩次都要記錄建置耗時、失敗階段,以及主要行程的 CPU、記憶體、狀態和 nice 值。

mkdir -p "$HOME/ci-observe"

ps -axo pid,ppid,user,%cpu,%mem,state,nice,etime,command \
  | sort -k4 -nr \
  | head -n 40 \
  > "$HOME/ci-observe/processes.txt"

iostat -w 2 -c 15 > "$HOME/ci-observe/iostat.txt"
vm_stat 2 15 > "$HOME/ci-observe/vm-stat.txt"

取樣應涵蓋實際工作區與真實的相依套件規模,但不要把首次下載相依套件與穩定建置混入同一條基準線。如果建置時間變長、iostat 持續繁忙,且壓縮行程明顯占用 CPU,就有充分理由調整輔助任務;如果主要問題是記憶體壓力或測試並行度過高,單純修改優先級通常無法奏效。

優先級是一種排程提示,不是資源上限。它適合處理「誰先執行」的問題,但不能取代並行控制,也無法保證某項任務取得固定比例的 CPU。

依關鍵路徑為任務分級

CI 行程可分為三類。第一類是編譯、連結、測試與產物匯出;這些工作決定流水線能否完成,應維持正常優先級。第二類是日誌壓縮、校驗和產生及舊產物整理;這些任務必須完成,但通常不需要搶在建置之前執行。第三類是編輯器索引、互動式分析與臨時診斷,只應在需要時啟動。

不要只按工具名稱套用規則

同一項工具可能出現在不同路徑中。例如,tar 在建置前解壓必要相依套件時屬於關鍵任務,但在建置後壓縮日誌時則屬於背景任務。判斷標準應該是:它是否會阻塞下一個步驟、失敗時是否必須終止流水線,以及輸出是否會立即由目前任務使用。

建議將分級直接寫入流水線設定,而不是仰賴操作者臨時執行。如此一來,審查腳本時便能直接看出哪些命令被降級,也能避免新任務預設繼承不合適的策略。

使用 nice 與 taskpolicy 包裝背景任務

nice 用於調整行程的排程優先值;正數表示更願意讓出執行機會。一般使用者可以降低自己所啟動行程的優先級,但不能任意將行程提升至更高優先級。macOS 的 taskpolicy -b 可將命令置於背景策略下執行,其影響不僅限於 CPU,因此只應套用於已確認不會阻塞關鍵路徑的任務。

以下腳本會讓校驗和以背景低優先級執行,同時讓 Xcode 建置維持正常策略,並分別保留兩者的結束狀態:

#!/bin/zsh

set -u
mkdir -p Artifacts Logs

taskpolicy -b nice -n 10 /bin/sh -c \
  'find Artifacts -type f -print0 | xargs -0 shasum -a 256 > Logs/checksums.txt' &
audit_pid=$!

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  build > Logs/xcodebuild.log 2>&1
build_rc=$?

wait "$audit_pid"
audit_rc=$?

if (( build_rc != 0 )); then
  exit "$build_rc"
fi

exit "$audit_rc"

不要對整個 CI Runner 統一套用 taskpolicy -b。這會連同編譯器、測試行程及其子行程一起降級,使整條流水線變慢,卻難以看出究竟是哪項任務造成影響。包裝應盡可能靠近單一背景命令。

在應用程式碼中正確使用 QoS

如果輔助工作由自行開發的 Swift 工具執行,應直接在程式碼層使用佇列 QoS,而不是在啟動後才猜測行程的職責。由使用者發起且會等待結果的工作可使用 .userInitiated;日誌整理、快取盤點等不需立即完成的工作可使用 .utility.background 只適合使用者不會等待,且延後完成也不影響目前結果的任務。

let queue = DispatchQueue(
    label: "com.surevm.ci.artifact-audit",
    qos: .utility
)

queue.async {
    auditArtifacts()
}

不要為了追求「更快」而將所有佇列都設為 .userInteractive。過高的 QoS 會讓輔助任務與關鍵工作爭搶排程資源,還可能造成優先級反轉:高優先級任務等待由低優先級佇列持有的鎖。共享鎖、序列佇列與同步呼叫都應納入檢查範圍。

驗證效果並設定回復條件

調整後,至少要以相同的建置輸入重複執行三輪,比較中位耗時、失敗位置及資源取樣。單次變快並不能證明策略有效。同時也要檢查日誌壓縮、校驗和等背景任務是否完整結束,不能用「關鍵建置成功」掩蓋輔助任務失敗。

驗收時可依序確認:

如果降級後的輔助任務積壓到下一輪建置,表示問題已從資源爭用轉變為輸送量不足。此時應減少背景任務數量、縮小處理範圍,或將其移至建置完成後的獨立階段,而不是繼續提高 nice 值。SureVM 節點的具體可選設定應以控制台為準,但無論使用哪一檔設定,先釐清關鍵路徑,再調整排程策略,通常都比全域修改更容易驗證與回復。

常見問題

nice 與 taskpolicy 能限制行程最多使用多少 CPU 嗎?

不能。兩者調整的是排程傾向與背景策略,不是硬性資源配額;若要限制負載,仍須控制建置並行數、測試分片及背景工作數量。

哪些 CI 工作適合套用背景策略?

日誌壓縮、校驗和產生、舊產物整理與非阻塞索引通常適合降級;編譯、連結、測試及其直接相依工作不應預設降級。

SureVM 雲端 Mac

選擇獨享實體 Mac 節點,滿足建置、開發與實驗需求

比較兩種 Mac mini M4 配置、四種租期與五個可訂購節點,依照實際工作流程做出合適選擇。

選擇租用方案