Cargo 要自动帮你清理缓存了

很多 Rust 开发者都有一个共同的经历:某天打开终端,df -h 一看,~/.cargo 目录已经悄悄长到了好几个 GB,甚至十几个 GB。

这并不奇怪。Cargo 在 home 目录里积累的缓存种类繁多:从 crates.io 拉下来的索引数据、下载的 .crate 压缩包、解压后的源码、git 依赖的克隆......每个新项目、每次版本更新都会往里加东西,但从来没有东西会被自动删掉。

社区里已经有第三方工具(比如 cargo-cache)来处理这个问题,但 Cargo 官方从未内置过缓存管理能力。

从 nightly-2023-11-17 开始,这件事有了变化。

本文基于 Rust 官方博客 2023 年 12 月 11 日发布的《Cargo cache cleaning》整理撰写,作者为 Eric Huss,代表 Cargo 团队。

原文地址


这个功能是什么

Cargo 在 nightly 频道新增了一个不稳定特性:垃圾回收(GC)。它的核心是两件事:

一是追踪 :Cargo 会在 ~/.cargo/.global-cache 创建一个 SQLite 数据库,记录各类缓存数据最后一次被使用的时间戳。每次运行 cargo buildcargo fetch 等涉及缓存读写的命令,都会更新这个数据库。

二是清理:分为自动和手动两种方式。


自动清理:每天运行一次

启用 GC 后,Cargo 每天会检查一次缓存数据的最后使用时间,对超期未使用的数据执行删除。

默认的超期策略是:

  • 本地可以重新生成(无需重新下载)的数据:超过 1 个月未使用则删除;
  • 需要重新下载才能恢复的数据:超过 3 个月未使用则删除。

这里的区分有一定合理性:解压后的源码目录(src/)不需要网络就能从已有的 .crate 文件重建,所以可以更激进地清理;而 .crate 文件本身和索引数据一旦删除就要重新下载,因此给了更宽松的窗口期。

有一个细节值得注意:当 Cargo 处于离线模式时(比如加了 --offline--frozen 参数),自动清理会被禁用。 设计者考虑到,如果你正处于长时间离线的环境中,强行删缓存可能会让你无法继续工作。


手动清理:cargo clean gc

除了自动清理,Cargo 还新增了 cargo clean gc 子命令,支持手动指定清理策略。

几个典型的用法示例:

bash 复制代码
# 删除超过 3 天未使用的下载数据
cargo clean gc --max-download-age=3days

# 将下载缓存总大小控制在 1 GiB 以内
cargo clean gc --max-download-size=1GiB

这对 CI 环境尤其有用------很多 CI 系统会缓存 ~/.cargo 目录来加速构建,但缓存太大又会拖慢上传和恢复速度,cargo clean gc 可以在构建结束后做一次精准的瘦身。

需要说明的是,目前这个子命令的 CLI 设计还属于实验阶段,稳定化之前可能会有调整。


如何启用

这是一个不稳定特性,目前需要 nightly 版本。在 ~/.cargo/config.toml 中加入:

toml 复制代码
[unstable]
gc = true

或者通过环境变量临时开启:

bash 复制代码
CARGO_UNSTABLE_GC=true cargo build

也可以用 CLI flag 对单次命令生效:

bash 复制代码
cargo build -Zgc

启用后,Cargo 会在首次运行时扫描已有的缓存目录,建立初始的追踪数据库。这次初始扫描可能会有一点延迟,之后的日常使用影响极小(测试数据显示额外开销在 0 到约 50ms 之间)。


背后的设计考量

这个功能听起来简单,实现层面却有不少值得一提的权衡。

性能:每次 Cargo 运行都需要更新数据库,但团队特别注意不要让这影响正常的构建体验。实际删除操作每天只做一次,而不是每次命令后都做,就是为了把开销集中在一个时间点,而不是分散地拖慢每一次构建。

并发锁:这是改动中技术上最复杂的部分。Cargo 之前只有一把包缓存锁,现在改为三种锁定状态:多个构建可以同时持有共享读锁;下载新包时持有独立的写锁(不影响其他构建并行读取);清理缓存时持有排他写锁,阻止任何读取。

值得注意的是,1.75 之前的 Cargo 不认识这把排他写锁。在新旧版本并存的环境里理论上存在冲突风险,但团队认为这种情况在实践中极少出现。

文件系统兼容性:Cargo 对各种异常情况做了容错设计。如果无法获取锁(比如在只读文件系统上),GC 会静默跳过,不会报错中断你的工作。这对 Docker 挂载目录和网络文件系统(NFS)这类场景尤其重要。

向后兼容:缓存数据会被各个版本的 Cargo 共用,因此数据库格式的稳定性至关重要。团队选择 SQLite 的原因之一正是它的磁盘格式自 2004 年以来保持稳定,且支持向后兼容的 schema 迁移。


还不包括哪些内容

有一点需要特别说明:这次 GC 功能清理的是 ~/.cargo 全局缓存,不包括项目的 target/ 目录。

target/ 目录往往才是磁盘空间的真正大户,在多项目开发场景下轻松超过百 GB。对 target/ 的清理支持,Cargo 团队计划通过将 fingerprint 系统迁移到 SQLite 来实现,但这是另一个更大的工程,目前还在规划阶段。


SQLite:Cargo 的下一步

这个 GC 功能对 Cargo 项目还有一层额外的意义:它是 SQLite 在 Cargo 中的初次大规模实战。

团队明确表示,SQLite 在 Cargo 中的使用还会扩展到更多地方,目前规划中的有两个方向:

注册表索引缓存:现在 Cargo 把索引缓存存储为大量小文件,在某些文件系统上性能不佳,而且同样无限增长。未来可能会改用 SQLite 存储,同时支持细粒度的时间戳追踪和定期清理。

target 目录管理:Cargo 现在用一套叫做"fingerprint"的机制来判断哪些内容需要重新编译,每个构建产物对应 4 个格式各异的文件。团队计划把这套机制也迁移到 SQLite,届时不仅可以清理过期数据,还有望改进追踪精度、提升性能,并提供"为什么这个 crate 被重新编译了"之类的调试信息。


小结

Cargo 的缓存自动清理功能,解决了一个长期存在但被忽视的问题:本地缓存只进不出。

它的设计策略是保守的:每天一次、按使用时间决定去留、离线时不动、遇到错误静默跳过。这些选择都优先保证用户体验的平稳,而不是激进地压缩磁盘占用。

对于多数开发者来说,启用之后基本感觉不到它的存在------直到某天发现 ~/.cargo 比以前小了不少。


相关推荐
淡然一笑3532 小时前
Linux服务器安装失败全面指南:常见问题深度解析与高效解决策略
后端
小谢小哥2 小时前
41-分布式事务详解
后端
IT_陈寒2 小时前
Java集合的这个坑,我调试了整整3小时才爬出来
前端·人工智能·后端
落木萧萧8252 小时前
MyBatisGX 示例工程:CRUD + 关联查询完整演示
java·后端
fliter2 小时前
一个徽章坏了,顺带扯出了 2.3 万个 feature
后端·架构
tumeng07112 小时前
Spring详解
java·后端·spring
jwt7939279373 小时前
Spring之DataSource配置
java·后端·spring
黑牛儿3 小时前
Swoole协程 vs Go协程:PHP开发者一看就懂的实战对比
后端·golang·php·swoole
Rust研习社3 小时前
深入理解 Rust 裸指针:内存操作的双刃剑
开发语言·后端·rust