← 文章 / 云原生与基础设施
Hacker News 4小时前 · 2026-10-14 02:51:08 · 8 阅读

重塑 Anthropic 的源代码控制

在 Anthropic,编码已不再是系统构建或科研工作的瓶颈。Claude 能够自主驱动科研迭代循环,对复杂程序进行微观优化,并以我们前所未见的高度和规模从零构建新服务。真正的瓶颈在于那些专为人类规模设计的开发者基础设施:版本控制、构建和持续集成(CI)。

2026 年 5 月,Anthropic 平均每天合并 2500 次代码变更;到 9 月,这一数字已升至每天 30,000 次。我们预计这种增长速度短期内不会放缓。合并代码的潜在需求几乎是无限的,每消除一个瓶颈,都会立即导致我们产出的代码量进一步激增。

2026 年 1 月至 9 月,每天合入单仓(monorepo)的变更次数。

为了支撑这种增长,我们开发了内部版本控制系统 Scion,并在五个月内将单仓完全从 Git 迁移过去。同期,我们还构建并上线了代码审查界面(名为 Antique)、代码搜索功能、一套完善的版本控制 IAM 系统,以及管理落地大量已审查代码的新方法等。在本文中,我们将重点介绍 Scion 的具体构建过程。这是一个极具意义的案例研究:Claude 编写代码的能力既是我们陷入当前状况的根源,也是我们能够迅速构建出高质量、可扩展系统来应对挑战的关键。

扩展我们的单仓

在探讨具体构建内容之前,我们必须承认我们的负载模式非常独特。毫不意外,Anthropic 使用 Claude 编写软件的频率远超几乎所有其他公司,因此我们往往是最早看到专为人类规模设计的工具开始吃紧的团队。版本控制仅是其中一个例子。虽然我们不认为许多组织在近期会面临这些问题,但猜想其他使用 AI 编码工具的机构很快也会遇到同样的压力点,因此我们希望分享我们的构建成果和经验教训。

Git 是一个去中心化的系统:每个 clone 都是一个完整独立的仓库,可以完全离线地计算 diff、查看历史和 blame、检出任意版本。对于绝大多数仓库来说,这是正确的设计,也是 git 能成为事实标准的重要原因。 但随着我们的 monorepo 不断增长,它越来越不适合这个设计空间。仅 main 分支的历史就接近 100 GB,压缩后的 working copy 也超过 25 GB。我们每天要为 CI 执行约 2500 万次代码检出,这还没算开发环境、agent 沙箱和研究任务。如果按原始方式拉取,每天将产生超过 600 PB 的 git 流量。 Git 提供了一些只操作仓库部分内容的工具,比如 shallow clone、sparse checkout 和 promisor remote。除此之外,针对大型 monorepo 也有一些标准做法,比如搭建内部 git 镜像,以及在 blob 存储中保存仓库快照(seed)。这些我们都用过——高峰期我们维护了数百个内部 git 镜像来应对负载。这为我们争取了时间,但随着合并频率上升,运维难度越来越大。seed 距离 main 最新提交越来越远,要让依赖历史的操作(如 diff 和 blame)在部分 clone 上正常工作,也需要投入越来越多的精力。 退一步看,我们发现和大多数公司一样,我们实际上把 git 当作集中式系统使用——只有一份权威副本——而我们的客户端几乎全是在线运行的 agent 和任务。git 那些为分布式、离线工作而设计的优秀特性,反而成了仓库规模扩张的障碍。于是我们问自己:如果从一开始就围绕「agent 始终在线」这一前提来设计版本控制,它会是什么样子?

面向 agent 规模的版本控制设计原则

在 Agentic coding 的推动下,软件开发的诸多常规假设正在发生转变。我们在构建 Scion 时积极拥抱了这些变化,使其在若干核心原则上采取了与 Git 截然不同的方法。大型 monorepo 的源代码管理已有先例,例如 Google 的 Piper 和 Meta 的 Sapling,我们在设计 Scion 时也借鉴了这些系统积累的宝贵经验。我们的核心原则如下:

客户端始终在线。 Git 假设客户端处于离线状态,且需要持有仓库的完整副本。由于我们的客户端几乎全是 Agent 和任务,我们设计了 Scion 的客户端和协议,使其围绕一个始终可达的服务器运行,客户端仅在需要时才拉取数据。

客户端与服务器的非对称性。在 Git 中,每个客户端都有能力充当一个完整的服务器。而在 Scion 中,服务器负责回答文件历史、blame 查询,创建 commit,执行 merge 和 rebase 等操作。客户端维护一个有界的本地对象缓存,在磁盘上布局工作副本文件,监控文件系统变更,并为用户提供便捷的操作界面。客户端自身能计算的能力很有限,但作为交换,它对本地数据的要求也大幅降低。

全面的源代码管理服务可以驱动许多创新应用。在 Git 下,CI 任务或工具若需查询仓库信息(如某行最后由谁修改,或两个 commit 之间的关系),必须维护一个本地仓库副本,或寄希望于厂商特定的 API 能提供答案。而 Scion 将客户端使用的所有源代码管理查询都通过 API 暴露出来,因此许多任务根本无需本地仓库。如果没有这种廉价的程序化访问方式,许多内部工具(如我们的自动化 bisect 和 revert 流水线)将无法在规模化时实用运行。

一切必须支持水平扩展。在峰值期间,我们运行了数百个 git 镜像,这给系统容量带来了巨大压力,且当仓库规模超出镜像的磁盘或内存限制时,系统便无法继续扩展。在 Scion 中,仓库数据是分布式的,每个组件都可以独立扩展;服务器甚至都没有仓库的完整实体副本。由于没有 packfiles(git 的压缩对象包)这类概念,因此无需进行仓库维护。

提交必须低成本、增量化且无锁。在 git 中,你是在本地提交后推送以分享工作成果。而在 Scion 中,所有的提交、rebase 或 merge 操作都在服务器端进行,因此无需推送,但每项操作都必须足够快。创建提交及更新其下游数据结构的成本,必须随着变更大小而非仓库整体规模而扩展,否则服务器无法跟上节奏。Scion 的开发生命周期尽量避免使用 refs(指向提交的指针,如分支和标签),主 ref 或发布分支除外,因为所有服务器都需达成一致的全局状态是悬而未决的扩展难题。

工作副本是可选的,且在设计上稀疏。由于我们的 monorepo 规模庞大,且每天为 CI 检出代码数千万次,我们希望尽可能避免检出操作。使用 Scion 时,例如需要某个提交与其合并基点之间 diff 的 CI 任务,可以直接向服务器请求。写入操作同理:许多写入工作负载也可以跳过检出,完全通过 API 完成。Scion 协议以稀疏检出为核心,仅包含客户端请求的目录;同时支持虚拟化检出,按需实体化文件。这样,当任务确实需要磁盘上的文件时,客户端只为实际读取的内容付出成本。

Scion 的工作原理

了解这些原则在构建 Scion 过程中是如何落地的,会很有帮助。这些洞察可应用于其他源代码控制系统,或更广泛地应用于软件开发生命周期的其他阶段。

数据结构

在 Scion 中,工作单元是“变更集”(changeset),即写前日志(write-ahead log)中的一个条目。变更集存储的内容和 git commit 大体相同。区别在于:git commit 存储一个指向 tree 的指针,而变更集存储相对于其第一个父提交1发生变更的文件列表,外加指向 blob 存储中压缩文件内容的内容寻址指针。变更集在服务端创建,随时随处立即可用。
Scion 中变更集的结构。
这种结构让我们能以极高的吞吐量接受变更集,因为不需要接收 packfile,没有锁或跨服务器协调,也没有 refs 需要移动。严格来说 Scion 也支持 refs,但由于创建和拉取 commit 都不涉及 refs,移动一个 ref 只是对单行 Spanner 数据做一次 compare-and-swap,与变更集的创建过程相互独立。 变更集只记录发生了什么变更,单靠它无法快速回答诸如“某一行最后是谁改的”“哪些 commit 影响过某个文件”这类问题。为此,Scion 会基于变更集构建额外的数据结构,包括专门用于 file blame 和路径历史查询的结构,以及 commit graph 的跳表。我们把变更集本身之外的所有数据统称为“派生数据”(derived data),并通过一套通用机制来计算。每种派生数据的定义都是递归的:某个变更集上的取值由其父提交上的取值推导而来。 这套机制带来三个好处。第一,当新的数据结构能加速某些操作时,可以轻松新增一种派生数据。第二,对每种派生数据,我们可以自行决定是即时计算还是延迟处理,避免物化不需要的数据。第三,由于客户端看不到这些数据结构的内部实现,我们可以在不改动客户端的前提下对它们做优化。随着系统不断演进,我们已经多次新增派生数据类型,比如用于拷贝检测或按路径搜索文件的索引。

Scion 的扩展性

少量 Scion 服务器即可为客户端所需的各项操作提供源码管控 API,例如获取变更集、计算 diff、查找 merge base 以及拉取 blob。所有源码管控数据均基于内容寻址,这意味着数据一旦存储便永不改变,任何服务器都可以独立缓存数据,无需相互协调。这使得服务器具有横向扩展能力,且协调开销极低。本质上,它们充当大型缓存,还能实时计算派生数据并将结果写回 Spanner。

Scion 的众多使用方不再需要维护本地仓库。获取和维护仓库曾是 CI 任务及需要查询源码管控信息(尤其是提交历史和 blame)的工具的重大维护负担。在迁移初期,由于尚不具备专属客户端、仓库格式和协议,Scion 仅处理这些“元数据”查询。拉取文件内容和实例化工作副本是数据密集型操作,因此需要单独的设计。

Scion 的 scn 客户端在多个关键方面偏离了 git 的 fetch 协议,从而支持高度可扩展且比 git 服务器消耗 CPU 更少的架构。Scion 的协商过程并不告知服务器客户端已有哪些内容,而是仅围绕客户端请求的 tree 和 blob 进行操作。客户端从根 tree 出发,检查本地存储中缺失的 tree 并递归请求,随后对 blob 执行相同流程。由于每个请求都针对一个永不变化的特定对象,任何缓存节点都能响应,该架构与传统的分级缓存非常相似。此过程高效且支持断点续传,因为不存在单个多 GB 的 packfile 响应。稀疏检出乃至虚拟检出无需特殊支持,客户端只需不请求不需要的 tree 即可。

在整个集群中,客户端通过 socket 与守护进程 scion-daemon 通信,该进程负责存储整个节点的对象。我们大多数节点都需要访问 monorepo,且同一节点上的任务通常会复用大部分相同的对象。每个守护进程从定期刷新的快照启动,随后仅与其所在区域称为 scion-cache 的树和 blob 缓存服务器通信。这些缓存服务器直接从内存中流式传输对象,且采用守护进程可直接消费的磁盘格式,因此它们无需花费时间计算客户端缺失的对象或执行 delta 压缩。当缓存中缺少某对象时,它会向权威的 scion-servers 发起请求,这些服务器会按需从 Spanner 或云存储中读取数据。

scion-daemon、scion-cache 和 scion-server 对同一协议的 RPC 调用均作出一致响应,因此客户端无需感知缓存拓扑结构。理论上任何层级都可以与其他任意层级通信,但我们严格强制执行层级划分,以隔离故障域。我们希望避免某种情形:区域性 scion-cache 的故障压倒上游 scion-servers,进而拖垮整个系统。

Scion 的 checkout 架构。

该系统的每一层均维持超过 99% 的缓存命中率,且无需维护长生命周期的状态,因此扩展十分轻松。就 CPU 和内存需求而言,我们 Scion 基础设施的足迹比之前的 git 镜像基础设施小了 200 倍。CI 和科研任务的代码检出速度快了两个数量级(从分钟级降至秒级),从而释放了集群中大量容量;仅 CI 就实现了 25% 的 CPU 容量提升。客户端和缓存均无需执行仓库维护,这简化了任务的生命周期管理。

构建 Scion 的启示

我们在启动构建 Scion 的同期,Claude Mythos Preview 和 Claude Tag 也恰好在内部上线。这两个版本的发布既同时加剧了对 Scion 的需求, 又显著加速了其构建速度。

举个特别震撼的例子。今年早些时候,我们基本靠手写完成了第一版 git 到 Scion 的数据接入管道,当时还当作原型来用。我们觉得它已经不错了——直到我们拿到 Claude Mythos Preview 的一个早期内部快照。我们让它重写整个系统,没有给出任何额外指示,它产出的版本内存占用降低了 20 倍,速度快了 4 倍,同时还完整保留了接入顺序和数据结构方面的所有隐式约束。

2026 年 1 月至 9 月,Scion 与 Antique 代码库每周合并的变更数量。

我们在构建和运营 Scion 过程中的经验,对于如何开发 agent 规模的软件有一些有意思的启示。

小团队的快速开发会越来越常见。一个七人团队不仅构建了 Scion 和 Antique(我们的代码评审工具,写一整篇博客都讲不完),还做了半打相关系统,并在五个月内完成了全面迁移。与此同时,每天合并的代码量还增长了 10 倍。在过去,开发并上线这样一套源码管理系统,通常需要一家公司投入更多工程师、花上数年做渐进式迁移。我们相信,这种快速敏捷的迁移方式今后会越来越多。

工程师的价值并未降低,但工作内容正在发生转变。 参与该系统研发的所有工程师都会承认,Claude 在编写代码方面已经优于他们。但工程师们依然提供了对高层级系统组件间交互及设计层面权衡的不可替代的理解,在我们要解决的问题和已知需要达到的规模上做出了明智的选择。很多时候,工程师的作用在于对 Claude 说“不”。Agent 有时会为了满足一个本就不该被允许的模式,而违反核心系统约束。这一点在我们之前的迁移工作中尤为常见。例如,有若干系统试图通过线性读取提交日志来暴力查找变更何时合并到主分支,而不是去查询我们的代码审查工具。Anthropic 大量使用对抗性审查,尽管这在很多方面极其有效,但也可能短视,因此需要资深工程师引导 Claude,避免因假设性的顾虑而对系统某些部分进行过度设计。例如,在一次审查中,Claude 对信任另一个内部服务表现得过于防御性,导致其添加了过多的验证和复杂性。我们的团队成员现在依然审查该系统的每一次变更请求,但他们审视的是变更的意图,而非逐行阅读代码。

我们显著提升了优化和质量水平。 凭借 Claude 能够比我们更快地编写海量代码的能力,Scion 成为我们曾参与过的测试最完善、防御最严密的项目之一。即便在紧迫的时间表下,我们也能够为我们遇到过的每一个非平凡的基础设施和源码控制交互构建封闭的模拟环境,并实施模糊测试(fuzzing)和复杂的速率限制。Claude 还随意发现了显著的优化点——例如,它在一个关于祖先关系的查询算法中发现了我们遗漏的一个索引——而这些优化原本需要专家花费数天甚至数周的时间进行分析和调优……结果第二天,它又把这件事全做了一遍。

原生 Scion 仓库上观察到的性能提升。

我们为单体仓库安全奠定了基础。 在大型单体仓库中,管理安全、合规性的重要性不亚于管理规模和体量。Scion 从设计之初就将这些因素纳入考量。它支持对仓库实施细粒度的访问控制,并允许将权限应用到单独的目录,从而在保护最敏感代码的同时,保留单体仓库的优势。此外,我们构建的代码审查工具能够表达复杂的语义约束,使得简单的纯评论修改与敏感的逻辑变更得以区分,从而让人工审查者将精力集中在影响最大的变更上。

运营卓越性迈上了新台阶。 我们的团队大量使用 Claude Tag,帮助监控系统各组件的健康状况。例如,我们创建了专门的 Slack 频道,指派 Claude 极其专注地监控单一组件的运行状态。我们将这些 Claude 实例称为“超级粉丝(superfans)”。我们有负责 scion-daemon、scion-cache、Antique 合并队列等的“超级粉丝”,它们可以访问遥测数据和日志,表现得就像一位经验丰富的工程师,除了盯着图表寻找异常外无事可做。虽然我们有传统的告警机制,但“超级粉丝”能发现那些微妙的、新出现的问题,相比之下,传统告警显得过于单一。

我们彻底重新审视了先验假设。 这个项目促使我们深入反思那些为人类尺度设计的工作流。例如,传统的合并队列在代理(agent)规模下并没有太大意义。当数百甚至数千个变更在测试运行时合并,CI(持续集成)究竟意味着什么?我们未能有效复现行业标准的全局仓库锁,因此转而投资于合并后和部署前的自动化测试。这确实意味着并发变更可能会冲突,但 Claude 通常比人类更快地修复这些冲突,甚至比人类更早识别出它们。

为更广泛的生态系统做贡献

我们认为,Scion 只是应对越来越强的 AI agent 所带来的扩展性问题的方案之一。我们分享这些细节,是作为一个团队如何应对类似挑战的示例,但并不指望我们的方案或考量适用于所有场景。

Scion 绝不是 git 的通用替代品。这里介绍的理念,真正适用的场景只有超大规模 monorepo——要么有数万名工程师,要么有大量 agent 在持续产出代码。此外,Anthropic 还有一个独特之处:我们在内部使用的 token 开销,远超其他企业实际愿意投入的水平。我们这样做,既是为了满足自身的软件开发需求,也是为了测试 Claude 的能力。

从实际角度看,很难把 Scion 做成对其他组织普遍有用的形式。它与 git 差异很大,采用它意味着公司要迁移开发生命周期的每一个环节,而且还需要配套的代码评审、代码搜索和 CI。这些组件在 Anthropic 内部的版本高度定制化,其他组织很难直接使用;我们担心,试图做一个面向公众、面面俱到的版本,反而不会真正有用。

不过,我们预计这类问题在未来六到十二个月内会越来越普遍,如果每家使用 git 的公司都要独自面对类似挑战,那是对工程资源的浪费。因此,我们认为最有影响力的做法是分享我们的思路,让其他开发者可以把 Scion 中最成功的部分借鉴到 git 上,而不必脱离 git 的整体模型。git 服务端的工作方式不必和客户端一样,完全可以采用能显著提升扩展性的新型服务端架构。git 平台可以像 Scion 那样提供全面的服务端 RPC,甚至可以在各个代码托管平台之间实现标准化,让 git 客户端学会与之通信。协商协议也不必保持今天的形态,git 中各种 sparse 和 shallow 特性也可以设计得彼此良好配合。

原始来源: Hacker News

评论 (0)