モノレポのビルドを「止める」な。pnpmとNode.jsメモリ管理による限界突破のアーキテクチャ
大規模なモノレポ開発において、CI/CDパイプラインや開発者のローカル環境を殺す最大の敵は「OOM(Out of Memory)エラー」です。特に、TypeScriptの型チェックやWebpack/Viteのバンドル処理が並列で走り出した瞬間、Node.jsのヒープメモリは爆発し、ビルドは無慈悲に終了します。
多くのエンジニアは「メモリを増やす」という物理的な解決策に逃げがちですが、真のエンジニアは「並列度」と「割り当て」の数理モデルを制御します。本稿では、pnpmを軸としたモノレポのビルド最適化の極意を伝授します。
—
1. なぜ「並列数」を絞るのか:CPUとメモリのトレードオフ
CI環境(GitHub Actionsの `ubuntu-latest` 等)では、コア数は多いもののメモリが制限されているケースがほとんどです。pnpmのデフォルト設定は「全コアを使い切る」という野心的なものですが、これが悲劇の始まりです。
`–jobs` フラグを適切に設定しないと、各ワークスペースのビルドが同時にメモリを食い合い、スワップが発生してビルド時間が指数関数的に増大します。
推奨設定:CI環境での最適化
GitHub Actionsであれば、実行環境のメモリ量に合わせて以下のように制御します。
.github/workflows/ci.yml のビルドステップ
–jobs 4: 4コア程度を同時稼働させ、メモリ枯渇を防ぐ
–filter: 依存関係グラフに基づき、必要なパッケージのみをビルド
pnpm run build –jobs 4 –filter …[origin/main]
アーキテクトの視点:
ここで重要なのは、「CPUコア数=最適並列数」ではないという点です。TypeScriptのコンパイルはCPUバウンドかつメモリ集約的です。メモリ量に合わせ、`nproc` コマンドで動的に制御するのも賢い手法です。
—
2. NODE_OPTIONSによるヒープ制御の真髄
Node.jsのデフォルトのガベージコレクション(GC)は、メモリ不足に対して非常に遅い反応を示します。特に大規模モノレポでは、`–max-old-space-size` を明示的に設定しないと、Node.jsは限界までメモリを食いつぶそうとします。
`package.json` での構成例
ルートの `package.json` に設定を集約し、チーム全体で共有するのがベストプラクティスです。
{
“scripts”: {
// NODE_OPTIONSを環境変数として注入
// max-old-space-size=4096: ヒープ上限を4GBに制限(CI環境に合わせる)
// trace-warnings: メモリ関連の警告を可視化
“build”: “cross-env NODE_OPTIONS=’–max-old-space-size=4096′ pnpm recursive run build”
}
}
実務の知見:
`–max-old-space-size` は闇雲に増やせばいいわけではありません。物理メモリの8割程度に留めないと、OS側のプロセス管理に影響を及ぼし、かえってビルドが不安定になります。
—
3. 開発スピードを極限まで高める「神設定」
チーム開発において、「誰の環境でも同じパフォーマンスが出る」ことは、ツール導入以上に重要です。
`.npmrc` によるグローバル最適化
プロジェクトルートに `.npmrc` を配置し、以下の設定を加えてください。
pnpmのインストール速度を最大化する
共有キャッシュをフル活用し、不要なI/Oを削る
shamefully-hoist=false
auto-install-peers=true
並列インストール数を制限し、CIのネットワーク負荷を平準化
concurrent-fetches=5
生産性を爆上げする「隠しショートカット」
VS Codeを使っているなら、`pnpm-workspace.yaml` を開いた状態で `Cmd + Shift + P` -> `Developer: Reload Window` を多用してください。ワークスペースの依存関係解決をIDEに即座に再認識させる唯一の確実な方法です。
—
4. チームで共有すべき「絶対ルール」
1. 依存関係の純潔化: `pnpm` の `dependency-check` をCIに組み込み、意図しない依存関係(幽霊依存)を排除してください。これだけで、ビルド時のメモリ消費量が数パーセント改善します。
2. キャッシュ戦略の分離: `actions/cache` を使う際、`node_modules` 全体をキャッシュするのではなく、`~/.pnpm-store` をキャッシュ対象にしてください。これこそが、pnpmの真の力を引き出す唯一の道です。
GitHub Actionsにおけるキャッシュ戦略の核
- uses: actions/cache@v3
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-pnpm-${{ hashFiles(‘/pnpm-lock.yaml’) }}
—
結びに:アーキテクトとしての提言
「ビルドが遅い」「メモリが足りない」と嘆く時間は、エンジニアにとって最も生産性の低い時間です。ツールのデフォルト設定を盲信せず、「実行環境のメモリ境界」を理解し、ツール側の並列実行制御でそれを飼い慣らす。 これこそが、大規模モノレポを制するエンジニアの作法です。
あなたのビルドログからOOMエラーが消えたとき、それは単にビルドが成功したというだけではありません。あなたのアーキテクチャが、開発者の脳内リソースを奪う「無駄な待ち時間」という名のコストを削減したという、立派な功績なのです。
さあ、今すぐ `package.json` を開き、この最適化を適用してください。ビルドの速度が変われば、チームの景色が変わります。