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 和全局安装的命令又是另外两层,不会被这个命令一起处理,完整的整理顺序可以接着看开发工具缓存清理。