GitHub Actions で npm キャッシュを設定したら CI の待ち時間がかなり短くなった話
毎回 npm install を走らせていた CI ワークフローを actions/cache でキャッシュするようにしたところ、実行時間がかなり短縮されました。その設定と手順をまとめます。
🙌 結論から
GitHub Actions のワークフローで actions/cache を使って npm のキャッシュを設定したところ、毎回 2〜3 分かかっていた npm install が初回以外ほぼスキップされ、CI 全体の時間がかなり短縮されました✨
設定自体は数行追加するだけなので、ちょっとビルド系遅いな〜思ったらやってみる価値ありです!
package-lock.json が変わっていないプッシュなら、以降のインストールは 10〜20 秒程度で終わるようになりました(^o^)/
💡 なぜキャッシュが必要だったか
仕事で使っているプロジェクトでは、プッシュのたびに GitHub Actions でテストとビルドを回していました。
ワークフローのログを眺めていると、npm install だけで毎回 2〜3 分ほどかかっていることに気づきました(´・ω・`)
依存パッケージの内容がほとんど変わっていないのに、毎回フルでインストールするのは非効率…
package-lock.json が変わっていなければ、前回のインストール結果をそのまま使えるはず!
CI の待ち時間が長いと、プッシュのたびにブラウザで進捗を確認する手間が増えるのが割とストレスですよね…
👀 actions/cache の使い方
actions/cache を使うと、特定のディレクトリをキャッシュキーで保存・復元できます。
- name: Cache node_modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
ポイントは key に hashFiles('**/package-lock.json') を使っていることです!
package-lock.json が変わらない限り同じキャッシュが使われ、変わった場合は新しいキャッシュが自動で作られます。
restore-keys は、完全一致するキャッシュがない場合の部分一致フォールバックです。
依存が少し変わった場合でも古いキャッシュをベースに差分だけインストールできるので、キャッシュ効果がより広い状況で出やすくなります。
✨ 適用してみた結果
キャッシュが効いた場合の npm install は、2〜3 分から 10〜20 秒程度まで短縮されました。
package-lock.json が変わっていないプッシュがほとんどなので、日常の CI はほぼキャッシュヒットする状態になっています。
一点、調べていて気づいたのですが、actions/setup-node アクション自体にも cache オプションが内蔵されています。
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
こちらでも同等のキャッシュが効くので、設定をシンプルにしたい場合はこちらの方が便利かもしれません。
私は最初 actions/cache を直接使っていましたが、後からこの setup-node のオプションに気づいて、今はこちらに切り替えています。
どちらの方法でも効果は変わらないので、プロジェクトの好みで選んで問題ないと思います。
⚠️ キャッシュの容量上限とブランチスコープに注意
しばらく運用していたら、あるとき急にキャッシュが効かなくなったことがありました(・_・;)
調べてみると、リポジトリ全体のキャッシュ容量には上限(デフォルト10GB)があることが原因でした。
上限を超えると、GitHubが古いキャッシュから自動的に削除していく「eviction(追い出し)」が発生します。さらに、7日間アクセスされなかったキャッシュも自動的に消える仕様です。
私のプロジェクトはモノレポで複数のキャッシュキー(npm・turbo・ビルド成果物など)を使っていたので、気づかないうちに合計容量が膨らんでいたようです。
もう一つ知らずにハマったのが、キャッシュのスコープです。
キャッシュはブランチ単位で分離されていて、あるフィーチャーブランチで作られたキャッシュは、別のフィーチャーブランチからは直接参照できません。
例外的に、restore-keys によるフォールバックでは「ベースブランチ(main など)のキャッシュ」までは辿れる仕組みになっています。
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
これを理解してから、main ブランチのCIを優先してキャッシュを温めておく(main へのプッシュ・マージのたびに確実にキャッシュを作る)ようにしたら、フィーチャーブランチのCIも restore-keys 経由で恩恵を受けられるようになりました。
「なぜかキャッシュが効かない」と感じたら、容量の上限とブランチスコープの2点をまず疑ってみるのがおすすめです!
パパ系Webエンジニア〜愛用ガジェット一覧 🛍️
この記事を書いている作業環境のガジェットを楽天ROOMにまとめています ✨ 在宅エンジニアのデスク作りの参考にどうぞ 🙌
👍 まとめ
actions/cache の設定は数行で完結します。
それだけで CI の待ち時間がかなり変わったので、設定コストに対して得られるリターンはかなり大きかったです!
setup-node の cache オプションを使えばさらにシンプルに書けるので、どちらか使いやすい方を試してみてください。
まだキャッシュを設定していないプロジェクトがあれば、ぜひ一度確認してみることをおすすめします(^^)
※ キャッシュの容量上限や仕様は今後変わる可能性があります。最新情報は GitHub公式ドキュメント をご確認ください👀