2026年 越境チームの Trunk/Merge Queue とマルチリージョン物理 Mac:オーケストレーションの尾部遅延とロックストームをどう潰す?キュー深度・ラベルルーティング・成果物アフィニティの閾値意思決定マトリクス(コピペ可能な GitHub Actions merge-group 断片+FAQ)
Trunk と GitHub Merge Queue を既に導入しているのに、マルチリージョンの物理 Mac セルフホスト Runner でキューとロック争奪に悩むチーム向けです。キュー深度・ラベルルーティング・成果物アフィニティを三枚の閾値マトリクスで束ね、コピペ可能な merge_group ワークフロー・七ステップ Runbook・引用用数値・FAQ で落とし込みます。
課題:オーケストレーション尾部とロックストームはどこから来るか
Trunk がデフォルトブランチに乗り、GitHub Merge Queue がマージ前検証と順序の安全を両立させるとき、制御面はクラウドにあり、計算はマルチリージョンの 物理 Mac セルフホスト Runner に載ります。尾部の主因はコンパイル時間より キュー先頭の詰まり、越境での成果物移動、シミュレータ・Derived Data・キーチェーンなど 共有可変状態 による隠れロック争奪です。リージョナルプールの位相と PR/成果物の引き継ぎは 多タイムゾーン・リレー:PR ルーティング、成果物の近接、地域 Mac プールのロック で整理し、checkout の冷起動は 越境 CI の Git checkout 戦略(マルチリージョン物理 Mac) と併読すると一貫します。
- 制約: Merge Queue の深度と Runner 能力から乖離した
merge_group並列は、先頭の巨大 PR 数本で全員の待ち時間を膨らませます。物理機は CPU/ディスクの硬い上限に当たり、対策が遅れるほど尾部が増幅します。 - 隠れコスト: 一リージョンでビルドした成果物を、マルチリージョン検証ジョブが越境 pull し続けるパターン。リージョンごとに全面ミラーリングすればインデックス一貫性・鍵の所在・GC コストが乗ります。どちらも感覚ではなく アフィニティ閾値 が要ります。
- 安定性とロックストーム: セルフホスト Runner 上で同一ユーザセッションの Xcode/Simulator/キーチェーンを奪い合う並列ジョブは、フレークとリトライとして現れます。リトライが ロック更新の嵐 と API スロットリングに積み上がり、キューをさらに伸ばします。
三枚の意思決定マトリクス:キュー深度・ラベルルーティング・成果物アフィニティ
マトリクス A:Merge Queue/concurrency を締めるタイミング
| シグナル | 読み取り | 最初の打ち手 |
|---|---|---|
| キュー待ち P95 > merge_group 1 回分の 2 倍 | キャパ不足、または巨大 PR 数本による先頭詰まり | 並列マージ上限、重いジョブ分割、巨大変更は専用キューや夜間枠へ |
| キュー再編後に merge_group 失敗が急増 | 単一 PR ではなくデフォルトブランチ側のドリフト | rebase/merge 方針と高速チェックを締め、キュー内一時マージコミットの寿命を短くする |
| 1 台の Runner に複数ジョブで CPU/ディスク飽和 | 過負荷による偽陰性 | リポジトリまたはリソース単位の concurrency、もしくはリージョン別スケール |
マトリクス B:ラベルルーティング(マルチリージョン物理 Mac)
| 目的 | 推奨ラベル | 避けること |
|---|---|---|
| デフォルトブランチ向けマージ検証 | runs-on: [self-hosted, macOS, region-apac] を成果物ストレージのリージョンに揃える |
広すぎる macOS だけで高 RTT リージョンに落ちる構成 |
| Apple ID/署名/公証 | 法域に縛った Runner プール+分離キーチェーンプロファイル | 対話ログイン 1 セッションを複数ジョブで共有 |
| UI/シミュレータのフレーク率が高い | 単一 concurrency または専用 Runner、セッション状態のリセット | ロック争奪にリトライだけを重ねる対症療法 |
マトリクス C:成果物アフィニティ(閾値付き)
| 条件 | 戦略 |
|---|---|
| 1 回の検証で中間成果物が ~5GB 超、越境 RTT P95 が ~80〜120ms 超 | ビルドと検証を同一リージョンに同居。大きな blob は同一リージョンのオブジェクトストレージへ。ワークフローは参照とダイジェストのみ渡す |
| 成果物が小さく再現ビルドが効く | 単一の権威リージョン+他地域は派生キャッシュ。重複アップロードを先に削る |
| コンプライアンスで監査ソースを一つにしたい | 署名/公証の「権威リージョン」を固定し、他リージョンは検証済み成果物のみ消費 |
三枚はセットで使います。キュー深度は 誰が先に走るか、ラベルは どこで走るか、アフィニティは 走る前に何を動かすか に答えます。一角を落とすと「マージは緑だがリリースは遅い」状態に戻ります。
七ステップ Runbook
- ベースラインを固定: PR チェック、
merge_group、デフォルトへの push の計測を分離。リージョン別の Runner キュー深度とリトライ率を追う。 - merge_group を SLA 化: 「マージ待ち時間」のビジネス上限内に収める目標時間を置き、成果物ダウンロードの割合は別行で可視化する。
- concurrency を締める: 共有可変リソースには
concurrencyまたは「Runner あたり重いジョブは 1 つ」で、先に偽陰性を潰す。 - 地域ラベルを足す: デフォルトブランチ検証を成果物・依存ミラーと同じリージョンに固定。越境は読み取り専用加速に留める。
- 成果物パスを較正: 巨大な中間成果物は同一リージョンで生成→参照→ダイジェスト検証。merge_group 内のフル再アップロード連発を避ける。
- ゲームデイ障害: 片リージョンをドレインする/スロットリングを注入し、キューのフォールバックが署名法域を壊さないか確認(権威リージョンが動けない場合の RTO を文書化)。
- 閾値を書き残す: キュー深度/RTT/成果物サイズを ADR に落とし、スケールで暗黙ルールが消えないようにする。
引用用閾値(ADR 向け)
- Merge Queue 待ち P95 >
merge_group1 回検証の おおよそ 2 倍 → ハード購入の前に concurrency とジョブ分割を調整。 - 越境成果物 pull の RTT P95 > おおよそ 80〜120ms かつ 1 回の取得が おおよそ 5GB 超 → デフォルトは同一リージョンでビルド+検証を揃える。
- セルフホスト物理 Mac では concurrency グループ内で 重い UI ジョブは Runner あたり 1 つ にし、シミュレータ争奪を避ける。
- 短期のキュー再編失敗率 > おおよそ 5% → ブランチ保護とドリフトを疑い、リトライ増加だけに頼らない。
コピペ可能な GitHub Actions merge_group 断片
pull_request に加えて最小限の merge_group トリガーと、セルフホスト Runner のロック嵐を抑える concurrency キーです。runs-on のラベルとリージョン名は自チームのフリートに合わせて置き換えてください。
name: ci
on:
pull_request:
merge_group:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
validate:
if: github.event_name == 'merge_group' || github.event_name == 'pull_request'
runs-on: [self-hosted, macOS, region-apac]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.merge_group.head_sha || github.event.pull_request.head.sha }}
- name: Build and test (merge queue aware)
run: |
echo "Running on ${{ github.event_name }} @ ${{ github.sha }}"
# xcodebuild / swift test / your merge validation commands
注:merge_group ではキューの一時マージコミットと同じ ref を checkout する必要があります。concurrency キーは PR 実行とマージキュー実行が誤ってキャンセルし合わないよう調整し、必要ならワークフローを分割してください。GitHub の merge_group の意味は進化し得るため、本番前に捨てリポジトリで E2E 検証してください。
FAQ
- merge_group と pull_request は同じ job 定義を共有できますか?
- ステップは共有可能ですが、明示的な
if/ref処理を残してください。キュー再編で SHA を取り違えると「ランダム赤」になります。 - 物理 Mac プールと GitHub ホスト Runner は混在させるべき?
- 可能ですがラベルは排他的にし文書化します。成果物とキャッシュのパス契約を揃え、ファイルシステム構成が異なる Runner 階級をまたぐと concurrency グループが壊れやすいです。
- ロックストーム:まずキューを空にする?ジョブを止める?
- 先に並列度を下げロック粒度を細かくします。キューをドレインすると順序付きマージの意図が失われ、通常は制御面インシデント向けです。
Mac mini でキューを安定運用する
Trunk と Merge Queue はマージの正しさを CI に押し込み、物理 Mac Runner は実際の Xcode とシミュレータ負荷を担います。合わせると 安定性と I/O の一貫性 がピーク GHz より重要になります。Apple Silicon の Mac mini は ユニファイドメモリの帯域 とおおよそ 待機 4W 前後 の消費電力を両立し、リージョナル Runner を夜間もウォームに保ちやすく、多くの x86 タワーで熱変動のたびにロック TTL を振り直す負担を抑えられます。
macOS は開発者端末と同じ根っこを持ち、「ローカルは通るのに CI だけフレーク」という隙を狭めます。Gatekeeper・SIP・FileVault により、典型的な Windows ビルドファームよりセッションとディスク状態を監査で説明しやすいです。Runner を成果物・ミラーに同居させ、ラベルと concurrency でロック境界を強制する——静かで省電力かつ挙動が読みやすいハードウェアで merge_group を回したいなら、Mac mini M4 は価格性能の出発点として依然強力です。
「merge_group がたまに通る」から「キュー深度とアフィニティ閾値を信頼できる」へ進むなら、今すぐ Mac mini を手元に置き、クラウド枠との押し合いだけで終わらせずこのマトリクスを実シリコンで検証してください。
地域別物理 Mac で Merge Queue を回しますか?
低遅延でラベル振り分け可能な Mac mini クラウドノードで、merge_group の待ちと越境成果物尾部を短縮します。