npm 快取越用越大,Mole 這次只清沒有索引參照的資料
最近有使用者提到,用 Mole 維護完 Mac,npm 的快取還在,自己清理以後從 8 GB 變成了 2 GB。這則回饋讓我重新看了一遍開發工具的快取,哪些內容還能用,哪些已經沒有參照,還有哪些工具自己就在清,Mole 再清一次反而讓下一次建置多花時間。
這次調查和改動發生在 10 月 5 日,程式碼已經補上,會在後續版本提供,目前發布的 Preview 版本還不包含這些改動,下面寫的是這次開發過程中的實測和取捨。
npm 的快取為什麼會越用越大
npm 預設把快取放在 ~/.npm/_cacache,content-v2 保存套件和套件來源傳回的內容,index-v5 記錄哪個請求能找到哪份內容,檔案按內容的雜湊命名,同一份內容可以被多個索引項目參照。
套件資訊更新後,新內容會被寫進去,舊索引項目在後續查詢時可能被整理掉,舊檔案卻還留著,時間一長,就累積出一批已經沒有索引指向的檔案。npm 的文件也說明,快取不會自行清理,會隨著安裝繼續增長。
不過那位使用者已經清過自己的快取,我沒拿到清理前的副本,也不能確認他具體執行了哪個指令,所以 8 GB 降到 2 GB 是這次調查的起點,不能當成 Mole 已經清出了 6 GB,更不能說這 6 GB 全是沒有參照的內容。
副本測試中,verify 也刪掉了仍可使用的資料
實驗用的是本機快取的副本,npm 11.19.1、cacache 20.0.4,原快取沒有改動,它剛建立約一天,內容檔案一共 309796162 位元組,還不是一份累積了幾個月的快取。
| 判定方式 | 內容檔案數 | 內容位元組數 |
|---|---|---|
| 沒有任何有效索引項目參照 | 1 | 614295 |
npm cache verify 實際刪除 |
6 | 25469972 |
差別來自 cacache 的 verify 實作,它清理內容、驗證完整性,再重建索引,同一個 key 只保留最後一筆,舊項目裡卻可能還有不同請求需要的完整中繼資料和精簡中繼資料,不能簡單當成同一份內容的新舊版本。
在副本上,verify 刪除的內容裡有 11199796 位元組仍被有效的另一種請求項目參照,清理前能離線讀取的幾個套件資訊,清理後再離線執行 npm view,卻出現 ENOTCACHED 錯誤,連線以後可以重新取得,但快取原來能做的事已經變了。
所以這裡選了更窄的判定,只要任何一筆有效索引還參照著它,就留著,即使這次只找到約 0.6 MB,也不為了接近 verify 的數字去刪另一種請求仍然需要的內容。
清理頁列出未被參照的資料,整份快取仍可手動選擇
Mole 的新實作只掃描預設的 ~/.npm/_cacache,沒有參照、超過一天的內容會合成一列,放在清理頁的開發工具分組裡,預設勾選,整個 npm 快取仍然需要自己確認,兩種選擇重疊的部分也只計算一次。
掃描時遇到 npm install、npm ci 這類正在寫入快取的指令就先不處理,讀不清索引也不列出來,刪除前還會重新確認參照,索引檔案和暫存目錄都不改,Mole 也不會在背景呼叫 npm 的清理指令。
保留索引仍然指向的內容,不等於保證每個專案永遠能離線建置,鎖定檔還可能按雜湊直接找某個套件,私有套件來源和舊版本也未必始終能重新下載,原始碼、鎖定檔和需要保留的產物仍然要另外保存。如果設定了其他 npm 快取路徑,這一輪也沒有涵蓋。
Go、Cargo 和 pnpm,不能照著 npm 再做一遍
Go 本來就會自動整理建置快取,實作裡保留最近五天用過的項目,再留一小時的時間餘裕,也就是 121 小時,Mole 以前把整個建置快取目錄列進預設清理,這次改成同樣的時間規則,保留最近還會用到的項目,不用在下次建置時全部重新編譯。
Cargo 已經有自動快取回收,這次樣本裡沒有到期內容,之前清掉解壓後的套件來源,約 29 小時後又下載了約 157 MB、解壓出約 1.06 GB,所以這次沒有再做一份 Cargo 自動回收,原來需要自己確認的清理項仍然保留。
pnpm 的共用套件儲存區又不同,這次 APFS 樣本的硬連結數量無法判斷檔案是否仍被使用,直接套用這個條件會把整個儲存區都選進去,所以這次沒有新增 pnpm 自動回收,要用硬連結數量來清理,得先確認它在這個檔案系統上真的能反映使用狀態。
全域工具和舊 Xcode 專案裡的建置產物
全域安裝的 pake-cli 裡有一個 src-tauri/target,約 1.48 GiB,是它建置 Tauri 時留下的 Rust 產物,不是 npm 下載快取。現在這種由工具寫下有效 CACHEDIR.TAG 標記的建置目錄,也會作為待確認項列出來,工具本身和它的 node_modules 相依套件不會跟著刪,Rust 建置還在執行時也先留著。
Xcode 那邊還找到 24 份原專案路徑已經不存在的 DerivedData,調查時約 9 GiB,只有啟動磁碟上的專案能確認已經刪除、Xcode 至少兩週沒有用過對應快取,才會列出整個目錄,外接磁碟沒接上、沒有讀取權限,都不能算作專案不存在。這兩個大小是當時找到的內容,不是承諾每台 Mac 都能釋放這麼多,再需要建置時,仍然要花時間重新產生。
自己處理時,先確認正在清哪一層
想看 npm 快取在哪,可以先執行下面這個唯讀指令,自訂路徑會由 npm 自己回報。
npm config get cache
確認沒有安裝和建置正在寫入這份快取,再決定是否執行 npm cache verify,它會實際修改快取,不只是查看,專案的 node_modules 和全域安裝的指令又是另外兩層,不會被這個指令一起處理,完整的整理順序可以接著看開發工具快取清理。