跳到正文
RadarAI· GitHub Blog·· 3 小时前AI 评分60

GitHub 详解新 Git 存储架构 Rubicon,内部测试支持单仓库每秒 7200 次 push

GitHub 深度解析支撑智能体开发的新 Git 存储架构 Rubicon

AI 导读

GitHub 工程团队详解在 GitHub Universe 上宣布的新一代 Git 存储架构 Project Rubicon,内部测试可支持单仓库 5000 个并发智能体、每秒 7200 次 push,同时保持读取低延迟。

正文 · AI 翻译

GitHub Blog· Karthik Raman·2026-11-06 04:23· 57 分钟前AI 评分54

深入解析 GitHub 全新的 Git 存储架构

AI 导读

GitHub 工程团队详解在 GitHub Universe 宣布的新一代 Git 存储架构,内部测试支持单仓库每秒 7,200 次 push 和 5,000 个并发 agent。

正文 · 原文

上周在 GitHub Universe 上,我们发布了全新的 Git 存储架构,每秒可处理数千次写入,读取能力可按需扩展。通过内部测试,我们已证明能够支持 5,000 个并发 agent 在同一个仓库上工作、每秒向一个仓库发送 7,200 次 push,同时以始终如一的低延迟处理读取流量。这一新架构是 GitHub 为数亿开发者支持 agentic 开发的基础,我们一直在 GitHub 运行的同时在其底层构建它。

过去一年里,agent 改变了 GitHub 上繁忙仓库的面貌。在本系列的第一篇文章中,我们阐述了下一代 Git 存储架构背后的两项原则:最小化协调,以及将存储与计算解耦。在上一篇文章中,我们介绍了过渡环节:将 99.99999% 的仓库迁移到 Azure、将写入法定人数限定到单个引用,以及搭建由 Azure Blob Storage 支持的虚拟副本。

我们还留下了一个观察结论:一次写入中需要排序的部分非常少。Git 对象是不可变的,并以其内容命名,这意味着两个写入者可以同时存储它们而无需就任何事情达成一致。一次 push 中需要达成一致的部分是引用更新,而引用更新相比之下非常小。虚拟副本是围绕这一点进行构建的第一步,也是我们将仓库接入新架构的手段。

架构的其余部分建立在这一观察之上。只有引用更新需要达成一致。这使得 Git 基础设施中过去紧密耦合的部分(存储数据、处理请求和维护仓库)能够独立扩展。对开发者而言,Git 依然是 Git:他们以同样的方式 push、fetch 和创建分支,同样的保护机制依然在位。下面,我们将梳理设计背后的需求,跟踪一个请求在系统中的流转过程,并深入剖析其中的两个组件。

为 agent 规模而构建

我们从两个维度来思考 GitHub 平台的规模:

  1. 横向规模是指有多少开发者和 agent 能够在众多仓库上工作。GitHub 目前在这一维度上的许多方法已经过多年的实战检验,并被延续到新架构中:跨基础设施的租户分区、租户流量的隔离和/或公平性保障,以及负载感知路由,等等。
  2. 纵向规模是指有多少开发者和 agent 能够在一个仓库上工作。随着 agent 编写更多代码,我们预计单个仓库会变得更大、更繁忙,因此我们的大部分需求都来自纵向维度。

如今,GitHub.com 上最繁忙的仓库每秒最多接收几十次写入,通常以短时突发形式出现。随着越来越多此类工作转移到并行、全天候运行的智能体上,我们预计这些突发将在未来一年内转变为持续速率。每次推送都会带来更多读取:CI 运行、部署系统拉取新提交,其他智能体也会获取该提交以获取上下文。这种读取扇出对整体流量产生巨大的放大效应,且随写入量扩展。

我们已经在曲线顶端的仓库上看到这一趋势。下图展示了一个 GitHub 仓库从 3 月到 5 月的每日请求量,期间其流量增长了约 4 倍。此后其增长一直保持指数轨迹。

Line chart of daily requests to a single repository from March through May 2026. The March average is 44M requests per day and the May average is 181M requests per day, about a 4x increase.

我们必须为一个智能体驱动的开发持续以这种速度加速的世界做规划。我们没有按下一个增量来规划,而是设定了至少两个数量级以上的扩展目标。对于单个仓库,这意味着新架构需要:

  • 每秒接受数千次写入。
  • Support multiple writer nodes. There are two reasons for this:
    • 经验告诉我们,索引传入的 pack 和检查对象连通性是写入中 CPU 开销最大的部分,且该开销随写入大小扩展,因此单个节点会很快受限于计算能力。
    • 将这项工作分散到多个节点可提高容错性,并在节点故障时缩短恢复时间。
  • 最小化提交延迟。智能体在每次推送后等待,然后才进入下一步。
  • 按顺序记录每个引用的变更,并具备快速跨引用线性化的能力。落后的虚拟副本需要仅下载其服务传入请求所需的数据来追赶。
  • 在无变化时保持读取低成本,鉴于读写比,这是常见情况。
  • 让每个组件独立扩展。

除此之外,系统还必须能够提升整体可靠性并在全球范围内扩展。

引入“Project Rubicon”

2026 年 5 月,紧接初始 Azure 迁移之后,我们开始设计一个能满足这些需求的新架构。我们尝试了多种方案。在所有方案中,pack 数据都存放在 Azure Blob Storage 中,部分方案中我们还尝试将引用状态也放在那里。我们将在下文介绍发现的一些权衡,但最终确定了一个我们认为能在最大化吞吐量的同时,保留 GitHub 这类平台所需的正确性、持久性、可用性和延迟保证的设计。我们称之为“Rubicon”,寓意行业在智能体开发上跨过的不可回头之点。

让我们自上而下地了解 Rubicon。许多东西保持不变:来自 Git 客户端、API 和 Web UI 的请求到达 Spokes,Spokes 仍负责路由和负载均衡,并提供 GitHub 其余部分用于读写仓库数据的内部 API。Spokes 还保持其作为放置和均衡服务的角色,维护水平扩展所需的分区、隔离和租户公平性。相比之下,Spokes 下方的数据平面是全新的。

Architecture diagram. Git, API, and Web requests pass through Spokes to groups of virtual replicas per repository. Virtual replicas hold only cached state. Ref-CAS stores refs and the pack manifest, and Azure Blob Storage stores pack data. Compaction nodes compact and garbage collect packs in Blob Storage and update the pack manifest in Ref-CAS.

Spokes 将每个请求路由到分配给该仓库的一小组虚拟副本之一。虚拟副本是一组一次性计算资源,可处理读取、写入或两者兼有。虚拟副本的本地磁盘是一个可随时重建的缓存。

Spokes 在可能的情况下会将仓库的流量路由到相同的虚拟副本,这能保持其缓存热度和低延迟,但正确性并不依赖于请求落在任何特定的虚拟副本上。当某个虚拟副本消失时,仓库的其他虚拟副本会继续提供服务,而替代者会在流量到达时填充其缓存。

当仓库变得繁忙时,我们会增加更多虚拟副本。另外,一组压缩节点在后台直接针对 Blob Storage 和 Ref-CAS 工作,合并每个仓库的包并移除不可达对象。这项工作从不在虚拟副本上运行,这意味着它们的 CPU 和内存几乎可以完全专用于处理请求。

持久状态存在于两个地方:

  1. Git 对象,以包和辅助读取加速数据的形式,存放在 Azure Blob Storage 中。
  2. 引用以及构成仓库的包列表(包清单)存放在一个独立的复制日志服务中,我们称之为 Ref-CAS(意为“引用比较并交换”)。

让我们从一次写入和一次读取的角度来梳理这个架构:

  • 写入:虚拟副本对传入的包建立索引并上传到 Blob Storage,然后向 Ref-CAS 提交一个事务,在清单中列出该包并更新引用。之后,压缩节点会将该包与其他包合并,以保持读取速度。
  • 读取:虚拟副本向 Ref-CAS 检查其引用版本,从 Blob Storage 拉取所需的包到其缓存中,并从那里提供读取服务。

在新架构中,我们采取了许多不同的方法,从虚拟副本上的软件栈到如何针对不同用例(例如,许多小仓库 vs. 一个大仓库)调整流量路由和工作负载调度。让我们深入探讨架构中的两个组件,并围绕这两个设计原则讲述它们的故事。

最小化协调:以高吞吐量变更引用状态

像 Azure Blob Storage 这样的对象存储服务在其擅长的领域表现出色,天然适合许多数据密集型应用,如源代码存储。它们持久且可复制,能容纳我们需要的任意数据量,每字节成本相对较低,并且只要流量分散在许多键上,就能提供极高的聚合吞吐量。因此,新 Git 存储架构中几乎所有持久化内容都存放在 Azure Blob Storage 中,包括:

  • 由推送、压缩和垃圾回收写入的包和索引。
  • 我们的维护流程从这些包构建的各种读取加速器(如多包索引、可达性位图和某些自定义格式)。
  • 让压缩和垃圾回收能够恢复或审计其工作的记录,以及压缩节点用于认领工作的租约。
  • 频繁重复获取的缓存响应。
  • 仓库引用状态的检查点,尽管不是用于服务请求的权威状态。
Diagram of one repository's data in Azure Blob Storage. Push, Compaction, Maintenance, GC, and Ref-CAS write into it, and fetches read from it by exact key. It holds packs and indexes, records and leases, read accelerators such as the multi-pack index and reachability bitmap, cached fetch responses, and reference checkpoints.

大部分数据一次写入一个不同的键,之后永不改变,这正是它非常适合 Blob Storage 的原因。

引用则恰恰相反。一个繁忙仓库的引用是一块相对较小的状态,许多写入会同时争相更新它。这种访问模式更难由 Blob Storage 来承载,我们最初的几个原型就说明了原因。对于这类工作负载,对象存储是一种直觉上合理的选择,而且在一定的规模范围内效果很好:条件写入天然提供了比较并交换(compare-and-swap),数据本身也已经持久化并复制。然而,我们陷入了一组艰难的选择,下面用我们尝试过的两种方案来说明。

我们的第一种方案是为仓库的所有引用建立一个预写日志,并用单个 blob 作为索引。每次更新都是针对该 blob 的一次条件写入。这种做法是正确的,而且读取开销很低:虚拟副本只需询问该 blob 是否发生变化,如果没有变化,就完成了。问题出在写入上。同一时间只能有一个针对该 blob 的条件写入处于进行中,因此即使我们把一次写入期间到达的所有推送批量合并到下一次写入中,该仓库的写入上限仍然是每次往返单个 blob 只能完成一次写入。在实践中,这给我们的写入吞吐量只有每秒几百次。

我们的第二种方案把引用拆分到多个分段 blob 上,每个引用通过哈希映射到其中一个。对不同分段中引用的写入不再相互等待,从而提高了上限。但现在一次读取必须检查每个分段才能知道是否有任何变化,而且其速度取决于最慢的那个分段。我们用写入延迟换取了读取延迟,而读取次数比写入次数高出几个数量级。我们可以通过并排展示多次写入和一次读取来直观地说明这一点:

两种方案都无法满足所有要求,所以我们构建了别的东西:一个专用于引用的复制日志服务,即 Ref-CAS 服务。这就是我们在上一篇文章结尾所说的、仍然存在于技术栈某处的那个(小型)共识组。它的工作原理如下。

Ref-CAS 中的每次引用更新都是一次由法定人数提交的比较并交换。一个事务只锁定它涉及的引用,检查每个引用是否仍具有写入方期望的值,并在日志的大多数副本记录该事务后使其可见。各个代理大多推送到自己的分支,因此大多数更新不会产生争用,并且会并行复制。当两次推送确实更新同一个引用时,只有一次会追加到日志中。

对 Ref-CAS 的每次提交都会获得一个日志序列号(LSN),而仓库的版本就是其最新一次提交的 LSN。读取首先针对该版本进行版本检查。虚拟副本会发送它已持有的引用状态的版本。如果该版本仍然是最新的,回复就完全不携带任何引用,因此未发生变化的仓库只需一次比较。如果虚拟副本落后,它会收到当前的引用和版本,以及指向 pack 数据和其他需要知道的信息的指针,例如上一次垃圾回收周期发生的时间。

与我们旧架构中的共识组不同,Ref-CAS 从不持有或校验对象数据。它只存储指向对象数据的指针。虚拟副本在提交引用事务之前,会先把推送的 pack 上传到 Blob Storage。引用只会指向已经持久化的对象。

这些方法使我们能够仅凭引用状态就实现每秒数万次操作的事务处理,并为复制日志上的额外标注提供了基线,这些标注有助于读取操作决定针对给定请求下载哪些内容。

但在单个仓库上支持每秒数千次写入会带来一个二阶问题:维持读写性能。为此,我们需要一种不同的压缩方法。

将存储与计算解耦:在每秒数千次写入下进行压缩

每次推送都会向 Blob 存储添加一个 pack。Git 可以从多个 pack 提供仓库服务,但每增加一个 pack 都会使查找略微更昂贵,因此 pack 数量不能无限制增长。在我们的旧架构中,压缩运行在同时处理流量的副本上,并且必须在每个副本上运行。在新架构中,它完全在后台的独立节点上运行,直接针对持久化存储,并使用 Ref-CAS 服务通知虚拟副本不断变化的状态。

每个压缩节点通过租约对仓库数据的一个子集声明所有权,这使我们能够水平扩展压缩。以下是一个压缩节点如何处理仓库 pack 的单个分片:

该节点首先在 Blob 存储中对分片获取租约,因此同一时间只有一个节点压缩一个分片。与此同时,虚拟副本继续上传 pack 并将它们列在 pack 清单中。节点读取清单以查找新的 pack,并将它们收集到一个开放段中,该段实际上是即将被压缩的数据的暂存区。当段达到基于 pack 数量、大小或年龄的既定限制时,它会封存该段,使其不再接受新的 pack,然后将其中的 pack 合并为更少、更大的 pack。在压缩进行期间,该分片的下一个段可以开始捕获其输入 pack。在发布任何内容之前,节点会检查新 pack 是否包含与输入完全相同的对象。然后它上传新 pack 并将它们添加到清单中。第二个事务从清单中移除输入,清理过程稍后删除它们。

为了横向扩展,此过程会同时对许多分片独立运行。每个 pack 的哈希映射到一个分片,因此增加分片和节点会增加压缩吞吐量。一个压缩后仍低于大小限制的 pack 会按其自身哈希回到某个分片并再次合并,因此 pack 会逐层增长,直到达到限制。最终结果是,在推送量攀升的同时,pack 数量实际上保持平稳:

压缩会合并 pack,但会保留每个对象。垃圾回收是移除不再被任何内容引用的对象的步骤,例如已删除分支和强制推送留下的对象。在我们的新架构中,垃圾回收的工作方式与压缩非常相似,使用专用工作节点来处理计算密集型部分。它还有一些用于安全删除数据的特殊行为,值得单独讨论。

使用这些方法,我们能够持续展示出随着写入量攀升而保持平稳的延迟曲线,并且我们还有其他计划中的优化,以使压缩和垃圾回收过程本身更加高效。

下一步

我们从一开始遵循的两项原则,让这一架构的每个部分都能独立扩展。我们通过增加虚拟副本来扩展读和写。Ref-CAS 对每个引用独立排序,而不是对整个仓库排序。我们通过增加压缩节点来扩展维护能力。我们的目标是让每个仓库——从开发者的个人项目到最繁忙的 monorepo——都运行在同一架构上,无需对繁忙的仓库做特殊处理。仓库不会一直保持小规模。一个个人项目可能一夜之间就接入大量 agent,而我们不应该需要迁移或特殊处理来跟上。统一架构还意味着我们做的每一项改进都能同时惠及每个仓库。

这一基础让我们能做更多事情。接下来我们聚焦三个方向:将架构扩展到超出我们今天能预测的需求、让 agent 在 GitHub 上的工作更高效,以及为 agentic 开发演进 Git 本身。

改进架构

我们计划进一步推进这一架构。以下每项改进都能让仓库处理更多活动,同时减少每个请求背后的工作量:

  • 只读取所需内容。从 Blob Storage 中获取单个对象,而不是包含它的整个 pack。
  • 按类型组织 pack。将 commit、tree 和 blob 分组到各自的 pack 中。这样许多请求触及的数据就更少。
  • 部分副本。无需本地磁盘上的所有 pack 即可处理请求。
  • 物化视图。以即用形式保存仓库数据,例如预生成的归档、默认分支的近实时浅拷贝,以及与每个 commit 一起存储的 agent 会话上下文。
  • 全局副本。将副本放置在任何区域,靠近使用它们的工作负载。读取与最新写入之间保持有界陈旧度。
  • 缩容到零。根据实际活跃的仓库来扩缩容集群。

GitHub 上高效的 agent 操作

扩展基础设施是支持 agent 的一方面。另一方面是让每个 agent 操作更便宜:更少的获取字节、更少的往返次数,以及更少的等待变更时间。以下每项快速跟进项都建立在本博客的存储工作之上:

  • 无需克隆即可工作。许多 agent 任务从不需要完整的工作树。它们只需要少量文件和足够的上下文来进行更改。它们可以直接从服务器读取这两者。
  • 原子提交 API。在一次操作中提交多文件更改。整个更改要么提交成功,要么在冲突时干净地被拒绝。
  • 事件流。agent 及其周围系统在拥有实时信息时工作效果最佳。通用事件流让它们可以订阅变更并在变更发生时做出反应。

为 agent 改进 Git

我们在 Git 本身也有工作要做。GitHub 长期以来一直是 Git 项目的贡献者,我们现在不会停止。以下是我们期待继续推进的一些工作示例:

  • 轻量克隆。部分克隆、浅克隆和稀疏检出各自都能让 agent 获取更少内容。每一种都有不足。它们也不总能很好地配合。我们希望改进每一部分并让它们相互适配。
  • 消除隐藏的扩展成本。Git 中仍有一些代码路径的变慢速度远快于仓库的增长速度。最近的版本修复了其中几个,但我们预计还会发现更多。
  • 更快的历史遍历。像 commit-graph 这样的元数据可以在不遍历每个提交的情况下回答 merge-base 和可达性问题。当 agent 的 API 调用转化为服务器上的 Git 操作时,这一点尤为重要。
  • 避免常见陷阱。agent 会犯和开发者一样的错误。Git 可以捕获更多常见错误,并在它们造成损害之前发出警告。

agent 已经改变了软件的构建方式。GitHub 和 Git 也在随之改变。我们很高兴能将这一新架构带过起跑线。它目前处于有限私有预览阶段,并将在明年初更广泛地推出。我们迫不及待想看到,当在一个仓库上工作的 agent 数量只受限于他们的想法时,团队会构建出什么。我们将与客户和 Git 社区一起,持续演进 Git 和 GitHub。

文章 Inside GitHub’s new Git storage architecture 首次出现在 The GitHub Blog。

来源:GitHub Blog · github.blog

来源:RadarAI · aihot.news