こんにちは!日々の開発やCI/CDパイプラインの構築、本当にお疲れ様です。
チームの規模が大きくなり、アプリケーションが成長するにつれて、こんな悩みを持ったことはありませんか?
「なんだか最近、GitHub Actionsのビルドやテストが終わるのが遅いな……」
「ローカルではサクッと動くのに、CI環境だと突然 `JavaScript heap out of memory` で落ちるんだけど!」
実はこれ、デフォルトのGitHub Actionsが用意してくれている「標準のランナー(仮想マシン)」のスペックや設定を、そのまま無邪気に使い続けてしまっていることが原因のほとんどです。
今回は、数々のプロジェクトでCI/CDの高速化とコスト削減をやり抜いてきたシニアエンジニアの私から、「GitHub Actionsの実行時パフォーマンスを極限まで引き出し、メモリ・CPUを最適化する裏技」を分かりやすく、かつ徹底的に伝授します。
これをマスターすれば、イライラする待ち時間が減るだけでなく、巨大なフロントエンドビルドも一発で安定するようになりますよ。さあ、一緒に見ていきましょう!
—
そもそも「GitHub Actions」と「ランナー」って何?
初心者の方に向けて、まずは基本から整理しておきましょう。
- GitHub Actionsとは:コードをプッシュしたり、プルリクエストを作ったりしたときに、「テストして」「ビルドして」「本番サーバーにデプロイして」といった一連の作業を自動化してくれる、GitHub公式の強力な仕組みです。
- ランナー(Runner)とは:その自動化されたタスク(ジョブ)を実際に実行してくれる裏側の作業用コンピューター(仮想マシン)のことです。
GitHubは、私たちがコードを動かすために、LinuxやWindows、macOSといった綺麗な環境を毎回無料で(あるいは有料プランの範囲内で)貸し出してくれています。
デフォルト設定の落とし穴
GitHubが用意してくれている標準のクラウドランナー(GitHub-hosted runner)は、一般的なスペック(通常は2コアCPU、7GB程度のメモリ)を持っています。
しかし、「Node.jsを使ったモダンなフロントエンドビルド」や「並列テストの実行」を行う場合、このデフォルト設定のままだと、すぐにメモリの限界やCPUの非効率な使い方にぶつかってしまいます。
特にNode.jsは、デフォルトのままだと使えるメモリの量に厳しめの制限(上限)がかかっているため、大規模なアプリをビルドしようとすると「メモリ不足(OOM: Out Of Memory)」で無慈悲に落ちてしまうのです。
—
基礎セットアップ:まずは動く環境を作ろう
理屈はこれくらいにして、実際にGitHub ActionsでNode.jsのプロジェクトを高速に動かすための基本のワークフロー(設定ファイル)を見てみましょう。
プロジェクトのルートディレクトリに `.github/workflows/build.yml` というファイルを作成し、次のように記述します。
基本のワークフロー設定例
name: CI Build
mainブランチへのプッシュやPR時に実行
on:
push:
branches: [ “main” ]
pull_request:
branches: [ “main” ]
jobs:
build:
# 標準のUbuntu最新環境を指定
runs-on: ubuntu-latest
steps:
# 1. リポジトリのコードをランナーにチェックアウト
- name: Checkout Code
uses: actions/checkout@v4
# 2. Node.jsのセットアップ
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’ # npmのキャッシュを有効化して爆速化の第一歩
# 3. 依存関係のインストール
- name: Install Dependencies
run: npm ci
# 4. アプリケーションのビルド
- name: Run Build
run: npm run build
これが最も基本的な「HelloWorld」ならぬ「Hello CI」の構成です。まずはこの形をベースにします。
—
【ここからが本番】リソース消費を抑える極限のチューニング技法
ここからが、今回のメインディッシュです。
デフォルトのランナーで起きがちな「メモリ制限の回避」と「CPUプロセスの並列化最適化」を実践する、2つの裏技を紹介します。
裏技その1:Node.jsのメモリ制限(Heap Out of Memory)を華麗に回避する
React、Next.js、Vue、あるいは大型のTypeScriptプロジェクトなどをビルドしていると、以下のエラーに絶望したことはないでしょうか?
> `FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed – JavaScript heap out of memory`
これは、Node.js自体がデフォルトで使えるメモリ量にフタをしているために起こります。ランナーにはまだメモリが残っているのに、Node.jsがそれを使わせてくれないというもどかしい状態です。
対策:環境変数でメモリの上限を強制拡張する
GitHub Actionsのステップ(またはジョブ全体)で、環境変数 `NODE_OPTIONS` を指定して、使えるメモリの最大値を引き上げます。
- name: Run Build with Optimized Memory
# ランナーのメモリ(約7GB)のうち、4GBをNode.jsのヒープ領域に割り当てる設定
env:
NODE_OPTIONS: “–max-old-space-size=4096”
run: npm run build
たったこれだけの設定で、巨大なアセットのバンドルや型チェックも、メモリ不足で落ちることなくスムーズに完了するようになります。
—
裏技その2:CPUのコア数を完全に使い切る「並列処理パラメータ」の最適化
GitHub-hosted runner(`ubuntu-latest`)のCPUは、実は 2コア(2 vCPU) 割り当てられています。
しかし、テストツール(JestやVitestなど)やビルドツールは、デフォルトのままだとマシンのコア数をうまく認識できず、1つのコアだけでちまちま動いたり、逆にコア数を無視して暴走したりすることがあります。
CPUのパワーを100%引き出し、ビルド時間を最小化するための最適化がこちらです。
対策:ツールの並列度(Worker数)を明示的に指定する
例えば、テストランナーのJestを使う場合、CI環境ではCPUのコア数に合わせて明示的にワーカー数を指定するのが鉄則です。
- name: Run Tests with Optimized CPU Workers
# 2コアのCPUをフル活用するため、maxWorkersを指定
run: npx jest –maxWorkers=2
もし、Viteやesbuildなどの最新の高速ビルドツールを使っている場合も、環境変数やオプションでCPUの限界までスレッドを割り当てることで、待ち時間を劇的に短縮できます。
—
すべてを統合した「極限最適化」ワークフローの実例
これまでの知見をすべて詰め込んだ、現場でそのまま使える完璧なワークフローの完全版がこちらです。
name: Production-Ready Optimized CI
on:
push:
branches: [ “main” ]
pull_request:
branches: [ “main” ]
jobs:
optimized-build:
runs-on: ubuntu-latest
# ジョブ全体で共通の環境変数を定義することも可能
env:
NODE_OPTIONS: “–max-old-space-size=4096” # Node.jsのメモリ制限を4GBに拡張
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’
- name: Install Dependencies
run: npm ci
# Lintや型チェック(CPU 2コアをフル活用)
- name: Run Type Check & Lint
run: npm run lint
# テストの実行(–maxWorkersでCPUリソースを最適化)
- name: Run Unit Tests
run: npx jest –maxWorkers=2 –ci
# ビルドの実行(拡張されたメモリとCPUで高速化)
- name: Build Application
run: npm run build
—
まとめ:日々の開発を劇的に楽にしよう
今回は、GitHub Actionsの実行時パフォーマンスを最適化する「ランナーのメモリ・CPU割り当て」の裏技について解説しました。
- Node.jsのメモリ不足には `NODE_OPTIONS=”–max-old-space-size=4096″` で上限を拡張する
- CPUのパワーを無駄にしないよう、テストツール等の並列度(ワーカー数)を明示的に指定する
この2つを意識するだけで、CIの不安定なエラー(OOM)から解放され、ビルド時間も驚くほど短縮されます。
「CIが遅い・落ちる」というストレスから解放されると、コードを書く純粋な楽しさに集中できるようになります。これをマスターすれば、あなたのチームの生産性は間違いなく一段上のステージに引き上げられますよ。
明日からの開発に、ぜひ取り入れてみてくださいね!