GitHub Actions Matrixを極限までハックせよ:実行時間を「ゼロ」に近づける並列分散の深淵
GitHub Actionsの`strategy.matrix`を単なる「OSとバージョンの掛け合わせツール」だと思っているなら、君のCIパイプラインはまだ「眠っている」のと同じだ。
真のDevOpsエンジニアは、CIを単なるテスト実行の場ではなく、「フィードバックループのレイテンシを物理限界まで削り出す最適化実験場」と捉える。本稿では、Matrix戦略を駆使し、テスト実行時間を極限まで圧縮するための「現場の知見」を叩き込む。
—
1. 静的Matrixからの脱却:動的ジョブ生成による「完全並列化」
標準的なMatrix設定はYAMLにハードコードされるが、大規模プロジェクトではこれがボトルネックになる。依存関係の変化や、テスト対象のモジュール数に応じてMatrixを動的に生成する「事前計算ステップ」をパイプラインの先頭に置くのが定石だ。
ハック:GitHub API と `gh` CLIによる動的マトリックス生成
GitHub Actionsの`outputs`を活用し、JSONを動的に注入する。
jobs:
prepare-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: |
# テスト対象のディレクトリを検出し、JSONとして出力
# 複雑な計算はここで完結させ、次ジョブには結果だけを渡す
matrix=$(find ./packages -maxdepth 1 -type d | jq -R . | jq -s -c ‘{package: .}’)
echo “matrix=$matrix” >> $GITHUB_OUTPUT
test:
needs: prepare-matrix
strategy:
matrix: ${{ fromJson(needs.prepare-matrix.outputs.matrix) }}
runs-on: ubuntu-latest
steps:
- run: npm test –workspace=${{ matrix.package }}
この手法の真髄は、「ジョブ数そのものを計算によって最適化する」ことにある。テスト対象が増減してもYAMLを触る必要はない。
—
2. コストと実行時間を最適化する「ジョブ分割の非対称性」
全てのテストを全環境で流すのは愚策だ。Linux(Ubuntu)は安価だが、macOSやWindowsはコストが数倍高く、起動も遅い。ここで`include`と`exclude`の高度な活用が光る。
- Linux: 全てのマイナーバージョン、全テストスイートを網羅する。
- macOS/Windows: クリティカルなパスのみを抽出(タグ付け)し、実行時間を最短化する。
strategy:
fail-fast: true # 致命的なエラーは即座に停止し、分単位の課金を止める
matrix:
os: [ubuntu-latest, macos-latest]
node: [18, 20]
include:
- os: macos-latest
is_critical: true # 特定のジョブにフラグを立てる
exclude:
- os: macos-latest
node: 18 # 不要な組み合わせを排除し、無駄なRunner消費を抑制
—
3. Runnerのメモリ消費とパフォーマンス・ハック
`runs-on: ubuntu-latest`は便利だが、大規模なテストスイートではメモリボトルネックが発生する。
- Swapの活用: GitHub ActionsのRunnerはSwap領域が有効でないことが多い。メモリ不足で落ちるなら、一時的に`zram`を有効化するスクリプトをCIの最初に組み込め。
- キャッシュの賢い運用: `actions/cache`はキーが一致しないと無駄にネットワークを消費する。`key`に`hashFiles(‘/package-lock.json’)`だけでなく、「テスト対象のソースコードのハッシュ」も含めよ。これにより、コード変更がないモジュールの再テストを完全スキップできる。
—
4. 実行時間を極限まで削る:パイプラインの「物理的」最適化
A. 早期終了(Fail-fast)の戦略的運用
`fail-fast: true`は基本だが、並列実行数が多すぎる場合、一つの微細なエラーで全ジョブがキャンセルされるのは非効率だ。テストが不安定な期間は`false`にし、テスト結果のアーティファクトを統合してから最終判定を下す「カスタム集計ロジック」を実装しろ。
B. 実行時間予測と自動調整
過去のテスト実行時間をGitHub ActionsのAPIから取得し、`test-split`のようなツールと組み合わせることで、各ジョブの実行時間が均等になるようにテストを分割する。
現場で使うテスト分割のイメージ
過去の実行時間データに基づき、実行時間が均一になるようファイルをグループ化
npx test-split –files “tests//.test.js” –group-count 5
—
5. 伝説のアーキテクトからの助言:CIは「生き物」である
CIパイプラインの設計に「完成」はない。
1. 可観測性(Observability): `actions/github-script`を使って、各テストジョブの実行時間をDB(あるいはInfluxDB等)に飛ばせ。どのテストが最も「重い」のか、可視化していないのは、エンジニアとして怠慢だ。
2. モジュール依存の排除: テストが並列で走らない最大の理由は「DBの共有」や「ファイルシステムの競合」だ。これを排除し、`Service Containers`を徹底的に活用して、各テストが独立した環境(Isolated Environment)で完結するようにリファクタリングせよ。
結論:
GitHub ActionsのMatrixは、使いこなせば単なるCIの枠を超え、分散コンピューティングプラットフォームへと進化する。ハードコードを避け、動的な計算を導入し、課金と時間を徹底的に制御せよ。
君が書いたコードが、世界で最も速く、そして最も安価に検証されるパイプラインを構築することを願っている。CI/CDとは、改善の自動化そのものなのだから。