<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SCCACHE on Tony Bai</title><link>https://tonybai.com/tags/sccache/</link><description>Recent content in SCCACHE on Tony Bai</description><generator>Hugo</generator><language>zh-cn</language><copyright>2004-2026 Tony Bai. 版权所有.</copyright><lastBuildDate>Tue, 30 Jun 2026 06:00:00 +0800</lastBuildDate><atom:link href="https://tonybai.com/tags/sccache/index.xml" rel="self" type="application/rss+xml"/><item><title>一个 Rust 项目吃掉 75GB 硬盘？聊聊 Go 与 Rust 的“缓存焦虑”与拯救指南</title><link>https://tonybai.com/2026/06/30/rust-project-eating-75gb-disk-space-go-vs-rust-cache-anxiety/</link><pubDate>Tue, 30 Jun 2026 06:00:00 +0800</pubDate><guid>https://tonybai.com/2026/06/30/rust-project-eating-75gb-disk-space-go-vs-rust-cache-anxiety/</guid><description>本文探讨了现代编译器（以 Rust 和 Go 为例）为追求极致构建速度而引发的“磁盘吞噬”现象。文章深入剖析了 Rust 凭借其项目局部 target 目录带来的空间膨胀问题，以及 Go 语言在全球化依赖目录 pkg/mod 与构建缓存 GOCACHE 下的存储开销。作者通过对比两者的缓存机制，指出开发者面临的普遍“缓存焦虑”。文中还提供了详尽的解决方案：对于 Rust，通过 cargo-sweep、sccache 等工具及共享 target 目录进行优化；对于 Go，利用其内置的缓存自动清理机制及前沿的 GOCACHEPROG 协议实现远程编译缓存共享。文章强调，在软件工程中，“空间换时间”是编译器的底层逻辑，而随着分布式编译技术的发展，开发者最终将摆脱单机固态硬盘的物理容量限制。</description><content:encoded><![CDATA[<p><img alt="题图" loading="lazy" src="/images/wp-content/uploads/2026/rust-project-eating-75gb-disk-space-go-vs-rust-cache-anxiety-1.png"></p>
<p><a href="https://tonybai.com/2026/06/30/rust-project-eating-75gb-disk-space-go-vs-rust-cache-anxiety">本文永久链接</a> – <a href="https://tonybai.com/2026/06/30/rust-project-eating-75gb-disk-space-go-vs-rust-cache-anxiety">https://tonybai.com/2026/06/30/rust-project-eating-75gb-disk-space-go-vs-rust-cache-anxiety</a></p>
<p>大家好，我是Tony Bai。</p>
<p>在系统级编程的世界里，开发者们似乎永远在为两件事焦虑：一是代码能不能编译通过，二是编译究竟要花多长时间。</p>
<p>为了解决第二个问题，现代编译器进化出了极度贪婪的“胃口”。它们在你的本地疯狂下载依赖、保存中间产物、缓存增量编译结果——用一种近乎粗暴的方式，<strong>拿你的物理硬盘空间，去换取你生命中的几秒钟编译时间。</strong></p>
<p>最近，在 Reddit 的 <code>r/rust</code> 社区， 一位开发者发帖求助：《<a href="https://www.reddit.com/r/rust/comments/1ufaef8/my_rust_workspace_is_75gb_is_this_normal_for/">我的 Rust 工作区（Workspace）达到了 75GB，对于长期项目来说，这正常吗？》</a> ，这个经过几个月开发的项目，其 <code>target</code> 目录已经被塞得满满当当。</p>
<p><img loading="lazy" src="/images/wp-content/uploads/2026/rust-project-eating-75gb-disk-space-go-vs-rust-cache-anxiety-2.webp"></p>
<p>原以为大家会帮他排查问题，没想到评论区瞬间变成了“比惨大会”和“凡尔赛现场”：</p>
<ul>
<li><em>“老弟，才 75G？一切正常。我刚敲了一行 <code>cargo clean</code>，清出了 300GB 空间，感觉整个人都升华了。”</em></li>
<li><em>“我的记录是 1TB 的纯缓存，因为里面存了 300 个表的数据库crate 增量编译结果……”</em></li>
<li><em>“为了回答你这个问题，我专门买了一块新的 2TB 固态硬盘，只为了写 Rust。”</em></li>
</ul>
<p>面对这种夸张的“磁盘吞噬战”，无论是深陷 <code>target</code> 泥潭的 Rust 玩家，还是常年看着 <code>GOMODCACHE</code> 日益膨胀的 Go 语言开发者，都患上了严重的“缓存焦虑症”。</p>
<p>今天，我们就来深度剖析一下：为什么现代编译器变得如此贪吃？我们又该如何通过社区的黑科技与官方的底层机制，拯救我们奄奄一息的固态硬盘？</p>
<p><img loading="lazy" src="/images/wp-content/uploads/2025/paid/the-ultimate-guide-to-go-module-qr.png"></p>
<h2 id="病理分析为什么-rust-和-go-都在疯狂吞噬你的硬盘">病理分析：为什么 Rust 和 Go 都在疯狂吞噬你的硬盘？</h2>
<p>虽然最终结果都是“磁盘空间不足（Disk Full）”，但 Rust 和 Go “吃硬盘”的姿势和底层逻辑却截然不同。</p>
<h3 id="1-rust-的空间黑洞局部而疯狂的-target-目录">1. Rust 的空间黑洞：局部而疯狂的 <code>target/</code> 目录</h3>
<p>在 Rust 中，万恶之源就在你项目根目录下的那个 <code>target/</code> 文件夹。如果你用 <code>du -sh target/</code> 查看一下，你大概率会被那个数字吓倒。</p>
<p>Rust 极其贪吃的原因有三个：</p>
<ul>
<li><strong>重复编译的孤岛效应</strong>：默认情况下，Cargo（Rust 的包管理器）是按**项目局部（Per-project）**管理编译产物的。这意味着，如果你有 10 个独立的项目都依赖了 <code>tokio</code> 和 <code>serde</code>，那么这 10 个项目会各自在自己的 <code>target/</code> 目录下把这两个庞然大物重新编译一遍，并保存中间件。</li>
<li><strong>增量编译与环境矩阵</strong>：每次你修改一行代码，Rustc 为了加快下次编译的速度，会生成大量的增量文件。更要命的是，<code>dev</code> 模式、<code>release</code> 模式、甚至你切换了一次 Rustc 的版本（比如从 1.94.0 升到 1.95.0），旧版本的编译构件（Artifacts）都不会被自动删除，而是层层叠加。</li>
<li><strong>庞大的调试符号（Debug Symbols）</strong>：在默认的开发模式下，Rust 会生成极其详尽的调试信息，这些信息的大小往往是最终二进制文件的十倍甚至百倍。</li>
</ul>
<h3 id="2-go-的隐秘仓库全局膨胀的-pkgmod-与-gocache">2. Go 的隐秘仓库：全局膨胀的 <code>pkg/mod</code> 与 <code>GOCACHE</code></h3>
<p>相比之下，Go 的设计哲学显得更为全局观，但同样不可避免地会随着时间的推移而“发福”。</p>
<p>自 Go Modules 普及以来，Go 放弃了基于项目局部的 vendor 目录，转而将所有依赖都下载到了一个全局的、不可变的目录中（通常是 <code>~/go/pkg/mod</code>）。</p>
<ul>
<li><strong>只增不减的 Module 仓库</strong>：这种设计的好处是：10 个项目共用同一个依赖版本，硬盘上只存一份源码。但坏处是，随着你接手的项目越来越多，各种依赖的历史版本（v1.1, v1.2, v1.5&hellip;）会不断堆积，Go 默认并不会自动删除这些旧代码。</li>
<li><strong>深藏功与名的构建缓存</strong>：为了实现标志性的“秒级编译”，Go 在后台默默维护了一个全局的构建缓存目录（可通过 <code>go env GOCACHE</code> 查看）。这里面塞满了编译过的包对象（<code>.a</code> 文件），这让你在修改自己的代码时，不需要重新编译标准库和第三方库。</li>
</ul>
<h2 id="拯救行动rust-玩家的续命指南">拯救行动：Rust 玩家的“续命指南”</h2>
<p>对于被 <code>target</code> 目录折磨的 Rustacean 来说，生存策略可以分为“毁灭性打击”和“精准优化”两种。</p>
<h3 id="1-物理毁灭cargo-clean">1. 物理毁灭：<code>cargo clean</code></h3>
<p>这是最解压的命令。敲下 <code>cargo clean</code>，几百 GB 的空间瞬间释放。</p>
<p>但代价是惨痛的——下次编译时，你将不得不花上几十分钟盯着屏幕上的 <code>Compiling xxx</code> 进度条发呆。这属于典型的“爽一时，痛一天”。</p>
<h3 id="2-精准外科手术社区的极客神器">2. 精准外科手术：社区的极客神器</h3>
<p>为了不把洗澡水和孩子一起倒掉，Reddit 上的老手们推荐了一系列神器：</p>
<ul>
<li><strong><code>cargo-sweep</code></strong>：这是一个极其优雅的插件。你可以用 <code>cargo sweep --installed</code> 清理掉那些属于旧版本编译器的残留构件；或者用 <code>cargo sweep --time 30</code> 删掉过去 30 天都没有被访问过的僵尸文件。</li>
<li><strong><code>sccache</code></strong>：这是由 Mozilla 开发的神器，相当于 C/C++ 世界的 <code>ccache</code>。它不仅能接管编译缓存，还能<strong>在多个不相关的 Rust 项目之间共享相同的依赖编译结果</strong>。当你开启 <code>sccache</code> 后，那些重复的 <code>tokio</code> 编译噩梦将不复存在。</li>
<li><strong><code>kondo</code></strong>：如果你是一个全栈开发者，电脑里混杂了大量的 Rust、Node.js (<code>node_modules</code>) 和 Go 项目。这个命令行工具可以一键帮你递归扫描并清理所有项目的构建垃圾。</li>
</ul>
<h3 id="3-架构级调优全局共享-target">3. 架构级调优：全局共享 Target</h3>
<p>许多拥有多个相关 Rust 仓库的开发者，会在 <code>~/.cargo/config.toml</code> 中配置全局的 <code>build.target-dir</code>。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#a6e22e">build</span>]
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">target-dir</span> = <span style="color:#e6db74">&#34;/path/to/global/target&#34;</span>
</span></span></code></pre></div><p>这样，所有的项目都会把编译产物丢进同一个大池子里。虽然这个池子依然会变大，但至少你享受到了跨项目的依赖复用红利。</p>
<h2 id="go-团队的底层降维打击从自动老化到可编程缓存">Go 团队的底层降维打击：从自动老化到可编程缓存</h2>
<p>与 Rust 社区依靠丰富的第三方插件进行“自救”不同，Go 语言的解决方案往往带有强烈的“官方统筹”与“工程极简”色彩。</p>
<p>在依赖与缓存管理上，Go 官方其实在底层布下了多道防线，甚至开放了极其前沿的黑科技。</p>
<h3 id="1-自动老化的全局-gocache">1. 自动老化的全局 <code>GOCACHE</code></h3>
<p>不同于 Rust 那个只要你不删就永远存在的 <code>target</code> 目录，Go 的 <code>GOCACHE</code> 机制自带了一个非常克制的<strong>自动垃圾回收（GC）机制</strong>。</p>
<p>Go 会自动跟踪缓存项的最近使用时间（mtime）。Go 命令在退出时，会检查距离上次清扫是否已超过 1 天，若是则触发一次扫描，删除那些超过 5 天未被访问的构建缓存条目。这也是 Go 开发者很少会遇到单一缓存目录膨胀到几百 GB 的原因。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-gdscript3" data-lang="gdscript3"><span style="display:flex;"><span><span style="color:#f92672">//</span> <span style="color:#f92672">$</span>GOROOT<span style="color:#f92672">/</span>src<span style="color:#f92672">/</span>cmd<span style="color:#f92672">/</span>go<span style="color:#f92672">/</span>internal<span style="color:#f92672">/</span>cache<span style="color:#f92672">/</span>cache<span style="color:#f92672">.</span>go
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span> Time constants <span style="color:#66d9ef">for</span> cache expiration<span style="color:#f92672">.</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span> We set the mtime on a cache file on each use, but at most one per mtimeInterval (<span style="color:#ae81ff">1</span> hour),
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span> to avoid causing many unnecessary inode updates<span style="color:#f92672">.</span> The mtimes therefore
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span> roughly reflect <span style="color:#e6db74">&#34;time of last use&#34;</span> but may <span style="color:#f92672">in</span> fact be older by at most an hour<span style="color:#f92672">.</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span> We scan the cache <span style="color:#66d9ef">for</span> entries to delete at most once per trimInterval (<span style="color:#ae81ff">1</span> day)<span style="color:#f92672">.</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span> When we <span style="color:#66d9ef">do</span> scan the cache, we delete entries that have <span style="color:#f92672">not</span> been used <span style="color:#66d9ef">for</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span> at least trimLimit (<span style="color:#ae81ff">5</span> days)<span style="color:#f92672">.</span> Statistics gathered from a month of usage by
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span> Go developers found that essentially all reuse of cached entries happened
</span></span><span style="display:flex;"><span><span style="color:#f92672">//</span> within <span style="color:#ae81ff">5</span> days of the previous reuse<span style="color:#f92672">.</span> See golang<span style="color:#f92672">.</span>org<span style="color:#f92672">/</span>issue<span style="color:#f92672">/</span><span style="color:#ae81ff">22990.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> (
</span></span><span style="display:flex;"><span>	mtimeInterval <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span> <span style="color:#f92672">*</span> time<span style="color:#f92672">.</span>Hour
</span></span><span style="display:flex;"><span>	trimInterval  <span style="color:#f92672">=</span> <span style="color:#ae81ff">24</span> <span style="color:#f92672">*</span> time<span style="color:#f92672">.</span>Hour
</span></span><span style="display:flex;"><span>	trimLimit     <span style="color:#f92672">=</span> <span style="color:#ae81ff">5</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">24</span> <span style="color:#f92672">*</span> time<span style="color:#f92672">.</span>Hour
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><h3 id="2-清理指令">2. 清理指令</h3>
<p>如果你真的硬盘见底了，Go 也提供了两个极其安全的一键清理指令：</p>
<ul>
<li><code>go clean -cache</code>：清空编译缓存（和Rust一样，下次编译会变慢，但不会丢失源码）。</li>
<li><code>go clean -modcache</code>：清空下载的全局 Module 依赖源码（下次编译会重新联网下载代码）。</li>
</ul>
<h3 id="3-前沿黑科技gocacheprog-协议的无限想象">3. 前沿黑科技：<code>GOCACHEPROG</code> 协议的无限想象</h3>
<p>这是 Go 1.21 以实验性功能引入、并在 Go 1.24 正式稳定的一个鲜为人知、但极具颠覆性的环境变量——<code>GOCACHEPROG</code>。</p>
<p>在过去，构建缓存只能老老实实地写在本地硬盘上。但 Go 团队意识到，对于大型微服务团队和 CI/CD 平台来说，局部的硬盘缓存已经满足不了需求了。</p>
<p>通过实现 <a href="https://github.com/golang/go/issues/59719">GOCACHEPROG 接口</a>，<strong>开发者可以编写一个独立的、与 Go 编译器通信的外部程序，彻底接管缓存的读取与写入逻辑！</strong></p>
<p>这意味着什么？</p>
<ul>
<li>你可以写一个 Go 程序，把编译缓存直接写进<strong>公司的 Redis 集群或者 AWS S3 存储桶</strong>中。</li>
<li>整个公司的所有开发者、以及所有的 CI 构建流，都可以<strong>实时共享同一个远程编译缓存池</strong>。一个人编译了某个底层库，全公司受益！这不仅彻底拯救了所有人的本地硬盘，更是将大型项目的构建速度推向了理论极限。</li>
</ul>
<h3 id="4-未来展望module-缓存的极致压缩">4. 未来展望：Module 缓存的极致压缩</h3>
<p>对于Go mod cache的膨胀问题，Go 的社区目前也在积极探讨对 <code>pkg/mod</code> 目录的物理优化，包括希望<a href="https://github.com/golang/go/issues/28835">提供module 的remove命令</a>来支持手动删除Go module cache目录下的某个版本的go module。</p>
<p>由于解压后的 Module 源码包含大量的碎小文件，这会极其消耗 Linux 系统的 inode 资源和磁盘空间。社区中已有第三方工具（如 Tailscale 开源的 <a href="https://github.com/tailscale/gomodfs">gomodfs</a>）探索以 FUSE/NFS 方式将 zip 格式的依赖包直接挂载为文件系统，从而避免解压带来的磁盘和 inode 开销。</p>
<h2 id="小结空间与时间的永恒博弈">小结：空间与时间的永恒博弈</h2>
<p>从 Reddit 上的“75GB 惨案”，到各大语言社区绞尽脑汁的缓存优化，我们看到的是软件工程学中最经典的博弈：<strong>空间换时间</strong>。</p>
<p>编译器是聪明的。它知道程序员的薪水（时间）比固态硬盘要昂贵得多。所以，它宁愿被骂作“硬盘杀手”，也要尽可能把一切中间状态缓存下来，只为了在你按下 <code>Ctrl+S</code> 的那一刻，能以最快的速度把报错信息拍在你脸上。</p>
<p>幸运的是，无论是 Rust 社区诸如 <code>sccache</code> 的极客工具，还是 Go 语言底层开放的 <code>GOCACHEPROG</code> 协议，都在向我们证明：<strong>随着云原生和分布式编译的演进，我们终将打破单机物理硬盘的桎梏。</strong></p>
<p>最后，敲下这篇文章时，我默默看了一眼我的 <code>~/.cargo/</code> 和 <code>~/go/</code> 目录。</p>
<p><strong>那么，正在阅读这篇文章的你，敢不敢在终端里输入一行 <code>du -sh</code>，在评论区晒出你的“硬盘惨案”呢？</strong></p>
<p>资料链接：https://www.reddit.com/r/rust/comments/1ufaef8/my_rust_workspace_is_75gb_is_this_normal_for/</p>
<hr>
<p>还在为写 Agent 框架频频死循环、上下文爆炸而束手无策？我的新专栏 <strong>《<a href="http://gk.link/a/12IzL">从0 开始构建 Agent Harness</a>》</strong> 将带你：</p>
<ul>
<li>抛弃臃肿框架，回归“驾驭工程 (Harness Engineering)”的第一性原理</li>
<li>用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等，复刻极简OpenClaw</li>
<li>构建坚不可摧的 Safety Middleware 与飞书人工审批防线</li>
<li>在底层实现 Token 成本审计、链路追踪与自动化跑分评估</li>
<li>从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”</li>
</ul>
<p>扫描下方二维码，开启从 0 开始构建Agent Harness 的实战之旅。</p>
<p><img loading="lazy" src="/images/wp-content/uploads/2026/build-agent-harness-from-scratch-qr.png"></p>
<hr>
<p><strong>原「Gopher部落」已重装升级为「Go &amp; AI 精进营」知识星球，快来加入星球，开启你的技术跃迁之旅吧！</strong></p>
<p>我们致力于打造一个高品质的 <strong>Go 语言深度学习</strong> 与 <strong>AI 应用探索</strong> 平台。在这里，你将获得：</p>
<ul>
<li><strong>体系化 Go 核心进阶内容:</strong> 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏，夯实你的 Go 内功。</li>
<li><strong>前沿 Go+AI 实战赋能:</strong> 紧跟时代步伐，学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等，掌握 AI 时代新技能。</li>
<li><strong>星主 Tony Bai 亲自答疑:</strong> 遇到难题？星主第一时间为你深度解析，扫清学习障碍。</li>
<li><strong>高活跃 Gopher 交流圈:</strong> 与众多优秀 Gopher 分享心得、讨论技术，碰撞思想火花。</li>
<li><strong>独家资源与内容首发:</strong> 技术文章、课程更新、精选资源，第一时间触达。</li>
</ul>
<p>衷心希望「Go &amp; AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚，享受技术精进的快乐！欢迎你的加入！</p>
<p><img loading="lazy" src="http://image.tonybai.com/img/tonybai/gopher-and-ai-tribe-zsxq-small-card.jpg"></p>
<hr>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img loading="lazy" src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png"></p>
<!-- 
公众号地址：
-->
]]></content:encoded></item></channel></rss>