<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Node.js on AI2CORE - AI技術ブログ</title>
    <link>https://www.ai2core.com/tags/node.js/</link>
    <description>Recent content in Node.js on AI2CORE - AI技術ブログ</description>
    <generator>Hugo -- 0.146.4</generator>
    <language>ja</language>
    <lastBuildDate>Wed, 04 Mar 2026 09:05:00 +0900</lastBuildDate>
    <atom:link href="https://www.ai2core.com/tags/node.js/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>GitHub Actions高速化実践：Matrix戦略・依存キャッシュ・失敗切り分けの設計ガイド</title>
      <link>https://www.ai2core.com/posts/2026-03-04-github-actions-matrix-cache-strategy/</link>
      <pubDate>Wed, 04 Mar 2026 09:05:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-03-04-github-actions-matrix-cache-strategy/</guid>
      <description>GitHub Actionsの実行時間と失敗率を同時に改善するためのmatrix設計、キャッシュ戦略、並列最適化、トラブルシューティング手順を具体例付きで解説。</description>
      <content:encoded><![CDATA[<h1 id="github-actions高速化実践matrix戦略依存キャッシュ失敗切り分けの設計ガイド">GitHub Actions高速化実践：Matrix戦略・依存キャッシュ・失敗切り分けの設計ガイド</h1>
<p>GitHub Actions は便利ですが、プロジェクトが成長すると「遅い」「不安定」「原因が分かりにくい」という三重苦になりがちです。特に monorepo や複数ランタイム対応（Node/Python/Go など）では、ワークフローの設計次第で CI 時間が 2〜3 倍変わります。</p>
<p>本記事では、<strong>実行時間を短くしながら失敗時の調査コストも下げる</strong>ために、matrix 設計・キャッシュ設計・障害時の確認順序を具体的に整理します。</p>
<h2 id="1-まず何を並列化するかを決める">1. まず「何を並列化するか」を決める</h2>
<p>Actions の高速化は、いきなりキャッシュ最適化から入るより、先にジョブ分解を決める方が効きます。原則は次の通りです。</p>
<ul>
<li>並列化すべき: 独立テスト（OS/バージョン別、サービス別）</li>
<li>直列にすべき: デプロイ、DB マイグレーション、本番反映</li>
<li>依存を分ける: lint/typecheck/test/build を一つに詰め込まない</li>
</ul>
<p>悪い例は、1ジョブに全部詰め込み、失敗時に最初から再実行するパターンです。良い設計では「lint は通るが test だけ失敗」のように切り分けできます。</p>
<h2 id="2-matrix-を作るときの実践ルール">2. matrix を作るときの実践ルール</h2>
<p>matrix は便利ですが、組み合わせ爆発で逆に遅くなることがあります。例えば <code>os x runtime x db</code> をすべて直積にすると、不要なジョブが大量発生します。そこで <code>include/exclude</code> を活用します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">strategy</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">fail-fast</span>: <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">matrix</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">os</span>: [<span style="color:#ae81ff">ubuntu-latest, macos-latest]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">node</span>: [<span style="color:#ae81ff">20</span>, <span style="color:#ae81ff">22</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">include</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">os</span>: <span style="color:#ae81ff">ubuntu-latest</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">node</span>: <span style="color:#ae81ff">22</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">coverage</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">exclude</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">os</span>: <span style="color:#ae81ff">macos-latest</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">node</span>: <span style="color:#ae81ff">20</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>ポイントは次です。</p>
<ol>
<li><strong>基準環境を1つ決める</strong>（例: ubuntu + latest）</li>
<li>カバレッジ計測や重い E2E は基準環境だけで実施</li>
<li>互換性確認は軽量テスト中心にする</li>
</ol>
<p>この設計にすると、品質を落とさずに全体時間を短縮できます。</p>
<h2 id="3-キャッシュは鍵設計が9割">3. キャッシュは「鍵設計」が9割</h2>
<p><code>actions/cache</code> や <code>setup-node</code> / <code>setup-python</code> のキャッシュを入れても、キー設計が甘いとヒット率が低く、逆に復元時間だけ増えます。</p>
<p>Node.js の例:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/setup-node@v4</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">node-version</span>: <span style="color:#ae81ff">${{ matrix.node }}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">cache</span>: <span style="color:#e6db74">&#39;npm&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">cache-dependency-path</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      package-lock.json
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      packages/*/package-lock.json</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Python (pip) の例:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/setup-python@v5</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">python-version</span>: <span style="color:#e6db74">&#39;3.12&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">cache</span>: <span style="color:#e6db74">&#39;pip&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">cache-dependency-path</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      requirements.txt
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      requirements-dev.txt</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>実務で効くコツ:</p>
<ul>
<li>lockfile をキーに含める（依存変化に追従）</li>
<li>OS・ランタイムバージョンをキーに含める</li>
<li>monorepo は対象サブディレクトリ単位でキー分割</li>
<li>restore-keys を入れすぎない（古いキャッシュ復元で不整合）</li>
</ul>
<h2 id="4-concurrency-で古い実行を止める">4. concurrency で「古い実行を止める」</h2>
<p>PR に連続 push されると、古い CI が残り続けてランナー枯渇を起こします。<code>concurrency</code> を入れて、最新コミットだけ走らせる構成にします。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">concurrency</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">group</span>: <span style="color:#ae81ff">ci-${{ github.workflow }}-${{ github.ref }}</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">cancel-in-progress</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>これだけで無駄実行を大きく減らせます。特にレビュー中に細かい修正を重ねるチームほど効果が高いです。</p>
<h2 id="5-paths-filter-で不要ジョブを起動しない">5. paths-filter で不要ジョブを起動しない</h2>
<p>ドキュメント更新だけなのに全テストが走る、という状態はよくあります。<code>dorny/paths-filter</code> で変更範囲に応じてジョブを分岐します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">dorny/paths-filter@v3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">id</span>: <span style="color:#ae81ff">changes</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">filters</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      backend:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        - &#39;backend/**&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      frontend:
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        - &#39;frontend/**&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 例: backend が変わった時だけ実行</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">if</span>: <span style="color:#ae81ff">steps.changes.outputs.backend == &#39;true&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>これにより、実行時間だけでなくランナーコストも下げられます。</p>
<h2 id="6-失敗時の調査を速くするログ設計">6. 失敗時の調査を速くするログ設計</h2>
<p>CI が遅い組織は、だいたい「失敗調査も遅い」です。改善するには、次の3点を標準化します。</p>
<ul>
<li>失敗したジョブで artifact（ログ、スクリーンショット、coverage）を必ず保存</li>
<li>重要ステップに <code>::group::</code> を付けてログを畳む</li>
<li>flaky テスト検出用に rerun 情報を残す</li>
</ul>
<p>artifact 例:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Upload test reports</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">if</span>: <span style="color:#ae81ff">always()</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/upload-artifact@v4</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">test-report-${{ matrix.os }}-${{ matrix.node }}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      reports/
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      coverage/</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>if: always()</code> を忘れると、失敗時ほど証跡が残らないので注意です。</p>
<h2 id="7-self-hosted-runner-を使う場合の注意点">7. self-hosted runner を使う場合の注意点</h2>
<p>高速化目的で self-hosted runner を導入する場合、運用事故が増えやすい領域です。</p>
<ul>
<li>毎回クリーンワークスペース化（残骸で再現不能バグ）</li>
<li>シークレットを runner に永続化しない</li>
<li>パッチ適用・再起動の定期メンテをスケジュール化</li>
<li>runner ラベルを用途別に分離（deploy と test を混在させない）</li>
</ul>
<p>また、デプロイ権限を持つ runner と、PR 由来コードを実行する runner は分離するのが基本です。</p>
<h2 id="8-実際の改善ステップ2週間">8. 実際の改善ステップ（2週間）</h2>
<h3 id="day-1-2-現状計測">Day 1-2: 現状計測</h3>
<ul>
<li>平均実行時間、p95 実行時間、失敗率を取得</li>
<li>一番遅いジョブ上位3つを特定</li>
</ul>
<h3 id="day-3-5-ジョブ分割と-matrix-整理">Day 3-5: ジョブ分割と matrix 整理</h3>
<ul>
<li>lint/typecheck/test/build を分離</li>
<li>matrix の組み合わせを include/exclude で整理</li>
</ul>
<h3 id="day-6-8-キャッシュ最適化">Day 6-8: キャッシュ最適化</h3>
<ul>
<li>lockfile ベースキーへ統一</li>
<li>キャッシュヒット率を可視化</li>
</ul>
<h3 id="day-9-10-無駄実行削減">Day 9-10: 無駄実行削減</h3>
<ul>
<li>concurrency + cancel-in-progress</li>
<li>paths-filter で対象限定</li>
</ul>
<h3 id="day-11-14-運用ルール化">Day 11-14: 運用ルール化</h3>
<ul>
<li>失敗時 artifact を全ジョブ標準化</li>
<li>flaky テスト記録のテンプレート化</li>
</ul>
<p>この手順で進めると、速度改善だけでなく再発防止まで一気に整います。</p>
<h2 id="9-よくある失敗パターン">9. よくある失敗パターン</h2>
<h3 id="パターンa-キャッシュを入れたのに遅い">パターンA: キャッシュを入れたのに遅い</h3>
<p>原因:</p>
<ul>
<li>キーが細かすぎて毎回ミスヒット</li>
<li>圧縮/復元コストが大きいディレクトリを丸ごとキャッシュ</li>
</ul>
<p>対処:</p>
<ul>
<li>依存に限定してキャッシュ</li>
<li>キーに lockfile ハッシュを利用</li>
</ul>
<h3 id="パターンb-matrix-失敗がノイズ化">パターンB: matrix 失敗がノイズ化</h3>
<p>原因:</p>
<ul>
<li><code>fail-fast: true</code> で他環境の情報が取れない</li>
<li>ログ命名が統一されず比較困難</li>
</ul>
<p>対処:</p>
<ul>
<li><code>fail-fast: false</code></li>
<li>artifact 命名規則を統一</li>
</ul>
<h3 id="パターンc-pr-の待ち時間が長い">パターンC: PR の待ち時間が長い</h3>
<p>原因:</p>
<ul>
<li>古いコミットの CI が走り続ける</li>
<li>変更範囲に関係ないジョブが常時起動</li>
</ul>
<p>対処:</p>
<ul>
<li>concurrency で古い実行を停止</li>
<li>paths-filter 導入</li>
</ul>
<h2 id="10-運用チェックリスト">10. 運用チェックリスト</h2>
<ul>
<li><input disabled="" type="checkbox"> ジョブは責務別に分離されている</li>
<li><input disabled="" type="checkbox"> matrix は必要最小限に絞られている</li>
<li><input disabled="" type="checkbox"> 依存キャッシュのキーに lockfile が含まれる</li>
<li><input disabled="" type="checkbox"> concurrency で古い実行をキャンセルしている</li>
<li><input disabled="" type="checkbox"> 変更範囲に応じたジョブ起動制御がある</li>
<li><input disabled="" type="checkbox"> 失敗時 artifact が必ず残る</li>
</ul>
<p>GitHub Actions は「機能を使う」だけでは速くなりません。<strong>実行単位の設計、キャッシュ鍵設計、無駄実行抑制、証跡設計</strong>をセットで行うと、初めて安定した CI 基盤になります。</p>
]]></content:encoded>
      <category>Tech</category>
      <category>GitHub Actions</category>
      <category>CI</category>
      <category>DevOps</category>
      <category>Node.js</category>
      <category>Python</category>
    </item>
  </channel>
</rss>
