こんにちは!開発チームの背中を押す先輩エンジニアです。
毎日の開発作業、お疲れ様です。
「機能を作って、さあプッシュ!……あれ、まだビルドが終わらないな」
CI/CDのビルド待ち時間、地味にストレスが溜まりますよね。コーヒーブレイクに行くには短すぎるし、かといって集中力を切らすには絶妙な長さ。この「待ち時間」の積み重ねが、チーム全体の開発者体験(DX)をじわじわと蝕んでいきます。
でも、安心してください。CircleCIの「キャッシュ戦略」を正しく理解し、設定をひと工夫するだけで、この待ち時間は劇的に短縮できます。
今回は、初心者の方でも迷わず実装できるように、CircleCIのキャッシュの仕組みから、Node.js(npm)やRuby(Bundler)の依存関係を爆速でキャッシュする実践的なテクニックまで、優しく論理的に解説していきます。
これをマスターすれば、あなたのチームの毎日の開発が劇的に快適になりますよ!
—
1. CircleCIのキャッシュってそもそも何?
CI/CDパイプラインが動くとき、裏側では毎回まっさらな仮想環境(コンテナ)が立ち上がります。
そのため、何もしない状態だと、以下のようなムダな作業を毎回のビルドで行うことになります。
1. 空っぽの環境が起動する
2. インターネットの海から、何百・何千もの重いライブラリ(`node_modules`や`vendor/bundle`など)を毎回ゼロからダウンロードする
3. やっとテストが走り始める
これでは時間がかかるのも当然です。
キャッシュとは、前回のビルドで作った「重いファイルたち(依存関係)」をどこかに保存しておき、次回のビルドで「再利用」する仕組みです。毎回海まで水を汲みに行くのではなく、家のすぐ横に貯水タンクを置いておくようなイメージですね。
—
2. 登場人物は2つだけ:`restore_cache` と `save_cache`
CircleCIでキャッシュを扱うために覚えるべき命令(ステップ)は、基本的にたったの2つです。
- `restore_cache`: 過去に保存したキャッシュを、環境に戻す(復元する)
- `save_cache`: 新しくダウンロードした依存関係を、次のために保存する
これらを設定ファイル(`.circleci/config.yml`)に組み込んでいきます。
—
3. 実践!Node.js (npm) の依存関係を爆速キャッシュする
まずは、モダンなWeb開発で最もよく使われるNode.js(npm)を例に見ていきましょう。
以下の設定ファイルを見てください。ポイントはコメントで丁寧に解説しています。
version: 2.1
共通で使用する環境や実行環境の定義
orbs:
node: circleci/node@5.1.0
jobs:
build:
docker:
- image: cimg/node:18.16.0 # 安定したNode.jsの公式イメージ
steps:
- checkout # GitHubからソースコードをクローン
# 1. キャッシュの復元
- restore_cache:
keys:
# lockファイルのハッシュ値をキーにして、依存関係が変わったか判定する
- v1-npm-deps-{{ checksum “package-lock.json” }}
# 万が一完全一致しない場合、過去のv1で始まるキャッシュをフォールバックとして使う
- v1-npm-deps-
# 2. 依存関係のインストール
- run:
name: Install Dependencies
command: npm ci
# 3. キャッシュの保存(もしロックファイルが変わっていれば新しく保存される)
- save_cache:
key: v1-npm-deps-{{ checksum “package-lock.json” }}
paths:
- ~/.npm # npmのキャッシュが保存されるディレクトリを指定
ここが重要:`checksum` の魔法
`key` の部分にある `{{ checksum “package-lock.json” }}` に注目してください。
CircleCIは、`package-lock.json`(ライブラリのバージョンが正確に記録されたファイル)の中身が変わったかどうかを自動で計算し、その結果をキーに含めます。
- 依存関係が変わっていない場合: 保存されたキャッシュがそのまま使われる(爆速⚡️)
- 新しくライブラリを追加・更新した場合: ハッシュ値が変わるため、古いキャッシュを無視して新しくインストールし、それが新しいキャッシュとして保存される(安全🔒)
人間が「キャッシュをクリアしなきゃ!」と意識する必要は一切ありません。システムが勝手に賢く判断してくれます。
—
4. Ruby (Bundler) の場合:実戦的なキャッシュ設定
Ruby on Railsなどのプロジェクトでも基本は同じです。Bundlerの場合は、`vendor/bundle`というディレクトリにGemをインストールすることが多いため、指定するパスが変わります。
version: 2.1
jobs:
build:
docker:
- image: cimg/ruby:3.2.2
steps:
- checkout
# キャッシュの復元
- restore_cache:
keys:
- v1-bundle-deps-{{ checksum “Gemfile.lock” }}
- v1-bundle-deps-
# Bundlerのパスを設定してインストール
- run:
name: Bundle Install
command: |
bundle config set –local path ‘vendor/bundle’
bundle install
# キャッシュの保存
- save_cache:
key: v1-bundle-deps-{{ checksum “Gemfile.lock” }}
paths:
- vendor/bundle
このように、「どのファイルを基準に判定するか(checksum)」と「どこを保存するか(paths)」の2つさえ間違わなければ、どんな言語やツールであっても簡単にキャッシュを導入できます。
—
5. キャッシュ戦略を成功させるためのプロの知見(ハック)
最後に、現場で数々のパイプラインを最適化してきた私から、少し踏み込んだ「知見」をシェアします。
① キャッシュのサイズを大きくしすぎない
「あれもこれも」とプロジェクト全体やビルド成果物(`dist`や`build`フォルダなど)までキャッシュしようとすると、キャッシュのアップロード・ダウンロード自体に時間がかかるようになり、本末転倒になります。
キャッシュの対象は「外部からダウンロードする重い依存関係(`node_modules`, `vendor/bundle`など)」に絞るのが鉄則です。
② キャッシュキーのプレフィックス(`v1-`など)をうまく使う
設定例で `v1-npm-deps-…` のように `v1` というプレフィックスをつけていました。
もし「Node.jsのバージョンを大きく上げた」「キャッシュの構造をごっそり変えたい」という時は、ここを `v2-` に書き換えるだけで、古いゴミキャッシュを一掃し、綺麗な状態からスタートできます。トラブルシューティングの強力な武器になります。
—
まとめ:待ち時間を削り、開発のフロー状態(ゾーン)を守ろう
今回は、CircleCIの `restore_cache` と `save_cache` を使ったビルド時間短縮のテクニックを解説しました。
- キャッシュとは、前回の依存関係を再利用してムダなダウンロードを省く仕組み
- `checksum` を使って、ファイルの変更を検知しながら賢く管理する
- 対象は「外部の依存関係」に絞ることで、最大の効果を発揮する
たった数行の設定を加えるだけで、ビルド時間が半分以下になることも珍しくありません。浮いた時間は、新しいコードを書くため、あるいは美味しいコーヒーをゆっくり楽しむために使いましょう。
あなたのチームの開発者体験が、今日より少しでも良くなることを心から応援しています!