Node.jsのnode-compile-cache肥大化対策|Astro 7で3.2GB溜まる原因と抑止設定
作成日:

Node.jsのnode-compile-cache肥大化対策|Astro 7で3.2GB溜まる原因と抑止設定


論旨

Astro 7 や Vite などのモダンなフロントエンド環境で日々のビルドを回していると、気づかないうちに一時フォルダ($TMPDIR)の空き容量が数ギガバイト単位で消失していることがあります。当ラボの 16GB RAMDisk 環境をディスク可視化アプリ『DaisyDisk』で調査したところ、なんと node-compile-cache 単体で 3.2GB(124,472 ファイル) もの細切れバイナリが堆積していました。

結論からお伝えすると、Astro 7 のビルド時に Node.js の V8 バイトコードキャッシュ機能が「二度と再利用されないランダムハッシュ名の使い捨て .mjs チャンク」まで毎回新規保存し続け、しかも Node.js 側には古いキャッシュを自動削除する上限管理(LRU 整理)が存在しないこと が肥大化の真犯人です。

本記事では、ビルドコマンドに公式環境変数 NODE_DISABLE_COMPILE_CACHE=1 を 1 つ添えるだけでフルビルド後のキャッシュ容量を 3.2GB → 1.0MB(約 99.97% 削減) へ封じ込めた実測手順と、Google Chrome キャッシュ等を手動掃除する際に絶対踏み抜いてはいけない「3 つの安全境界」を一次データにもとづいて徹底解説します。

合わせて読みたい深まる関連情報
対象となる方
  • Astro・Vite・Next.js などの Node.js 系ビルドツール を日常的に実行しており、一時フォルダや SSD の空き容量がじわじわ減っている方

  • Mac で RAMDisk に $TMPDIR や各種キャッシュを集約 しているものの、数日経つと数ギガバイトの謎の領域に圧迫されて困っている方

  • Google Chrome のキャッシュや tmp フォルダを手動で掃除 するときに、「どのフォルダならいつ消しても安全で、どれを消すとログイン情報や容量計算が壊れるのか」の正確な境界線を知りたい方

DaisyDiskで発覚した3.2GBの巨大キャッシュ

当ラボでは、内蔵 SSD の書き込み寿命(TBW)を徹底保護しビルド処理を高速化するため、メイン機の 27 インチ 5K iMac(iMac20,1:メモリ 64GB 搭載)に 16GB、サブ機の 27 インチ iMac(iMac15,1:メモリ 32GB 搭載)に 8GB の APFS RAMDisk(/Volumes/RAMDisk/$USER)をログイン時に自動構築して運用しています。

ある日、日々のメンテナンス作業の中で「そういえば Google Chrome のキャッシュや一時ファイルって、いつでも・どのフォルダでも手動で消して大丈夫なんだっけ?」と疑問が湧き、現在の RAMDisk 内の使用状況を正確に把握するため、DaisyDisk 公式マニュアルでも案内されているスタンドアロン版アプリで /Volumes/RAMDisk/$USER/tmp(環境変数 $TMPDIR の割り当て先)をスキャンしてみました。

すると、画面上に予想外の巨大な色ブロックが浮かび上がったのです。

DaisyDiskで可視化したRAMDisk内tmpフォルダの容量内訳(node-compile-cacheが3.2GBを占有)

なんと、一時フォルダ tmp 全体の使用量 3.3 GB のうち、node-compile-cache という単一フォルダだけで 3.2 GB(実に 97% 近く!) を独占していました。ターミナルから du -sh(2 進数の GiB 換算表示)で実測しても 3.0G という堂々たる塊です。

質問するおじさん

わしは最初、Chrome の動画キャッシュか何かが RAMDisk を食いつぶしとるんかと思うたんよ。まさか tmp の奥底で Node.js のキャッシュが 3.2GB も居座っとるとは夢にも思わんかったわい!

解説ナビゲーター

16GB の RAMDisk のうち 3.2GB ですから、これだけで全体の 20%(5 分の 1) を丸ごと占領していた計算になりますね。もし 8GB の RAMDisk 環境や、空き容量の少ないノート Mac の内蔵 SSD だったら見過ごせない規模です。

ビルド環境のフォルダに12.4万個の細切れファイル

なぜ node-compile-cache がここまで膨れ上がったのか、ターミナルからディレクトリ内部の構造を掘り下げて調査しました。このフォルダの下には、実行された Node.js のバージョンやアーキテクチャごとにサブディレクトリが作られます。

実際に各サブディレクトリの容量とファイル数を計測した結果がこちらです。

  • v24.20.0-x64-964aae3f-501(通常ツール実行用) : 容量 59 MB

  • v24.22.1-x64-...(2 系統) : 容量 2.3 MB × 2

  • v25.4.0-x64-392347a2-501(Astro ビルド実行環境) : 容量 3.0 GiB(DaisyDisk 10 進表記で 3.2 GB)/ ファイル数 124,472 個

普段のちょっとしたスクリプト実行で使われる v24 系のキャッシュは数メガ〜数十メガバイトで健全に収まっています。ところが、ブログの本番ビルドで稼働している v25.4.0-x64-... のディレクトリを開いた瞬間、目ん玉が飛び出ました。数キロバイト〜数十キロバイトの細切れバイナリファイルが 12 万 4,472 個 もすし詰めになっていたのです。

どれくらい異常な数かというと、そのディレクトリ内でファイルの更新日時を調べようと何気なく stat * コマンドを叩いたところ、シェルの引数上限(ARG_MAX)を突破して zsh: argument list too long: stat とエラーで弾かれてしまうほどでした。

バイナリ解析で判明した使い捨てチャンクの正体

この 12.4 万個ものキャッシュファイルは、一体何をコンパイルした残骸なのでしょうか。キャッシュディレクトリ内に転がっていたバイナリファイル(例:0000868b など)のヘッダ文字列を strings コマンドで直接覗いてみたところ、決定的な 裏付け(エビデンス) が飛び出してきました。

# キャッシュバイナリ(0000868b)の先頭文字列を抽出した結果
file:///Users/$USER/Desktop/ai-blog-project/.astro/.prerender/chunks/astro-ts-rust-code-tree-sqlite-outline-guide_BTWEzK_K.mjs

記録されていたパスは、node_modules 内の固定ライブラリではありませんでした。Astro が静的 HTML を生成(プリレンダー)する過程で一時的に吐き出した .astro/.prerender/chunks/記事スラッグ_<ランダムハッシュ>.mjs というチャンクファイルだったのです。

質問するおばさん

ちょっと待って!ファイル名の後ろに _BTWEzK_K.mjs みたいなランダムな英数字がついてるじゃないの。これってビルドするたびに毎回違う名前で作られるってこと?

解説ナビゲーター

大正解です!まさにそこが最大の落とし穴でした。ファイル名に毎回異なるハッシュ値が付与されるため、前回のキャッシュが二度と再利用されないまま、ビルドのたびに新規ファイルとして無限に積み上がっていたのです。

Rust化したAstro 7でも肥大化する構造的原因

ここで 1 つの素朴な疑問が浮かびます。当ラボは 2026 年 11 月 7 日に、Astro 5からAstro 7へ完全移行した全記録 で紹介した通り、最新の Astro 7(v7.4.7)・Rust 製高速 MDX コンパイラ 『Sätteri』・そして Rust 製バンドラ 『Vite 8 Rolldown』 へと全面移行したばかりです。

「もしかして、この 3.2GB のキャッシュは Astro 5 時代の古い残骸が残っていただけなのでは?」と疑い、ファイル群の最終更新タイムスタンプを厳密に照合しました。しかし、そこには Astro 7 へ移行した後の 11 月 12 日〜 14 日にかけて生成されたばかりの最新ファイル がずらりと並んでいました。つまり、Rust 化した最新鋭の Astro 7 環境であっても、現在進行形で毎ビルドごとに大量の Node キャッシュが生産され続けていたのです。

変換はRustでもHTML生成はNode.jsが担う

なぜ「コンパイラもバンドラも Rust 化したはずの Astro 7」で、Node.js のコンパイルキャッシュが爆発するのでしょうか。そのカラクリは、静的サイトジェネレーター(SSG)における 「前半のバンドル工程」と「後半のプリレンダー(HTML 書き出し)工程」の役割分担 にあります。

Astro 7ビルドにおけるRustとNode.jsの役割分担と肥大化箇所
Step 1
高速変換・バンドル(Rust 🦀)

Rust製コンパイラ・Sätteri・Vite 8 Rolldownが全記事を高速変換し、.astro/.prerender/chunks/記事_<hash>.mjs を出力します。

Step 2
HTML生成実行(Node.js 🟢)

静的HTMLを書き出すため、Node.jsランタイムが生成された数百〜約1,000個の .mjs チャンクを次々と import() して実行します。

Step 3
使い捨てキャッシュの無限堆積

ランダムハッシュ名の1回きり .mjs まで毎回 $TMPDIR/node-compile-cache へ新規保存され、上限なく3.2GBまで肥大化します。

前半の変換・バンドルがRustで高速化されても、後半のHTML生成でNode.jsが全チャンクをimport()する構造が鍵となります。

図解の通り、記事の Markdown / MDX を解析して JavaScript のチャンクへ束ねる前半戦は、間違いなく Rust(Sätteri + Rolldown)が爆速で処理しています。しかし、束ねられた .astro/.prerender/chunks/*.mjs を実際に動かして最終的な .html ファイルへとレンダリングする後半戦は、Node.js の V8 エンジン自身がそれらの .mjs ファイルを順番に import()(読み込み・評価)して実行 しているのです。

当ラボのブログは現在、公開記事だけでも 756記事(ビルド対象ファイル総数 4,400 件超)の規模があります。そのため、1 回のフルビルドが走るたびに数百〜約 1,000 個ものプリレンダー用 .mjs チャンクが一時生成され、Node.js に読み込まれていました。

自動有効化されたV8バイトコードキャッシュの罠

では、なぜ Node.js は一時ファイルにすぎない .mjs チャンクまで律儀にキャッシュしてしまったのでしょうか。犯人は、Astro CLI の起動エントリーポイントである node_modules/.bin/astro の先頭 7〜 10 行目に書かれたコードにありました。

// node_modules/.bin/astro の先頭部分に組み込まれている処理
if (!process.env.CI) {
  // CI(継続的インテグレーション)環境でなければコンパイルキャッシュを有効化する
  module.enableCompileCache?.();
}

ここで呼び出されている module.enableCompileCache() は、Node.js 公式ドキュメント(module.enableCompileCache)に記載されている通り、Node.js v22.8.0 で導入された オンディスク V8 コードキャッシュ機構 です。一度読み込んだ JavaScript / ESM モジュールの V8 コンパイル結果(バイトコード)をディスクの $TMPDIR/node-compile-cache に保存しておくことで、2 回目以降の CLI 起動を高速化する仕組みになっています。

実際、この機構は「毎回同じファイルパスで読み込まれる node_modules 内の固定パッケージ群」に対しては非常に有効です。ところが、module.enableCompileCache() には 「一度有効化されると、その Node.js プロセス内で後から読み込まれるすべてのモジュール(一時生成された .mjs を含む)を無差別にキャッシュ対象にしてしまう」 という特性があります。

その結果、以下の負の連鎖が起きていました。

💥 ビルドごとにキャッシュが無限増殖する3段階メカニズム
  1. ビルドごとにファイル名のハッシュが変わる : Vite / Rollup / Rolldown はコンテンツやチャンク構成に応じて 記事名_BTWEzK_K.mjs のようなハッシュ付きファイル名を生成します。

  2. Node.js が「未知の新規モジュール」と判定する : ファイルパスが前回と 1 文字でも異なれば、Node.js のコンパイルキャッシュ機構は「まだキャッシュされていない新しいモジュールだ!」と真面目に判定し、V8 バイトコードを新規バイナリファイルとして $TMPDIR/node-compile-cache に書き出します。

  3. 元の .mjs はビルド完了後に消えるがキャッシュは永遠に残る : .astro/.prerender/chunks/ 本体用の一時ファイルはビルドが終われば入れ替わりますが、$TMPDIR/node-compile-cache に書き込まれたバイトコードは誰にも再利用されないまま永遠に残り続けます。

当ラボでは記事を執筆・改修するたびに必ず本番ビルドを回すため、ビルドごとにファイル名のハッシュが変わる「二度と再利用されない 1 回きりの使い捨て .mjs チャンク」まで毎回新規エントリとして $TMPDIR/node-compile-cache へ無限に追記され続け、わずか数日間で 124,472 個・3.2GB という巨大な地層を形成していたのです。

内蔵SSDの寿命を密かに削り続ける見えない脅威

今回の現象で特に恐ろしいのは、「RAMDisk を使っていない普通の Mac / PC ユーザーほど、この肥大化に気づけない」 という点です。

当ラボでは環境変数 TMPDIR を意図的に /Volumes/RAMDisk/$USER/tmp へ向け、さらに定期的に DaisyDisk で中身を監視していたからこそ現行犯で逮捕できました。しかし、標準の macOS 環境では $TMPDIR は /var/folders/xx/xxxxxxxxxxxxxxxx/T/ という、Finder の通常表示では絶対に見えないシステム深層フォルダに割り当てられています(Linux なら /tmp/node-compile-cache、Windows なら %LOCALAPPDATA%\Temp\node-compile-cache)。

つまり、MacBook Air や Mac mini で普通に Astro やフロントエンド開発を行っている方の環境では、今この瞬間も内蔵 SSD の見えない奥底に何万個もの細切れキャッシュが書き込まれ続け、貴重な SSD の空き容量と書き込み寿命(TBW)を毎回のビルドで無意味に削り取っている 可能性が極めて高いのです。

質問するおじさん

うちも最初は油断しとったんよ。実はこれまで動かしとった自動掃除スクリプトでは RAMDisk の「直下」だけを見張っとって、まさか一時フォルダ($TMPDIR)のさらに奥に隠れて作られとるとは見落としとったんじゃ。

ノルウェージャンフォレストキャット

にゃー……(見えない場所ほどホコリとキャッシュが溜まるのは、お部屋のソファの下と同じにゃ)。

ご自身の Mac で今どれくらい溜まっているか確認したい方は、ターミナルを開いて以下の 1 行をコピペして実行してみてください(何も削除しない読み取り専用の安全な確認コマンドです)。

# 現在の $TMPDIR 配下にある node-compile-cache の容量とファイル数を確認する
du -sh "${TMPDIR%/}/node-compile-cache" 2>/dev/null && find "${TMPDIR%/}/node-compile-cache" -type f | wc -l

もしここで数百 MB 〜数 GB、ファイル数が数万個と表示されたなら、あなたの環境でもまったく同じ「使い捨てチャンクの無限堆積」が起きています。

Chromeキャッシュとの違いと手動削除の安全境界

さて、原因が判明したところで、今回の調査のきっかけでもあった 「キャッシュって、そもそも放置していいもの?それとも手動で削除していいもの?」 という疑問について、Google Chrome のキャッシュと Node.js の node-compile-cache を比較しながら整理しておきましょう。

実は、同じ「キャッシュ」という名前がついていても、両者の設計思想(お行儀の良さ)は天と地ほど異なります。

Google Chromeのキャッシュ vs Node.jsのnode-compile-cache
自動管理の差
自動新陳代謝あり
Google Chrome のディスクキャッシュ
  • 容量上限(クォータ) : ディスク空き容量に応じて自動で最大枠を計算・制限
  • 古いデータの処分 : 上限に達すると LRU(Least Recently Used)系アルゴリズム で古いファイルから自動削除
  • 放置時の挙動 : 一定サイズで頭打ちになり、無限にディスクを埋め尽くすことはない
上限・自動削除なし
Node.js の node-compile-cache
  • 容量上限(クォータ) : 一切なし(ディスクが満杯になるまで追記し続ける)
  • 古いデータの処分 : LRU などの自動削除(Eviction)機構が未実装
  • 放置時の挙動 : ランダムハッシュのチャンクが読み込まれるたび、OS 再起動や手動削除まで 無限肥大化 する
Chromeには上限枠と自動パージがありますが、Nodeのコンパイルキャッシュには古いファイルを捨てる仕組みがありません。

Google Chromeのキャッシュ削除は不要?LRU自動整理・メモリセーバー設定・URL別保存先振り分け術 でも詳しく検証した通り、Chrome のキャッシュは「上限が来たら古いものから自動で押し出す(LRU 方式)」という優秀な管理人が常駐しています。これに対し、Node.js の node-compile-cache は「一度書いたキャッシュは OS 側で消されるまで置いておく」という素朴な設計のため、ランダムハッシュのチャンクと組み合わさると 最大の障壁(ボトルネック) へと豹変するのです。

自動整理があるChromeと上限なきNodeの差

この違いを知らないまま、「よし、RAMDisk や SSD の空き容量を増やすために、キャッシュと名のつくフォルダを手動で片っ端から削除しよう!」と動くのは非常に危険です。

なぜなら、キャッシュの手動削除には 「①削除するタイミングの境界」「②削除してよいフォルダの境界」「③シンボリックリンクとゴミ箱の境界」 という 3 つの重大な罠が潜んでいるからです。

起動中の直消しで空き容量が戻らないinodeの罠

1 つ目の安全境界は 「アプリが起動している最中に、裏側からキャッシュフォルダを直接削除してはいけない」 というタイミングの鉄則です。

特に Google Chrome が起動している状態で、Finder やターミナルの rm コマンドを使ってキャッシュフォルダの中身を強制削除すると、以下の 2 つのトラブルが同時に発生します。

⚠️ Chrome起動中に裏からキャッシュを直消しすると起きる2大トラブル
  1. 消したはずなのに空き容量が 1 バイトも戻らない(未解放 inode の罠) : macOS(UNIX 系 OS)のファイルシステムでは、実行中のプロセス(Chrome のメインプロセスや Helper プロセス)がファイルをオープンまたはメモリマップ(mmap)して掴んでいる最中にディレクトリ上のファイル名を削除(unlink)しても、プロセスがそのファイルを閉じるまでディスク(RAMDisk / SSD)上の実体データ領域(inode)は解放されません。結果として「Finder 上ではファイルが消えて 0 バイトに見えるのに、RAMDisk の空き容量がまったく増えない(幽霊領域化する)」という現象に陥ります。この幽霊領域は Chrome を Command + Q で完全終了 するまで解放されません。

  2. インデックスと実体ファイルの不整合(ERR_CACHE_READ_FAILURE) : Chrome はメモリ上にキャッシュの目次(インデックス情報)を保持しています。裏から突然ファイル実体だけを抜き取ると、「目次にはあるのにファイルが読めない」という不整合が起き、ページの表示崩れや読み込みエラー(ERR_CACHE_READ_FAILURE)を引き起こします。

質問するおばさん

あーっ!それ経験あるわ!「ファイルは消したのに、なんで空き容量が増えないのよ!」って Mac を睨みつけてたけど、裏で Chrome がファイルを握りしめたままだったのね!

解説ナビゲーター

そうなんです!Chrome 起動中に Finder や rm で裏からキャッシュを消しても、プロセスがファイルを掴んだまま(未解放 inode)になるため、Command + Q で完全終了するまで空き容量は 1 バイトも戻りません。しかも当ラボのサブ機(iMac15,1)のように、起動高速化のために --no-startup-window オプション付きで Chrome をバックグラウンド待機させている場合、「画面にウィンドウが出ていない=終了している」と錯覚しやすいので要注意です。必ず Dock の Chrome アイコンの下に小さな点灯マーク(起動中サイン)がないか確認し、Command + Q で完全終了してから フォルダ操作を行うのが鉄則です。

なお、どうしても Chrome を起動したままキャッシュだけを綺麗に空けたい場合は、フォルダを直接触るのではなく、Google Chrome ヘルプ(キャッシュと Cookie の消去)の手順に従い、Chrome の画面上で Command + Shift + Delete を押して 「キャッシュされた画像とファイル」の 1 項目だけにチェックを入れて削除 してください(ここで Cookie にチェックを入れると全サイトからログアウトされてしまいます)。

一方、Node.js の node-compile-cache については、ターミナルで npm run build や astro build が走っていない待機中であれば、プロセスがファイルを握っていないため いつでもフォルダごと丸ごと削除して 100% 安全 です。

消して良いキャッシュと絶対消せない重要データ

2 つ目の安全境界は、Chrome を完全終了した状態で手動整理する際の 「フォルダ選別の境界線」 です。

macOS における Chrome のデータは、大きく分けて ~/Library/Caches/Google/Chrome/(純粋なキャッシュ領域)と ~/Library/Application Support/Google/Chrome/Default/(ユーザープロファイルの本丸)の 2 箇所に分散しています。後者の Default/(複数プロファイル運用時は Profile 1/ 等)の中には、消していいキャッシュと 絶対に消してはいけない心臓部データ が隣り合わせで同居しています。

事故をゼロにするための判定一覧表をまとめました。

対象パス(Chrome完全終了時) 手動削除の可否 役割と削除時の影響・注意点
~/Library/Caches/Google/Chrome/Default/Cache/ ◎ 安全に削除可 画像・CSS等のWebキャッシュ本体。
削除しても次回アクセス時に再取得されるだけです。
~/Library/Caches/Google/Chrome/Default/Code Cache/ ◎ 安全に削除可 Webサイト上のJavaScript / WebAssemblyのV8コンパイルキャッシュ。
数GBに膨らみやすいですが、削除しても安全に再生成されます。
Default/GPUCache/ / DawnGraphiteCache/ / DawnWebGPUCache/ / ShaderCache/ ◎ 安全に削除可 GPU描画・WebGPU・シェーダーのコンパイルキャッシュ。
肥大化時は削除して問題ありません。
Default/Service Worker/CacheStorage/・ScriptCache/・Database/ ○ 条件付き可
(3点セット同時)
PWA(オフライン対応Webアプリ)やプッシュ通知用のキャッシュ。
数GBへ肥大化しやすい領域ですが、CacheStorage 単体だけを消すと管理DBと不整合を起こすため、消すなら必ず3フォルダ同時に中身を削除 してください。
Default/Cookies ❌ 絶対削除厳禁 全Webサイトのログインセッション情報。
消すとすべてのサイトから強制ログアウトされます。
Default/Login Data / Web Data ❌ 絶対削除厳禁 保存済みパスワードや自動入力データ。
消すとブラウザ保存の認証情報が失われます。
Default/Bookmarks / History / Preferences ❌ 絶対削除厳禁 ブックマーク、閲覧履歴、ブラウザ環境設定の本体です。
Default/Local Storage/ / IndexedDB/ / File System/ ❌ 絶対削除厳禁 Webアプリの下書きデータ、サイト固有の設定、拡張機能のローカル保存領域。
「キャッシュっぽく見える」ため誤爆しやすいですが、消すとWebアプリのデータや拡張機能設定が飛びます。
Default/Extensions/ / Extension State/ ❌ 絶対削除厳禁 インストール済みChrome拡張機能の本体と内部設定データです。
$TMPDIR/node-compile-cache/ ◎ 安全に削除可 Node.jsのV8バイトコードキャッシュ。
ビルド非実行中ならフォルダごと rm -rf しても副作用ゼロで即座に容量が回復します。

特に注意すべきなのが Local Storage や IndexedDB です。名前がストレージ系なので「一時キャッシュかな?」と勘違いして消してしまう方が後を絶ちませんが、ここには Web アプリの作業データや Chrome 拡張機能の設定値が格納されています。手動で掃除する際は、必ず上記の「◎ 安全に削除可」のフォルダだけに限定してください。

シンボリックリンク維持とゴミ箱残留を防ぐ鉄則

3 つ目の安全境界は、当ラボのように Macに16GB RAMDiskを自動構築する方法 の仕組みでキャッシュフォルダを RAMDisk へシンボリックリンク(エイリアスのような矢印)で逃がしている環境特有の注意点です。

  1. シンボリックリンクの「矢印自体」を消さず「中身」だけを消す : たとえば ~/Library/Caches/Google/Chrome 自体を RAMDisk へのシンボリックリンクにしている場合、そのリンク自体を消してしまうと、次回 Chrome を起動した瞬間に 内蔵 SSD 側に本物の実体フォルダが勝手に新規作成 され、せっかくの RAMDisk 隔離が解除されてしまいます。手動で消す際は、必ずリンクを辿った先の /Volumes/RAMDisk/$USER/... 内のサブフォルダや中身だけを削除してください。

  2. Finder では通常の削除ではなく Option + Command + Delete を使う : Finder 上で RAMDisk 内の不要ファイルを選んで通常の Command + Delete(ゴミ箱に入れる)を押すと、ファイルは削除されず RAMDisk ボリューム直下の隠しゴミ箱 /Volumes/RAMDisk/.Trashes/ に移動するだけ です。ゴミ箱を空にするまで RAMDisk の空き容量は 1 バイトも増えないため、Finder から消す際はゴミ箱を経由せず即座に完全削除するショートカット Option + Command + Delete(すぐに削除) を使うのがスマートです。

環境変数1行の根治策と99.9%削減の実測結果

ここまでの調査で、溜まっていた 3.2GB の node-compile-cache を安全に一掃できました。しかし、ただ消しただけでは数日後にまた 12 万個のファイルが復活してしまいます。ここからは、「二度と使い捨てチャンクでキャッシュを肥大化させない恒久対策」 を実装します。

ビルド時のキャッシュ生成を止める環境変数設定

「もしビルド時のコンパイルキャッシュを無効化したら、astro build の速度が遅くなるのでは?」と心配されるかもしれません。しかし、ここで先ほどの node_modules/.bin/astro に書かれていた Astro 開発陣自身の英語コメントを読んでみましょう。

// node_modules/.bin/astro 内の公式コメント
// In CI writing the cache is (most of the time) harmful,
// as it'll never get re-used and just slows down the CLI.
// (CI等の単発ビルドにおいてキャッシュを書き込むことは大抵の場合「有害」である。
//   なぜなら二度と再利用されないばかりか、CLIの動作を遅くするだけだからだ。)

驚くべきことに、Astro の開発チーム自身も 「1 回きりで走り切るビルド処理でコンパイルキャッシュを書き出すのは、再利用されない上にディスク書き込みで足を引っ張るだけなので有害(harmful)だ」 と明言しているのです!

ただ、Astro CLI は「環境変数 CI がセットされているかどうか」だけでこれを判定しているため、私たちがローカルの Mac で実行する npm run build や本番転送スクリプトでは CI 判定にならず、無意味なキャッシュ書き込みが全開で動いてしまっていたわけです。

これを解決する最もクリーンな公式手段が、Node.js 公式ドキュメント(NODE_DISABLE_COMPILE_CACHE)で提供されている環境変数 NODE_DISABLE_COMPILE_CACHE=1 です。

プロジェクトの package.json を開き、astro build を呼び出しているスクリプトの先頭に NODE_DISABLE_COMPILE_CACHE=1 を 1 つ追加します。

{
  "scripts": {
    "dev": "astro dev",
    "build": "NODE_DISABLE_COMPILE_CACHE=1 astro build",
    "preview": "astro preview"
  }
}

たったこれだけです!この設定の素晴らしい点は、「普段の CLI 起動や astro dev ではコンパイルキャッシュの恩恵を残しつつ、大量の使い捨てチャンクが発生する astro build の実行プロセス(およびその子プロセス)だけでピンポイントにキャッシュ書き込みを完全停止できる」 ところにあります。二律背反(トレードオフ) なしで、無駄なディスク I/O と容量圧迫だけを綺麗にゼロにできます。

質問するおじさん

なんと、package.json にその環境変数を 1 個足すだけでええんか!しかも無駄な書き込みがなくなってビルドまで軽くなるとは、一石二鳥どころか一石三鳥じゃな!

引数上限を回避する自動掃除スクリプトの二段構え

package.json の対策で発生源の 99.9% は遮断できましたが、エンジニアがターミナルから直接素の npx astro build を手動実行した場合や、他の Node.js 製 CLI ツールが書き込むキャッシュが長期間で少しずつ溜まるケースもゼロではありません。

そこで当ラボでは、5 分おきにバックグラウンド(LaunchAgent)で回している RAMDisk 同期・メンテナンススクリプト(~/bin/sync_brain.sh)に、以下の 自動パージ機構(二段構えの安全弁) を組み込みました。

# ------------------------------------------------------------------
# Node.js コンパイルキャッシュ ($TMPDIR/node-compile-cache) の自動上限管理
# ※ astro build 実行中は競合防止のためスキップ
# ------------------------------------------------------------------
NCC_DIR="/Volumes/RAMDisk/$USER/tmp/node-compile-cache"
if [ -d "$NCC_DIR" ] && ! pgrep -f "astro build" >/dev/null 2>&1; then
  # 第1ガード:24時間(1440分)以上更新されていない古いキャッシュファイルを自動削除
  # ※ ワイルドカード(*)を使わず find ... -delete を直接実行することで、
  #   万一10万ファイル超が溜まっていても "argument list too long" エラーを完全回避
  find "$NCC_DIR" -type f -mmin +1440 -delete 2>/dev/null || true

  # 第2ガード:それでもディレクトリ合計が 200MB(204,800 KB)を超えていたら丸ごとリセット
  NCC_KB=$(du -sk "$NCC_DIR" 2>/dev/null | awk '{print $1}')
  if [ "${NCC_KB:-0}" -gt 204800 ] 2>/dev/null; then
    rm -rf "$NCC_DIR" 2>/dev/null || true
  else
    find "$NCC_DIR" -mindepth 1 -type d -empty -delete 2>/dev/null || true
  fi
fi

このスクリプトには、実運用で絶対に失敗しないための 3 つの設計ノウハウが詰まっています。

🛡️ 自動掃除スクリプトに組み込んだ3つの堅牢設計(エッジケース対策)
  1. ビルド実行中の競合回避(! pgrep -f "astro build") : 万が一ビルド処理の真っ最中に 5 分タイマーが重なっても、稼働中のプロセスからキャッシュを奪わないよう自動でスキップします。

  2. 引数上限エラー(ARG_MAX)を絶対に出さない find ... -delete : シェルの * 展開や xargs を介さず、find コマンド内蔵の -delete アクションで直接削除するため、仮に 10 万個以上のファイルがあっても確実に掃除できます。

  3. 空文字エラーを防ぐデフォルト値展開("${NCC_KB:-0}") : du -sk の取得タイミングによって万が一変数が空になった場合でも、シェルの数値比較(-gt)が構文エラーで落ちないよう ${NCC_KB:-0} で安全にフォールバックさせています。

フルビルド直後に3.2GBから1.0MBへ激減

それでは、実際に既存の 3.2GB のキャッシュを一度リセットした上で、NODE_DISABLE_COMPILE_CACHE=1 を適用した状態で当ラボの全公開記事(756記事・総出力 4,416 ファイル)のフルビルドを実行し、ビルド完了直後の /Volumes/RAMDisk/$USER/tmp を再計測した実測結果をご覧ください!

計測対象 対策前(デフォルト挙動) 対策後(フルビルド完走直後) 削減効果・内訳の変化
tmp フォルダ全体 3,300 MB
(3.3 GB)
53 MB ▲ 3,247 MB 削減(約 98.4% 削減)
(残りは常駐Rustバイナリ等のみ)
node-compile-cache 単体 3,200 MB
(3.2 GB / 124,472個)
1.0 MB
(1,040 KB / 数個のみ)
▲ 3,199 MB 削減(約 99.97% 削減)
(プリレンダーチャンクの追記 0 件!)
対策前後の node-compile-cache 容量比較(フルビルド完走直後)
NODE_DISABLE_COMPILE_CACHE=1 の導入により、使い捨てチャンクのキャッシュ書き込みが完全ゼロ化(平方根スケール表示)
※桁差視認用・平方根スケール対策前の堆積量:3,200 MB (124,472個)対策後のフルビルド直後:1.0 MB (チャンク追記0件)容量削減率:99.97% 削減
対策前(デフォルト)ビルドのたびに .astro/.prerender/chunks/*.mjs が無限堆積
3,200 MB(3.2 GB / 124,472ファイル)肥大化
対策後(環境変数適用)4,416ファイルのフルビルド完走直後でもCLI起動分の 528KB + 512KB のみ
1.0 MB(1,040 KB / チャンク追記 0件)99.97%削減
※ 当ラボ実機(iMac 2020 / 64GB RAM / 16GB RAMDisk / Astro v7.4.7 / Node.js v25.4.0)におけるフルビルド完走直後の実測値。

結果はまさに劇的でした!4,416 ファイルを生成する巨大なフルビルドを最後まで完走した直後にもかかわらず、/Volumes/RAMDisk/$USER/tmp/node-compile-cache の容量はわずか 1.0 MB(親プロセスである npm や周辺 CLI 自体が起動した際の v24.20.0 キャッシュ 528 KB + v25.4.0 固定キャッシュ 512 KB のみ)でピタリと止まり、問題だった astro build の使い捨てプリレンダーチャンク(.astro/.prerender/chunks/*.mjs)の追記は完全に 0 件 へと封じ込められました。

これにより、16GB RAMDisk のうち丸ごと塞がれていた 3.2GB もの広大なメモリ領域 が完全に解放され、AIエディタの会話ログでRAMDiskが満杯?直近3日保持とSSD自動退避で空き容量を2倍にした構築録 の対策と合わせて、RAMDisk の空き容量には常に 10GB 以上の余裕が保たれるようになりました。

解説ナビゲーター

フルビルドを回しても増分が 0 バイト というのは本当に気持ちがいいですね!無駄な 1,000 個近いキャッシュファイルのディスク書き出し処理が省かれたことで、ビルド全体のレスポンスもさらに軽快になりました。

大容量メモリと高速SSDがもたらす快適コスト

今回のように、node-compile-cache のような突発的な数ギガバイトのキャッシュ肥大化が起きてもシステムが一切スワップせず、しかも内蔵 SSD へ 1 バイトも無駄な書き込みをさせずに防ぎ切れたのは、母艦である iMac に 64GB の大容量メモリ を搭載し、そこから 16GB の RAMDisk を切り出してすべてのテンポラリ・キャッシュ処理をメモリ空間内で完結させる 枠組み(スキーム) を敷いていたからです。

もしこれがメモリ 8GB や 16GB のマシンで内蔵 SSD に直接書き込まれていたら、数万〜十数万個の細切れファイルによるランダムライトと SSD 寿命の摩耗は避けられませんでした。

これから開発母艦のメモリ増設や Mac の新調、あるいは古いログ・アーカイブの高速退避用 SSD を検討されている方のために、当ラボの実運用で中核を担っているハードウェア構成と、長期使用における 「1 日・1 時間あたりの快適コスト(試算)」 をまとめておきます。

【Intel Mac増設用】Crucial iMac 27インチ(2019/2020)・ノートPC用 メモリ DDR4-2666 SO-DIMM (16GB×2 / 32GB×2)

【Intel Mac増設用】Crucial iMac 27インチ(2019/2020)・ノートPC用 メモリ DDR4-2666 SO-DIMM (16GB×2 / 32GB×2)

Apple Mac mini(M4 Pro 等 Appleシリコン Pro系チップ・64GBユニファイドメモリ構成)

Apple Mac mini(M4 Pro 等 Appleシリコン Pro系チップ・64GBユニファイドメモリ構成)

Crucial X9 Pro For Mac 外付けポータブルSSD (1TB / 2TB / 4TB・USB 3.2 Gen 2 最大1,050MB/s)

Crucial X9 Pro For Mac 外付けポータブルSSD (1TB / 2TB / 4TB・USB 3.2 Gen 2 最大1,050MB/s)

パソコンのメモリ増設や上位モデルの選択は、購入時の初期投資だけを見ると高く感じられるかもしれません。しかし、毎日触れる開発道具を「耐用年数 × 実稼働時間」で割ってみると、景色がまったく変わります(以下は 5 年間=約 1,825 日、毎日平均 5 時間=累計約 9,125 時間使用した場合の推論・試算です)。

  • 64GB メモリ増設キット(約 2.2 万〜 2.8 万円前後と仮定した場合の試算) : 5 年間(1,825 日)で割ると 1 日あたりわずか約 12 円〜 15 円、1 時間あたりなら 約 2.4 円〜 3 円 です。缶コーヒー 1 本の 10 分の 1 以下の負担で、16GB RAMDisk と内蔵 SSD 保護、ブラウザ数十タブ+ AI エディタ並列稼働の快適さが毎日手に入ります。

  • 外付け高速 SSD『Crucial X9 Pro For Mac』(2TB モデル・約 2.5 万〜 3 万円前後と仮定した場合の試算) : 5 年間で割ると 1 日あたり約 14 円〜 16 円、1 時間あたり 約 3 円 です。RAMDisk から溢れた過去の AI ログや写真資産を毎秒 1,050MB クラスの速度でストレスなく退避・保管できる保険として、これほど費用対効果の高い投資はありません。

  • 現行 Apple シリコン母艦『Mac mini M4 Pro(64GB ユニファイドメモリ構成)』(約 28 万〜 32 万円前後と仮定した場合の試算) : 5 年間で割ると 1 日あたり約 150 円〜 175 円、毎日 5 時間稼働なら 1 時間あたり約 30 円〜 35 円 です。ビルド待ち時間やメモリ不足によるフリーズで毎日数分〜十数分の人生を削られる損失を考えれば、自分の時間を 24 時間 AI と高速マシンに肩代わりさせる「タイム・イズ・マネー」の投資として十二分に元が取れます。

まとめ:AIプロンプトでキャッシュ診断を自動化

最後に、本記事で解明した node-compile-cache 肥大化の真因と対策、および手動キャッシュ削除の安全境界 を振り返ります。

✅ 本記事の重要ポイントまとめ
  1. Astro 7 でも node-compile-cache が 3.2GB へ肥大化する理由 : 前半の変換・バンドルが Rust(Sätteri + Vite 8 Rolldown)で高速化されていても、後半の HTML 書き出しで Node.js が .astro/.prerender/chunks/記事_<ランダムハッシュ>.mjs を import() し、module.enableCompileCache() によって二度と再利用されない使い捨てチャンクが毎回 $TMPDIR/node-compile-cache へ無限追記されるためです。

  2. 1 行で終わる根治策 : package.json のビルドスクリプトに NODE_DISABLE_COMPILE_CACHE=1 を付与することで、普段の CLI 速度を維持したまま、フルビルド時の使い捨てキャッシュ生成だけを 3.2GB → 1.0MB(99.97% 削減・チャンク追記 0 件) へ完全抑止できます。

  3. Chrome キャッシュ等を手動掃除する際の 3 大安全境界 : ①起動中の裏消しは未解放 inode で容量が戻らないため必ず Command + Q で完全終了してから 行うこと、②Application Support 内の Cookies・Login Data・Local Storage・IndexedDB・Extensions は絶対消さない こと(Service Worker 内は 3 点セット同時削除)、③RAMDisk のシンボリックリンク自体は消さず、Finder では Option + Command + Delete(すぐに削除) を使うことです。

「自分の Mac や開発プロジェクトでも $TMPDIR/node-compile-cache がどれくらい溜まっているか安全に調べたい」「自分の package.json や定期メンテナンススクリプトに合わせて最適に設定したい」という方は、自分でコマンドを打ち間違えて大切なファイルを消してしまうリスクを避けるため、必ず『Gemini 4 Argon』(または同等以上の最上位推論モデル)などの AI エディタを使い、以下のプロンプトをそのままコピー&ペーストして AI に診断と設定を任せてみてください。

以下の記事URLの内容(Node.jsのnode-compile-cache肥大化の仕組み・NODE_DISABLE_COMPILE_CACHE=1による抑止・キャッシュ削除の3大安全境界)を正確に読み込んだ上で、私の環境を診断・最適化してください。
参照記事:https://lab.chihouzakki.com/blog/review/nodejs-compile-cache-bloat-astro7-ramdisk-fix/

【AIへの指示ステップ(必ず順番に実行し、ファイルを変更・削除する前に計画を提示して私の合意を得てください)】
1. 現状の読み取り専用診断(削除はまだ行わないこと):
   - 現在の環境変数 `$TMPDIR` のパスと、`$TMPDIR/node-compile-cache`(およびRAMDisk運用時は `/Volumes/RAMDisk` 配下)の現在の合計容量(du -sh)とサブディレクトリ別内訳・ファイル数を調査して報告してください。
2. プロジェクトのビルド設定診断:
   - 現在のワークスペースの `package.json` を確認し、`astro build`(または単発の本番ビルドコマンド)に `NODE_DISABLE_COMPILE_CACHE=1` を安全に追加する修正案を提示してください。
3. 安全なクリーンアップと自動管理の提案:
   - もし `node-compile-cache` が肥大化していた場合、現在ビルドプロセスが走っていないことを確認した上で安全にリセットする手順(および必要に応じた定期上限管理スクリプト案)を提示してください。
質問するおじさん

見えない一時フォルダの 3.2GB がスッキリ空いて、ビルドのたびに SSD や RAMDisk が削られる心配もゼロになったわい。これで今夜は安心してぐっすり眠れるのう!

ノルウェージャンフォレストキャット

にゃー♪(メモリも SSD も広々快適になったから、あとはゆっくりお昼寝するにゃ)。

キャッシュの肥大化が解消して安心し、ぐっすり眠る猫