同じクラウドMac上でXcodeビルド、テスト、ログ圧縮、成果物の検証を同時に実行すると、特定のプロセスが完全に暴走するよりも、「多少遅れて完了してもよい」複数の補助タスクが重要なビルド処理とCPU、ディスク、メモリ帯域を奪い合うケースがよくあります。その結果、コンパイル時間が安定しない、テストがタイムアウトする、リモートデスクトップの応答が遅くなる、といった問題が発生します。この種の問題を解決するには、すべてのプロセスの優先度をやみくもに下げるのではなく、まずクリティカルパスとバックグラウンド処理を区別する必要があります。
比較可能なプロセスのベースラインを作る
最初に、ほかのタスクが動いていない状態で代表的なビルドを1回実行します。次に、圧縮、検証、インデックス作成を同時に動かした状態で同じビルドを繰り返します。どちらの場合も、ビルド時間、失敗した段階、主要プロセスの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プロセスは3種類に分類できます。1つ目はコンパイル、リンク、テスト、成果物のエクスポートです。これらはパイプラインが完了するかどうかを左右するため、通常の優先度を維持します。2つ目はログ圧縮、チェックサム生成、古い成果物の整理です。これらも完了させる必要がありますが、通常はビルドより先にリソースを確保する必要はありません。3つ目はエディタのインデックス作成、対話的な分析、一時的な診断です。これらは必要な場合にのみ起動します。
ツール名だけで一律に判断しない
同じツールでも、処理経路によって役割が異なる場合があります。たとえば、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を指定すると、補助タスクが重要な処理とスケジューリングリソースを奪い合うほか、優先度の逆転が発生する可能性もあります。たとえば、高優先度のタスクが、低優先度キューによって保持されているロックの解放を待つ状態です。共有ロック、シリアルキュー、同期呼び出しも確認対象に含めてください。
効果を検証し、ロールバック条件を設定する
調整後は、同じビルド入力で少なくとも3回繰り返し実行し、所要時間の中央値、失敗箇所、リソース計測結果を比較します。1回だけ速くなっても、ポリシーが有効だとは証明できません。また、ログ圧縮やチェックサム生成などのバックグラウンドタスクが最後まで完了していることも確認してください。「重要なビルドが成功した」という理由で補助タスクの失敗を見落としてはいけません。
受け入れ確認では、次の項目を順に確認できます。
- コンパイル、リンク、テストの各プロセスがバックグラウンドポリシーを継承していない。
- バックグラウンドタスクが失敗した場合、ゼロ以外の終了コードが返される。
- Runnerが同時に実行するパイプライン数が、ノードの処理能力を超えていない。
- 調整前後で同じコード、依存関係のロックファイル、Xcodeバージョンを使用している。
- リモートデスクトップの操作感だけで判断せず、プロセスとI/Oの計測結果を保存している。
- ノードの再起動後も、優先度を調整するラッパーがパイプラインスクリプトから自動的に適用される。
優先度を下げた結果、補助タスクが次回のビルドまで滞留するなら、問題はリソース競合からスループット不足へ変わっています。この場合はnice値をさらに上げるのではなく、バックグラウンドタスクの数を減らす、処理範囲を縮小する、またはビルド完了後の独立したステージへ移動してください。SureVMノードで選択できる具体的な構成はコンソールで確認する必要がありますが、どの構成を使用する場合でも、先にクリティカルパスを特定してからスケジューリングポリシーを変更する方が、全体を一律に調整するよりも検証とロールバックが容易です。
よくある質問
niceやtaskpolicyでCPU使用率の上限を設定できますか?
できません。これらはスケジューラの優先傾向を変える仕組みで、CPUの固定上限ではありません。負荷を制御するにはビルド並列数やバックグラウンド処理数も制限します。
バックグラウンド方針に向いているCI処理は何ですか?
ログ圧縮、チェックサム生成、古い成果物の整理などです。コンパイル、リンク、テスト本体と直接の依存処理は通常の優先度を維持します。
ビルド、開発、実験に使える専用物理Macノードを選択
Mac mini M4の2つの構成、4種類の契約期間、注文可能な5つのノードを比較し、実際のワークフローに合うプランを選べます。