GitHub Actions高速化実践:Matrix戦略・依存キャッシュ・失敗切り分けの設計ガイド
GitHub Actions は便利ですが、プロジェクトが成長すると「遅い」「不安定」「原因が分かりにくい」という三重苦になりがちです。特に monorepo や複数ランタイム対応(Node/Python/Go など)では、ワークフローの設計次第で CI 時間が 2〜3 倍変わります。
本記事では、実行時間を短くしながら失敗時の調査コストも下げるために、matrix 設計・キャッシュ設計・障害時の確認順序を具体的に整理します。
1. まず「何を並列化するか」を決める
Actions の高速化は、いきなりキャッシュ最適化から入るより、先にジョブ分解を決める方が効きます。原則は次の通りです。
- 並列化すべき: 独立テスト(OS/バージョン別、サービス別)
- 直列にすべき: デプロイ、DB マイグレーション、本番反映
- 依存を分ける: lint/typecheck/test/build を一つに詰め込まない
悪い例は、1ジョブに全部詰め込み、失敗時に最初から再実行するパターンです。良い設計では「lint は通るが test だけ失敗」のように切り分けできます。
2. matrix を作るときの実践ルール
matrix は便利ですが、組み合わせ爆発で逆に遅くなることがあります。例えば os x runtime x db をすべて直積にすると、不要なジョブが大量発生します。そこで include/exclude を活用します。
|
|
ポイントは次です。
- 基準環境を1つ決める(例: ubuntu + latest)
- カバレッジ計測や重い E2E は基準環境だけで実施
- 互換性確認は軽量テスト中心にする
この設計にすると、品質を落とさずに全体時間を短縮できます。
3. キャッシュは「鍵設計」が9割
actions/cache や setup-node / setup-python のキャッシュを入れても、キー設計が甘いとヒット率が低く、逆に復元時間だけ増えます。
Node.js の例:
|
|
Python (pip) の例:
|
|
実務で効くコツ:
- lockfile をキーに含める(依存変化に追従)
- OS・ランタイムバージョンをキーに含める
- monorepo は対象サブディレクトリ単位でキー分割
- restore-keys を入れすぎない(古いキャッシュ復元で不整合)
4. concurrency で「古い実行を止める」
PR に連続 push されると、古い CI が残り続けてランナー枯渇を起こします。concurrency を入れて、最新コミットだけ走らせる構成にします。
|
|
これだけで無駄実行を大きく減らせます。特にレビュー中に細かい修正を重ねるチームほど効果が高いです。
5. paths-filter で不要ジョブを起動しない
ドキュメント更新だけなのに全テストが走る、という状態はよくあります。dorny/paths-filter で変更範囲に応じてジョブを分岐します。
|
|
これにより、実行時間だけでなくランナーコストも下げられます。
6. 失敗時の調査を速くするログ設計
CI が遅い組織は、だいたい「失敗調査も遅い」です。改善するには、次の3点を標準化します。
- 失敗したジョブで artifact(ログ、スクリーンショット、coverage)を必ず保存
- 重要ステップに
::group::を付けてログを畳む - flaky テスト検出用に rerun 情報を残す
artifact 例:
|
|
if: always() を忘れると、失敗時ほど証跡が残らないので注意です。
7. self-hosted runner を使う場合の注意点
高速化目的で self-hosted runner を導入する場合、運用事故が増えやすい領域です。
- 毎回クリーンワークスペース化(残骸で再現不能バグ)
- シークレットを runner に永続化しない
- パッチ適用・再起動の定期メンテをスケジュール化
- runner ラベルを用途別に分離(deploy と test を混在させない)
また、デプロイ権限を持つ runner と、PR 由来コードを実行する runner は分離するのが基本です。
8. 実際の改善ステップ(2週間)
Day 1-2: 現状計測
- 平均実行時間、p95 実行時間、失敗率を取得
- 一番遅いジョブ上位3つを特定
Day 3-5: ジョブ分割と matrix 整理
- lint/typecheck/test/build を分離
- matrix の組み合わせを include/exclude で整理
Day 6-8: キャッシュ最適化
- lockfile ベースキーへ統一
- キャッシュヒット率を可視化
Day 9-10: 無駄実行削減
- concurrency + cancel-in-progress
- paths-filter で対象限定
Day 11-14: 運用ルール化
- 失敗時 artifact を全ジョブ標準化
- flaky テスト記録のテンプレート化
この手順で進めると、速度改善だけでなく再発防止まで一気に整います。
9. よくある失敗パターン
パターンA: キャッシュを入れたのに遅い
原因:
- キーが細かすぎて毎回ミスヒット
- 圧縮/復元コストが大きいディレクトリを丸ごとキャッシュ
対処:
- 依存に限定してキャッシュ
- キーに lockfile ハッシュを利用
パターンB: matrix 失敗がノイズ化
原因:
fail-fast: trueで他環境の情報が取れない- ログ命名が統一されず比較困難
対処:
fail-fast: false- artifact 命名規則を統一
パターンC: PR の待ち時間が長い
原因:
- 古いコミットの CI が走り続ける
- 変更範囲に関係ないジョブが常時起動
対処:
- concurrency で古い実行を停止
- paths-filter 導入
10. 運用チェックリスト
- ジョブは責務別に分離されている
- matrix は必要最小限に絞られている
- 依存キャッシュのキーに lockfile が含まれる
- concurrency で古い実行をキャンセルしている
- 変更範囲に応じたジョブ起動制御がある
- 失敗時 artifact が必ず残る
GitHub Actions は「機能を使う」だけでは速くなりません。実行単位の設計、キャッシュ鍵設計、無駄実行抑制、証跡設計をセットで行うと、初めて安定した CI 基盤になります。