跳到正文
Hacker News· Philpax·· 20 天前精选AI 评分71

Anthropic 重新设计源码管理:用自研 Scion 取代 git

Redesigning source control at Anthropic

AI 导读

Anthropic 发布工程博客,介绍其内部版本控制系统 Scion:7 人团队在五个月内将 monorepo 从 git 完整迁移,单日合并量从 2026 年 5 月约 2,500 次增至同年 9 月超 30,000 次。

推荐理由

原文以 Anthropic 内部迁移为例,给出面向智能体规模重构源码管理的具体设计与性能数据,可供面临同类压力的工程团队参考。

正文 · AI 翻译

在 Anthropic,编写代码已不再是构建系统或开展研究的瓶颈。Claude 能够以我们前所未见的速度和规模,自主迭代研究流程、对复杂程序进行微优化,并从零开始构建全新服务。如今的瓶颈变成了所有为人类规模设计的开发者基础设施:源码管理、构建与持续集成。

2026 年 5 月,Anthropic 平均每天合并约 2,500 次代码变更;到 9 月时,我们每天合并超过 30,000 次。我们预计这种增长速度短期内不会放缓;对合并代码的潜在需求几乎是无限的,每消除一个瓶颈,代码产出量就会立刻进一步增长。

2026 年 1 月至 9 月期间,每天合并到我们单体仓库的变更数。

为了支撑这一增长,我们构建了 Scion——一套内部版本控制系统,并在五个月内将我们的单体仓库从 git 完整迁移过来。在同一时期,我们还构建并发布了名为 Antique 的代码评审界面、代码搜索、完善的源码控制 IAM 系统、管理和落地大量已评审代码的新方法等等。本文将聚焦于 Scion 的具体构建过程。这是一个很有趣的案例研究:Claude 编写代码的能力,既是让我们最初陷入这一处境的原因,同时也让我们得以快速构建出一个高质量、可扩展的系统来应对它。

扩展我们的单体仓库

在介绍我们所构建的系统之前,我们应当先承认,我们的工作负载是非常特殊的。不出所料,Anthropic 使用 Claude 编写软件的频率远高于几乎任何其他公司,因此我们往往最先感受到那些为人类规模设计的工具开始吃紧。源码控制只是其中一个例子。虽然我们并不预期很多组织在近期就会遇到这些问题,但我们预计,那些使用 AI 编码工具的公司并不会太久就会遇到同样的压力点,这也是我们希望分享我们的工作和心得的原因。

Git 是一个去中心化的系统:每个克隆都是一份完整、自包含的仓库,能够计算差异、读取历史与追溯责任,并在完全离线的状态下签出任意版本。对于绝大多数仓库来说,这都是正确的设计,也是 Git 成为业界标准的重要原因。

随着我们的单体仓库不断增长,它越来越不适合这套设计范式。仅我们主干分支的历史就已经接近 100 GB,而一份压缩后的工作副本也超过 25 GB。我们每天要进行大约 2,500 万次代码签出用于 CI,这还不包括开发者环境、Agent 沙箱和研究任务。若以最朴素的方式获取,那每天的 git 流量将超过 600 PB。

Git 提供了一些工具来操作仓库的子集,例如浅克隆(shallow clone)、稀疏签出(sparse checkout)以及 promisor remotes。除此之外,针对大型单体仓库还有一些常见的变通方案,例如内部 git 镜像和保存在对象存储中的仓库快照(seeds)。这些手段我们都用过——在高峰期,我们运行着数百个内部 git 镜像来应对负载。这确实为我们争取到了时间,但随着合并速率不断攀升,这些镜像越来越难以运维。每次刷新之间,seeds 与主干末端的差距越来越大,要让依赖历史的操作(如 diff 和 blame)在部分克隆上正常工作,也需要付出越来越多的细致维护。

用于扩展 git 签出的典型部署方式。

退一步看,我们注意到和大多数公司一样,我们一直把 git 当作一个只有一份权威副本的集中式系统来使用,但我们几乎所有的客户端本质上都是始终在线的代理和任务。git 那些使其擅长分布式离线工作的特性,却在仓库不断增长时严重损害了我们的扩展能力。于是我们反问自己:如果从一开始就以代理始终在线这一事实为前提来设计源码控制,它会是什么样子?

面向代理规模的源码控制原则

随着智能体式编程(agentic coding)的兴起,许多关于软件开发方式的常见假设都在发生变化。我们在构建 Scion 时欣然接受了这些变化,这让我们能够在若干核心原则上采取与 git 截然不同的思路。针对超大规模 monorepo 的源码控制已有先例,最具代表性的是 Google 的 Piper 和 Meta 的 Sapling,因此我们能够汲取他们来之不易的经验来打造 Scion。我们的原则是:

客户端始终在线。Git 假定客户端需要离线工作,并且需要仓库的完整副本。而我们几乎所有的客户端都是代理和任务,因此我们将 Scion 的客户端和协议围绕一个始终可达的服务器来设计,让客户端在需要时按需获取数据。

客户端与服务器的不对称。在 git 中,每个客户端都能充当一个完备的服务器。在 Scion 中,服务器负责回答文件历史和 blame 相关的问题、创建提交、执行合并与 rebase 等操作;客户端则维护一个有界的本地对象缓存、将工作副本文件布局到磁盘上、监听文件系统变更,并为用户提供一种便捷的方式来与服务器协作。它自身几乎不进行计算,但作为交换,它在本地所需的数据量也大大减少。

一套完备的源码控制服务可以支撑许多新型应用。在使用 git 时,一个 CI 任务或工具若要查询仓库相关的问题——比如谁最后修改了某一行、两个提交之间有什么关系——就必须维护一份本地仓库,或者寄希望于厂商特定的 API 能给出答案。而在 Scion 中,客户端所用的全部源码控制查询也都通过 API 开放,因此许多场景下任务完全不需要本地仓库。若没有这种廉价且可编程的源码控制访问方式,许多内部工具(例如我们自动化的 bisect 和 revert 流水线)在大规模下根本不可行。

所有组件都必须支持水平扩展。在峰值时期,我们运行了几百个 git 镜像,这给容量带来了巨大压力,并且一旦仓库规模超出这些镜像的磁盘或内存上限,就再也无法扩展了。在 Scion 中,仓库的数据是分布式的,每个组件都可以独立扩展;即便在服务器端,也不存在任何一份完整物化的仓库副本。这里没有与 packfile(git 的压缩对象包)等价的东西,因此不需要任何仓库维护工作。

提交必须廉价、增量且无锁。在 git 中,你本地提交后再推送以共享成果。在 Scion 中,每一次提交、变基或合并都发生在服务器上,所以无需推送,但这些操作每一步都必须很快。创建提交并更新其下游数据结构的成本必须随变更规模伸缩,而不能随仓库整体规模伸缩,否则服务器就跟不上。Scion 的开发生命周期不鼓励使用 ref(指向提交的指针,例如分支和标签),主 ref 或发布分支除外,因为每个服务器都需达成一致的全局状态是一个迫在眉睫的可扩展性问题。

工作副本是可选的,且设计上就是稀疏的。由于我们的单体仓库极其庞大,并且我们每天要检出代码数千万次以用于 CI,我们希望尽可能避免检出。在 Scion 中,例如,一个需要某个提交与其合并基线之间 diff 的 CI 任务,可以直接向服务器请求。写入也是如此:许多写入负载也可以跳过检出,完全通过 API 完成。Scion 的协议以稀疏检出(只包含客户端请求的目录)和虚拟化检出(按需实例化文件)为核心,因此当任务确实需要磁盘里的文件时,客户端只为它真正读取的部分付费。

Scion 的工作原理

了解这些原则在构建 Scion 时是如何在实践中落地的会很有帮助。这些洞见也可以应用到其他源代码控制系统,或者更广泛地说,应用到软件开发生命周期的其他阶段。

数据结构

在 Scion 中,工作单元是一个“changeset”,即预写日志中的一条记录。一个 changeset 存储了你大多像 git 提交所期望的内容。但 git 提交存储的是指向树的指针,而 changeset 存储的是相对于其第一父节点发生变化的文件列表,1以及指向 blob 存储中压缩后文件内容的内容寻址指针。Changeset 在服务器端创建,并可在瞬间全局可用。

Scion 中 changeset 的形状。

这种结构让我们能够以极高的吞吐量接收 changeset,因为既没有 packfile 需要处理,也没有加锁或跨服务器协调,更不需要移动 ref。Scion 确实在技术上支持 ref,但由于创建或拉取提交都不涉及 ref,移动一个 ref 只是对一行 Spanner 数据行执行一次 compare-and-swap,并且与创建 changeset 是分开进行的。

一个 changeset 只记录发生变更的内容,因此它本身无法快速回答诸如谁最后编辑了某行,或哪些提交影响了某个文件这样的问题。为了回答这些问题,Scion 基于 changeset 构建了额外的数据结构,包括专用于文件 blame 和路径历史的数据结构,以及用于提交图的跳表。我们把所有不直接属于 changeset 的内容称为“derived data”,并通过一种通用机制统一计算。每种 derived data 的定义都是递归的,因此某个 changeset 上的 derived data 值是由其各个父 changeset 上的对应值计算得出的。

这种机制有三个好处。首先,当出现新的数据结构能让某些操作更快时,我们可以轻松添加一种新的派生数据类型。其次,对于每种类型,我们可以选择立即计算还是推迟工作,从而避免物化我们不需要的数据。第三,由于客户端永远不会看到这些数据结构的内部实现,我们可以在不修改客户端的情况下对其进行优化。随着系统的成长,我们已经多次添加新的派生数据类型,例如用于执行重复检测或基于路径的文件搜索的新索引。

扩展 Scion

一个小型 Scion 服务器集群为客户端所需的任何操作提供源代码管理 API,例如获取变更集、计算差异、查找合并基线以及取回 blob。所有源代码管理数据都按内容寻址,因此数据一旦存储就不会再改变,任何服务器都可以在无需与其他服务器确认的情况下对其进行缓存。因此,这些服务器可以水平扩展,几乎不需要协调。它们本质上就是大型缓存,还能够即时计算派生数据并将结果写回 Spanner。

Scion 的众多使用者不再需要维护各自的本地仓库。过去,获取和维护仓库对于需要查询源代码管理问题的 CI 作业或工具来说是一项繁重的维护负担,尤其是关于提交祖先和 blame 的查询。在迁移初期,在我们还没有自己的客户端、仓库格式或协议之前,Scion 只回答这些"元数据"查询。获取文件内容和物化工作副本是数据密集程度高得多的操作,它们需要专门的设计。

Scion 的 scn 客户端在几个重要方面与 git 的获取协议不同,因此它天然适合一种高度可扩展的架构,所需的 CPU 远少于 git 服务器。Scion 的协商不会告知服务器客户端已有什么内容;相反,它只针对客户端想要的树和 blob 进行操作。从根树开始,客户端检查其本地存储中缺失的树并递归请求它们,然后对 blob 重复这一过程。每个请求针对的是一个永远不会改变的特定对象,因此任何缓存都可以回答,其架构也极为接近传统的分层缓存。由于不存在单一的、多 GB 大小的 packfile 响应,这一过程既高效又可恢复。稀疏甚至虚拟化的 checkout 不需要特殊支持,因为客户端只是不会去请求它不需要的树。

在集群中,客户端通过 socket 与一个守护进程 scion-daemon 通信,该守护进程为整个节点存储对象。我们的大多数节点都需要访问 monorepo,并且同一节点上的作业会重复使用大多数相同的对象。每个守护进程从一个定期刷新的快照启动,然后只与所在区域的树和 blob 缓存服务器(称为 scion-cache)通信。这些缓存服务器以守护进程将要使用的磁盘格式直接从内存中流式传输对象,因此它们不需要花时间计算客户端缺失的对象或执行增量压缩。当某个缓存缺少一个对象时,它会向权威的 scion-servers 请求,scion-servers 会根据需要从 Spanner 或云存储中读取。

scion-daemon、scion-cache 和 scion-server 都对该协议响应相同的 RPC,因此客户端无需了解缓存拓扑。理论上任何一层都可以与任何其他层通信,但我们严格实行分层以隔离故障域。我们希望避免出现区域 scion-cache 的故障压垮上游 scion-server 并导致整个系统瘫痪的情况。

Scion 的检出架构。

该系统的每一层都保持着 99% 以上的缓存命中率,并且不需要长期存在的状态,因此可以轻松按需扩容。在 CPU 和内存需求方面,我们的 Scion 基础设施占用的资源现在比之前的 git 镜像基础设施小 200 倍。CI 和研究任务的代码检出速度提升了两个数量级(以秒计而非以分钟计),这为整个集群释放了大量容量;仅 CI 就实现了 25% 的 CPU 容量提升。客户端和缓存都不需要进行仓库维护,这简化了我们任务的生命周期管理。

我们在构建 Scion 过程中的经验

我们开始构建 Scion 的时间点与 Claude Mythos Preview 和 Claude Tag 在内部上线的节点几乎相同。这两次发布既同时放大了对 Scion 的需求,也显著加快了我们构建它的进度。

这里有一个特别惊人的例子。今年早些时候,我们基本上是手工编写了 git-to-Scion 导入流水线的第一版作为原型。我们原本觉得它已经足够好——直到我们拿到了 Claude Mythos Preview 的早期内部快照。我们让它重写整个组件,在没有任何额外指示的情况下,它产出的版本内存占用减少了 20 倍,速度提升了 4 倍,同时保留了有关导入顺序和数据结构的所有隐式不变式。

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

我们在构建和运营 Scion 过程中的经验,为构建智能体规模软件可能呈现的形态提供了一些有价值的启示。

小型团队的高速开发将变得更加普遍。 一个七人团队同时构建了 Scion 和 Antique(我们的代码审查工具,关于它我们完全可以再写一整篇博客文章),更不用说还有一打左右的相关系统,并在五个月内完成了全面迁移。我们在做到这些的同时,每日代码合并量增长了 10 倍。过去,开发和部署这样的源代码控制系统需要一家公司耗时数年进行迭代迁移,并投入多得多的工程师。我们认为这种快速、灵活的迁移在未来会变得更加普遍。

工程师的价值并未降低,但其工作内容正在发生变化。所有参与过该系统研发的工程师如今都会认为 Claude 在编写代码方面比他们更强。但我们的工程师依然提供了对高层跨系统组件以及设计层面权衡取舍的宝贵理解,让我们能够围绕想要解决的问题以及我们清楚需要达到的规模做出明智的选择。我们经常发现,工程师的职责变成了对 Claude 说「不」。智能体有时会为了满足某种模式而违反核心系统约束,而这种模式本来就不应被允许。在我们的迁移过程中这种情况尤为常见。例如,有多个系统试图通过线性读取提交日志来暴力查询变更合入主干的时间,而不是去询问我们的代码审查工具。Anthropic 还大量采用了对抗性审查,虽然这种方式在许多方面极为有效,但也可能目光短浅,因此经验丰富的工程师需要引导 Claude 避免为了应对假设性的担忧而对系统的某些部分进行过度设计。例如,有一次审查对信任另一个内部服务过度防御,导致 Claude 添加了大量冗余的校验和复杂度。我们的团队成员仍然会审查系统的每一项变更请求,但他们现在审视的是变更的意图,而不是逐行阅读代码。

我们能够大幅改进优化与质量。凭借 Claude 编写海量代码的速度远超我们自身,Scion 成为了我们合作过的测试与防护最完备的代码库之一。即使在紧迫的时间表下,我们也得以针对遇到过的每一个非平凡的基础设施与源代码控制交互构建了密封模拟,还加入了模糊测试与精细的速率限制。Claude 还顺带发现了重要的优化点——例如一项针对祖先查询的算法优化,它漏掉了我们的某个索引——这类工作原本需要专家耗费数日乃至数周的分析与调优……而且第二天它又做到了同样的事。

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

我们为新型的单仓库安全奠定了基础。在庞大的单仓库中,管理安全、保密与合规与管控其规模与体量同等重要。我们在设计之初就考虑到了这些因素。Scion 支持对仓库进行丰富的访问控制,并能够将权限应用到单个目录,从而在保护最敏感代码的同时保留单仓库的优势。此外,我们构建的代码审查工具能够对审批表达复杂的语义约束,使我们能够将仅涉及注释的简单变更与敏感的逻辑变更区分开来,让人类审查者能够聚焦于最重要的变更。

运营卓越可以达到新的高度。我们的团队大量使用 Claude Tag,这帮助我们监控系统的每个组件。例如,我们创建了 Slack 频道,让 Claude 专注于单个组件的健康状况。我们称这些 Claude 为「超级粉丝」。我们有一个 scion-daemon 超级粉丝、一个 scion-cache 超级粉丝、一个 Antique 合并队列超级粉丝,等等。它们可以访问我们的遥测数据和日志,行事就像一位经验丰富的工程师,除了刷新图表和查找任何异常之外没有其他事可做。我们也为所有系统设置了传统的告警,但我们的超级粉丝能够发现细微的、新颖的问题,相比之下这些告警显得很单一。

我们正在彻底重新审视我们的先验假设。这个项目促使我们认真审视其他为人类规模设计的工作流。例如,一个合适的合并队列在智能体规模下并没有太大意义。当数百甚至数千个变更在测试运行时合并时,CI 究竟意味着什么?我们一直无法有效地复制行业中合并队列全局仓库锁定的标准做法,因此我们转而投资于合并后和部署前的自动化测试。这确实意味着并发的变更可能会产生冲突,但 Claude 通常能比人类更快地修复这些冲突。

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

我们将 Scion 视为应对日益强大的 AI 智能体所引发的规模化问题的答案之一。我们分享这些细节是希望作为团队如何应对类似挑战的示例,尽管我们并不期望我们的解决方案或考虑因素适合每一种情况。

Scion 绝不是适合一般用途的 git 替代品。我们在这里概述的概念只对真正庞大的 monorepo 才有意义,无论是拥有数万名工程师,还是有大量智能体在大量编写代码。此外,Anthropic 的独特之处在于我们为内部使用而花费 token 的能力远远超过任何其他企业在现实情况下愿意花费的额度。我们这样做既是为了满足我们自己的软件开发需求,也是为了测试 Claude 的能力。

实际上,很难以对其他组织广泛有用的形式提供 Scion。它与 git 存在显著差异,意味着公司需要迁移开发流程中的每一个环节,并且它需要自己的代码审查、代码搜索和 CI。我们这些组件的内部版本都是高度定制的,因此其他组织很难采用,并且我们担心,尝试构建一个试图取悦所有人的公共版本实际上并不会特别有用。

然而,我们预计这类问题在未来六到十二个月内会变得更加普遍,而让每一家使用 git 的公司独自面对类似的挑战将是对工程资源的低效使用。因此,我们认为我们能做的最有影响力 的事情就是分享我们的思考,以便其他开发者能够将 Scion 中一些最成功的部分应用到 git 中,而不偏离整体模型。git 服务器不必以与 git 客户端相同的方式工作;存在能够提供显著可扩展性改进的新型服务器架构。Git 平台可以像 Scion 那样提供全面的服务器端 RPC,甚至可能以标准化的方式跨代码托管平台实现,让 git 客户端能够学会与之通信。协商协议也不必与今天完全一样,git 中各种稀疏和浅克隆特性可以很好地组合在一起。

最后,我们想要承认 git 社区花了近二十年的时间为数百万开发者扩展这个平台。我们的贡献要窄得多:分享我们对智能体式软件开发如何与这些系统交互的初步看法,以便我们能够与 GitHub、GitLab 和开源社区合作,支持开发者不断变化的需求。随着软件开发生命周期与 AI 的指数级发展相交,我们所有人都在同一条船上。

致谢

由 Nick G. 和源代码控制团队撰写:S.G.、R.K.、Dan C.、Garrett H.、Ben L.、Colin M.、Joe P。我们感谢 Vilas J. 和 Sergiu D. 在这段雄心勃勃的旅程中对源代码控制团队的支持,以及 Amir O. 对 Antique 的早期贡献。

脚注

  1. 相对于合并的第一个父提交存储变更文件是一种风格选择;在实践中,我们发现人们只关心单一代码库中基于主干开发模式下主线上提交的历史或 blame。

来源:Hacker News · anthropic.com