1台の Mac がすべての UI テストを直列で回すと、モバイルリリース列車は止まりがちです。本稿ではシャーディングが効く条件、単一ノードと複数ノード戦略を1つのマトリックスで比較する方法、ランナーラベルと実行時間バジェットの定義、そして専用 Mac mini M4 クラウドへ XCTest UI 作業を分散する8つの具体手順と、予測可能なキュー計算までをまとめます。
セルフホスト GitHub Actions ランナーを標準化している場合、シャーディングは「速い Mac 1台」を、レビュアーが文脈を切り替える前に PR 検証を終えるフリートに変える技法です。ビルド並列度全般は CI/CD 向け並列 Mac ビルドノード も参照してください。
高性能 Mac 1台でも UI テスト SLA に届かない理由
Xcode はユニットテストの並列化に強い一方、UI テストはシミュレータ起動、トランジションアニメ、SpringBoard 待ちに時間を使います。1ホスト上では CPU コア数に比例して線形には伸びません。
- シミュレータ GPU 競合:単一 M4 で3つの UI スイートを同時実行すると、テストが暗黙に想定する 33 ms を超え、タップの不安定さや誤検知につながります。
- ディスク増幅:各シャードが DerivedData 書き込みを複製します。500 GB 超の SSD 余裕がないと、空き容量がおよそ 15% を下回った時点で並列ジョブが崩れます。
- キューの不透明さ:シャード単位のラベルがないと、遅い PR が UI 基盤待ちなのかコンパイル待ちなのか分からず、コンパイル用ランナーを過剰に増やしても UI 期限に間に合いません。
意思決定マトリックス:単一 Mac とシャードフリート
| 観点 | 単一 M4「全部やる」 | UI 専用シャード Mac |
|---|---|---|
| 90分 UI スイートのウォールクロック | 直列 ≈ 90 分 | 3シャードかつバランス良ければ ≈ 35–40 分 |
| フレーク感度 | 過負荷時に高い | マシンあたり1スイートなら低め |
| 運用複雑度 | 低 | 中。ラベルとダッシュボードが必要 |
| 最適ジオグラフィ | 任意 | 開発者近くに HK / JP / KR / SG / US でシャードを置く |
ハードを増やす前にテストをバケット化する
シャーディングはインフラ問題の皮を被ったスケジューリング問題です。実行時間のばらつきが近いバケットに分割し、各バケットが中央値の ±20% に収まるようにします。そうでないと毎回同じシャードだけが遅延要因になります。
- 履歴時間をエクスポート(Xcode Cloud、XCTest ログ、CI DB など)し、p95 実行時間でソートする。
- ログイン重いフローを専用バケットに分離し、独立できるチェックアウトや設定フローをブロックしない。
- 物理デバイスラボが要るテストは別タグ—クラウド Mac mini はシミュレータ向きで、USB ファームではない。
- バケットサイズを PR バジェット内に収める。まだ 25 分を超えるなら機能モジュールで再分割する。
- バケットマップを git で版管理(
ui-shards.json)し、再実行を再現可能にする。
ヒント:コンパイル用と UI 用シャードはラベルを分ける。混在すると重い xcodebuild test がシミュレータ用タイムアウト前提のマシンを奪い、キュー逆転を招く。
UI シャード向けランナーラベル契約
| ラベル集合 | 目的 | runs-on 例 |
|---|---|---|
| self-hosted, macOS, m4, ios-compile | ビルドとユニットのみ | [self-hosted, macOS, m4, ios-compile] |
| self-hosted, macOS, m4, ios-ui, shard-1 | UI バケット A | [self-hosted, macOS, m4, ios-ui, shard-1] |
| self-hosted, macOS, m4, ios-ui, shard-2 | UI バケット B | B/C/D… は同パターン |
GitHub Actions マトリックス:二重スケジュールを防ぐ
多くのチームはシャードをマトリックス次元で表現します。失敗パターンは shard: [1,2,3] と、すべての Mac にマッチする広い runs-on を同時に宣言することです。GitHub が複数シャードを1ホストに載せ、ハード投資が無駄になります。各脚を一意ランナーラベルに固定するか、ホスト名と1対1のリポジトリ変数を使ってください。
matrix:
shard: [1, 2, 3]
include:
- shard: 1
runner_labels: [self-hosted, macOS, m4, ios-ui, shard-1]
- shard: 2
runner_labels: [self-hosted, macOS, m4, ios-ui, shard-2]
- shard: 3
runner_labels: [self-hosted, macOS, m4, ios-ui, shard-3]
jobs:
ui-tests:
runs-on: ${{ matrix.runner_labels }}
timeout-minutes: 40
UI ジョブに shard-* ラベルがないワークフローを拒否するリポジトリルールを併用すると、善意のコントリビュータが並列度を潰すのを防げます。第2リージョン(例:シンガポール計算+東京レビュアー)に Mac mini を追加する際は、ラベル体系をリージョンごとに複製(shard-1-sg)し、ネットワーク近接を YAML で明示します。
クラウド Mac mini M4 で UI シャードを立ち上げる8ステップ
NodeMac の Mac mini M4 に SSH できる前提です。接続手順は ヘルプセンター を参照してください。
- N 台をプロビジョニング。N は目標シャード数にフレーク再実行用ホットスペア 1 台を加えた値。
- 同一ビルドの Xcode とシミュレータランタイムを事前インストール。シャード間のドリフトは偽の「自分のランナーでは通る」を生む。
- GitHub ランナーを一意名で登録し、担当シャードのラベルのみ付与する。
- ワークフローごとに
timeout-minutesを設定。UI シャードはまず 40 分から、p95 で絞る。 - シャード ID を
xcodebuildに渡す(スキームやテストプラン、バケットごとの-only-testingリスト)。 - スリープを無効化し、ハーネスが GUI セッションを要するなら CI ユーザー常時ログインを文書化(セキュリティレビューに従う)。
- 成果物を中央集約(JUnit、スクリーンショット)。ファイル名にシャード名を入れトリアージを明確に。
- シャード歪みアラート:あるシャードだけが連続3回、他の 1.5 倍 以上遅いならバケットを再調整する。
FAQ
1台の Mac mini M4 に UI テストシャードはいくつ載せられる?
余裕を計測していない限り、シミュレータ負荷の高い UI シャードはマシンあたり1本のプライマリジョブとして扱う。軽量スイートなら2シャードも可能だが、GPU とストレージの競合でウォールクロックの改善が消えることが多い。
シャードはコンパイル専用ジョブと同じランナーラベルを使うべき?
いいえ。ios-ui-shard のような専用ラベルを使い、追加シミュレータと画面セッション前提のマシンをコンパイルジョブが奪わないようにする。
香港・東京・ソウル・シンガポール・米国のチームから数分で届くマシンが必要なら、上記キュー計算に合わせて NodeMac プラン を比較し、感覚ではなくシャード数を決めてください。
Mac mini M4 は iOS UI 作業の実用的シャーディング単位です。Apple Silicon は XCTest オーケストレーションに強いシングルスレッド性能を提供し、複数シミュレータサービスにはユニファイドメモリが効き、PR の合間の待機では省電力です。NodeMac は HK・JP・KR・SG・US で SSH と VNC 付きの 専用物理 Mac mini を提供し、各シャードはリモートデバッグ可能な実機に対応します。クローゼット一杯の Mac を買うより、オンデマンドレンタル でシャードマップを実証し、実テレメトリでバケットを再調整しながら CapEx を抑えられます。