深入了解 GitHub 全新的 Git 存储架构
1. 深入了解 GitHub 全新的 Git 存储架构
GitHub 全新的 Git 存储架构 Rubicon 使用 Azure Blob Storage 和 Ref-CAS 将存储与计算解耦,支持每秒数千次写入,助力大规模智能体驱动开发。
[!abstract] 速览
GitHub 全新的 Git 存储架构 Rubicon 通过迁移至 Azure Blob Storage,并引入用于管理引用的 Ref-CAS,应对智能体驱动开发的指数级增长。
该系统通过按引用进行串行化,每秒可支持数千次写入,同时允许并发写入不同分支。
虚拟副本独立处理读写,而压缩节点则在后台执行维护工作。
这一设计让各种规模的仓库——从个人项目到大型单体仓库——都能实现可扩展、低延迟的 Git 操作。
1.1. Inside GitHub’s new Git storage architecture
(深入了解 GitHub 全新的 Git 存储架构)
[!info] 主要内容
- 按引用进行串行化,提升写入吞吐量
将写入仲裁范围限定在单个引用,而非整个仓库,使多个智能体可以并发推送到不同分支,在保证正确性的同时实现每秒数千次写入。- 虚拟副本将存储与计算解耦
虚拟副本是可随时替换的计算节点,会缓存来自 Azure Blob Storage 的数据。读写操作可以独立扩展,且正确性不依赖任何单一节点。- Ref-CAS 使用共识协议管理引用状态
复制日志服务 Ref-CAS 使用比较并交换事务处理引用更新,实现高吞吐、有序写入,同时将对象数据保存在持久化的 Blob Storage 中。- 后台压缩保障读取性能
专用压缩节点在后台合并 pack,不影响虚拟副本,确保随着写入量增加,pack 数量保持平稳,读取延迟也保持一致。- 架构支持全球化、可扩展的智能体驱动开发
统一架构让任何仓库都能处理智能体驱动的工作负载,无需特殊处理;未来还计划改进部分副本和全球分布等能力。
Last week at GitHub Universe, we announced our new Git storage architecture, capable of handling thousands of writes per second with reads that scale on demand. Through internal testing, we’ve demonstrated the ability to support 5,000 concurrent agents working on a repository, sending 7,200 pushes per second to a repo, all while serving read traffic with consistently low latency. This new architecture is the foundation for GitHub to support agentic development for hundreds of millions of developers, and we’ve been building it underneath GitHub while it keeps running.
上周,在 GitHub Universe 大会上,我们宣布推出全新的 Git 存储架构,它每秒可处理数千次写入,读取能力则可按需扩展。
通过内部测试,我们证明了该架构能够支持 5,000 个智能体同时在一个仓库上工作,每秒向仓库推送 7,200 次,同时持续以低延迟处理读取流量。
这一新架构是 GitHub 支持数亿开发者进行智能体驱动的开发的基础,而我们一直在 GitHub 持续运行的同时,在其底层构建这一架构。
Over the past year, agents changed what a busy repository looks like on GitHub. In our first post in this series, we laid out the two principles behind our next-generation Git storage architecture: minimize coordination, and decouple storage from compute. In our last post, we covered the bridge: moving 99.99999% of repositories into Azure, scoping the write quorum to individual references, and standing up virtual replicas backed by Azure Blob Storage.
过去一年,智能体改变了 GitHub 上繁忙代码仓库的面貌。
在本系列的首篇文章中,我们阐述了下一代 Git 存储架构背后的两项原则:尽量减少协调,并将存储与计算解耦。
在上一篇文章中,我们介绍了实现这一目标的桥梁:将 99.99999% 的代码仓库迁移到 Azure,将写入仲裁范围限定为单个引用,并搭建由 Azure Blob Storage 支撑的虚拟副本。
We also left off with an observation: very little of a write needs to be ordered. Git objects are immutable and named by their contents, meaning two writers can store them at the same time without agreeing on anything. The part of a push that needs agreement is the reference update, and a reference update is tiny in comparison. Virtual replicas were the first step toward building around that, and the means by which we onboard repositories to the new architecture.
[!note] 核心观察
写入操作中需要排序的部分非常少。
Git 对象不可变,且以其内容命名,这意味着两个写入者可以同时存储对象,而无需达成任何共识。
推送过程中需要达成共识的是引用更新,与之相比,引用更新非常小。
虚拟副本是围绕这一点构建系统的第一步,也是我们将仓库接入新架构的方式。
The rest of the architecture builds on that observation. Only reference updates need agreement. That lets the parts of Git infrastructure that used to be tightly coupled (storing data, serving requests, and maintaining repositories) scale independently. For developers, Git stays Git: they push, fetch, and branch the same way, with the same protections in place. Below, we walk through the requirements behind the design, follow a request through the system, and dive deep into two of its components.
其余架构都建立在这一观察之上。
只有引用更新需要达成一致。
这使 Git 基础设施中原本紧密耦合的部分(存储数据、处理请求和维护仓库)能够独立扩展。
对开发者来说,Git 还是原来的 Git:他们仍以相同的方式推送、获取和创建分支,现有的保护措施也一如既往。
下面,我们将介绍这一设计背后的需求,跟踪一个请求在系统中的处理过程,并深入探讨其中的两个组件。
1.2. Building for agent scale
(为智能体规模构建)
We think about the scale of the GitHub platform along two axes:
我们从两个维度来思考 GitHub 平台的规模:
-
Horizontal scale is how many developers and agents can work across many repositories. Many of GitHub’s current approaches on this axis have been battle-tested over the years and are carried forward to the new architecture: partitioning of tenants across infrastructure, isolation and/or fairness enforcement of tenant traffic, and load-aware routing, to name a few.
横向扩展能力指的是有多少开发者和智能体能够在多个代码仓库中协同工作。GitHub 在这一方面采用的许多现有方案经过多年实践检验,并将沿用到新架构中,例如:将租户分配到不同基础设施分区、隔离租户流量和/或保障流量处理的公平性,以及基于负载进行路由等。
-
Vertical scale is how many developers and agents can work on one repository. As agents write more code, we expect individual repositories to get larger and busier, so most of our requirements came from the vertical axis.
纵向扩展能力指的是一个代码仓库可以容纳多少开发者和智能体同时协作。随着智能体编写越来越多的代码,我们预计单个代码仓库会变得更大、更繁忙,因此我们的需求大多来自纵向维度。
Today, the busiest repositories on GitHub.com receive at most dozens of writes in a single second, typically in short bursts. As more of that work shifts to agents running in parallel and around the clock, we expect those bursts to become a sustained rate within the next year. Each push leads to more reads: CI runs, deployment systems pull the new commit, and other agents fetch it for context. This read fan-out results in a large amplification effect on overall traffic that scales with writes.
如今,GitHub.com 上最繁忙的代码仓库在一秒内最多也只会收到几十次写入,而且通常是短暂的突发流量。
随着越来越多的工作转交给并行、全天候运行的智能体,我们预计在未来一年内,这种突发流量将转变为持续的请求速率。
每次推送都会带来更多读取:CI 会运行,部署系统会拉取新的提交,其他智能体也会获取它作为上下文。
这种读取扇出会大幅放大整体流量,且流量会随着写入量增长。
We’re already seeing this trend play out on repositories at the top of the curve. The chart below shows daily requests to one repository on GitHub from March to May, when its traffic grew about 4x. Its growth has continued on an exponential trajectory since then.
我们已经看到这一趋势在增长曲线顶端的代码仓库中显现出来。
下图展示了 3 月至 5 月期间 GitHub 上一个代码仓库的每日请求量,其流量增长了约 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.
| 指标 | 数值 | 口径来源 |
|---|---|---|
| 单仓并发智能体 | 5,000 个 | 内部测试 |
| 单仓每秒推送次数 | 7,200 次 | 内部测试 |
| 单仓每秒写入能力 | 数千 次 | 架构目标与实测 |
| 引用状态事务吞吐 | 每秒数万 次 | Ref-CAS |
| 单仓每日请求(3 月均值) | 44M | 图表数据 |
| 单仓每日请求(5 月均值) | 181M | 图表数据 |
| 单仓流量增长 | 约 4 倍 | 3 月至 5 月 |
We have to plan for a world where agent-driven development keeps accelerating at this speed. Instead of planning for the next increment, we set a target of at least two orders of magnitude more scale. For a single repository, that meant the new architecture needed to:
我们必须为这样一个世界做好规划:由智能体驱动的开发仍以这样的速度不断加速。
我们不再规划下一个增量,而是将目标定为至少提升两个数量级的规模。
对于单个代码仓库而言,这意味着新架构需要:
-
Accept thousands of writes per second.
每秒可接受数千次写入。
-
Support multiple writer nodes. There are two reasons for this:
- Experience has taught us that indexing incoming packs and checking object connectivity are the most CPU-expensive parts of a write, and that cost scales with the size of the write, so a single node will become compute-bound quickly.
- Spreading that work across many nodes improves fault tolerance and shortens recovery when a node fails.
支持多个写入节点。原因有两个:经验告诉我们,对传入的 pack 建立索引以及检查对象连通性,是写入过程中最消耗 CPU 的环节,而且开销会随写入规模增长,因此单个节点很快就会受限于计算能力。将这项工作分散到多个节点上,可以提高容错能力,并在节点发生故障时缩短恢复时间。
-
Minimize commit latency. An agent waits on each push before it moves to its next step.
尽量缩短提交延迟。每个智能体在每次推送后都要等待,才能进行下一步。
-
Record changes in order for each reference with the ability to quickly linearize across references. A virtual replica that falls behind needs to catch up by downloading only the data it needs to serve the incoming request.
按每个引用的顺序记录更改,并能够快速跨引用进行线性化。落后的虚拟副本要追赶进度,只需下载处理传入请求所需的数据。
-
Keep reads cheap when nothing has changed, which is the common case given the read-to-write ratio.
当没有任何变化时,尽量降低读取成本;考虑到读写比,这通常是最常见的情况。
-
Let each component scale independently.
让每个组件独立扩展。
On top of that, the system had to be able to improve overall reliability and scale globally.
除此之外,系统还必须能够提升整体可靠性并实现全球规模化。
1.3. Introducing “Project Rubicon”
(推出“鲁比孔计划”)
In May of 2026, hot on the heels of the initial Azure migration, we started working on a design that could meet those demands. We tried several approaches. In all of them, pack data lived in Azure Blob Storage, and in some we experimented with keeping reference state there too. We’ll cover some of the trade-offs we found below, but we settled on a design we felt maximized throughput while preserving the correctness, durability, availability, and latency guarantees a platform like GitHub requires. We called it “Rubicon,” a nod to the point of no return the industry crossed with agentic development.
2026 年 5 月,紧随最初的 Azure 迁移之后,我们开始着手设计一种能够满足这些需求的方案。
我们尝试了几种不同的方法。
在所有方案中,pack 数据都存储在 Azure Blob Storage 中;其中一些方案还尝试将引用状态也存储在那里。
下文会介绍我们发现的一些权衡取舍,但最终我们选择了一种设计,我们认为它在最大限度提升吞吐量的同时,也能保证 GitHub 这样的平台所要求的正确性、持久性、可用性和延迟。
我们将它称为“Rubicon”,借此指代整个行业因智能体开发而跨过的那个不可逆转的临界点。
Let’s walk through Rubicon from the top down. Many things stay the same: requests from Git clients, the API, and the web UI arrive at Spokes, which still handles routing and load balancing, and serves the internal API that the rest of GitHub uses to read and write repository data. Spokes also maintains its role as a placement and balancing service, maintaining the partitioning, isolation, and tenant fairness required for horizontal scale. The data plane underneath Spokes, in contrast, is new.
让我们从上到下梳理 Rubicon。
许多方面保持不变:来自 Git 客户端、API 和 Web UI 的请求都会到达 Spokes。Spokes 仍负责路由和负载均衡,并提供内部 API,供 GitHub 的其他部分读取和写入仓库数据。
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 routes each request to one of a small group of virtual replicas assigned to that repository. Virtual replicas are a single fleet of disposable compute that serves reads, writes, or both. A virtual replica’s local disk is a cache that can be rebuilt at any time.
Spokes 会将每个请求路由到分配给相应仓库的一小组虚拟副本之一。
虚拟副本构成一个统一的、可随时销毁的计算资源池,用于处理读取、写入或两者兼有的请求。
虚拟副本的本地磁盘是一个可随时重建的缓存。
Spokes routes a repository’s traffic to the same virtual replicas when it can, which keeps their caches warm and latency low, but nothing about correctness depends on a request landing on any particular virtual replica. When a virtual replica goes away, the repository’s other virtual replicas keep serving, and a replacement fills its cache as traffic arrives.
Spokes 会尽可能将仓库的流量路由到相同的虚拟副本,从而保持这些副本的缓存处于热状态并降低延迟;不过,正确性并不取决于请求是否落到某个特定的虚拟副本上。
当某个虚拟副本停止运行时,仓库的其他虚拟副本会继续提供服务,而替代副本则会随着流量到来逐步填充缓存。
When a repository gets busy, we add more virtual replicas. Separately, a fleet of compaction nodes works in the background, directly against Blob Storage and Ref-CAS, to merge each repository’s packs and remove unreachable objects. That work never runs on a virtual replica, meaning their CPU and memory can be almost entirely dedicated to serving requests.
当仓库负载变高时,我们会增加虚拟副本。
与此同时,一组压缩节点会在后台直接对 Blob Storage 和 Ref-CAS 执行操作,合并每个仓库的 pack,并删除不可达对象。
这些任务不会在虚拟副本上运行,因此它们的 CPU 和内存几乎可以全部用于处理请求。
Durable state lives in two places:
持久状态存储在两个地方:
-
Git objects, in the form of packs and auxiliary read acceleration data, live in Azure Blob Storage.
Git 对象以 pack 文件和辅助读取加速数据的形式存储在 Azure Blob Storage 中。
-
References and the list of packs that make up the repository (the pack manifest) live in a separate replicated log service we call Ref-CAS (for “Reference Compare-and-Swap”).
引用信息以及构成仓库的 pack 列表(即 pack 清单)存储在一个独立的复制日志服务中,我们称之为 Ref-CAS(“引用比较并交换”)。
| 持久状态 | 存放位置 |
|---|---|
| Git 对象 | Azure Blob Storage(pack 文件与辅助读取加速数据) |
| 引用信息与 pack 清单 | Ref-CAS(独立复制的日志服务) |
| 压缩与垃圾回收的记录、租约 | Azure Blob Storage |
| 频繁重复 fetch 的缓存响应 | Azure Blob Storage |
| 引用状态检查点 | Azure Blob Storage(非权威状态) |
Let’s walk the architecture from the perspective of a write and a read:
我们从写入和读取的角度来梳理一下架构:
-
Write: a virtual replica indexes the incoming pack and uploads it to Blob Storage, then commits one transaction to Ref-CAS that lists the pack in the manifest and updates the references. Later, a compaction node merges that pack with others to keep reads fast.
写入:虚拟副本会为传入的 pack 建立索引并将其上传到 Blob Storage,然后向 Ref-CAS 提交一个事务,在清单中列出该 pack 并更新引用。之后,压缩节点会将该 pack 与其他 pack 合并,以保持读取速度。
-
Read: a virtual replica checks its reference version with Ref-CAS, pulls the packs it needs from Blob Storage into its cache, and serves the read from there.
读取:虚拟副本使用 Ref-CAS 检查其引用版本,从 Blob Storage 将所需的 pack 拉取到缓存中,并从缓存中提供读取结果。
There are many distinct approaches we’ve taken within the new architecture, from the software stack on the virtual replicas to how traffic routing and workload scheduling are tuned for different use cases (e.g., many small repos vs. one large one). Let’s dive deep into two components of the architecture and tell their stories centered on the two design principles.
在新架构中,我们采用了许多不同的方法,包括虚拟副本上的软件栈,以及如何针对不同用例(例如大量小型代码仓库与单个大型代码仓库)调整流量路由和工作负载调度。
接下来,我们将深入探讨架构中的两个组件,并围绕这两项设计原则讲述它们的故事。
1.4. Minimizing coordination: mutating reference state at high throughput
(最小化协调:以高吞吐量修改引用状态)
Object storage services like Azure Blob Storage are incredibly good at what they do, and natural fits for many data-heavy applications like source code storage. They are durable and replicated, hold as much as we need them to, have a relatively low cost per byte, and deliver exceptionally high aggregate throughput so long as traffic is spread across many keys. As a result, nearly everything durable in the new Git storage architecture lives in Azure Blob Storage, including:
像 Azure Blob Storage 这样的对象存储服务非常擅长处理自身的工作,也天然适用于许多数据密集型应用,例如源代码存储。
它们持久可靠且具备副本,能够容纳我们所需的海量数据,每字节的成本相对较低,并且只要流量分散到许多键上,就能提供极高的总体吞吐量。
因此,新的 Git 存储架构中几乎所有需要持久保存的数据都存放在 Azure Blob Storage 中,包括:
-
Packs and indexes written by pushes, compaction, and garbage collection.
由推送、压缩和垃圾回收写入的 pack 文件和索引。
-
Various read accelerators (like multi-pack indexes, reachability bitmaps, and some custom formats) that our maintenance process builds from those packs.
由我们的维护流程基于这些 pack 构建的各种读取加速结构(如多包索引、可达性位图和一些自定义格式)。
-
Records that let compaction and garbage collection resume or audit their work, along with the leases that compaction nodes use to claim work.
使压缩和垃圾回收能够继续执行或审计其工作的记录,以及压缩节点用于领取任务的租约。
-
Cached responses for frequently repeated fetches.
对频繁重复的 fetch 请求进行缓存的响应。
-
Checkpoints for the reference state of the repository, though not the authoritative state used to serve requests.
用于仓库引用状态的检查点,但并非用于处理请求的权威状态。
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.
Most of that data is written once to a distinct key and never changes afterward, which is what makes it highly suitable for Blob Storage.
其中大部分数据只会写入独立的键一次,此后便不会再更改,因此非常适合存储在 Blob Storage 中。
References are the opposite. A busy repository’s references are a relatively small piece of state that many writes race to update at the same time. That access pattern is harder to serve from Blob Storage, and our first prototypes showed us why. Object storage for this kind of workload is an intuitive choice, and one that works well up to a certain level of scale: conditional writes give a compare-and-swap for free, and the data is already durable and replicated. However, we ran into a difficult set of choices, illustrated below by two approaches we tried.
引用则恰恰相反。
繁忙仓库的引用是相对较小的一部分状态,许多写入会同时争相更新它们。
这种访问模式更难通过 Blob Storage 来处理,我们最初的原型也让我们看到了原因。
对于这类工作负载,对象存储是一个直观的选择,而且在达到一定规模之前效果很好:条件写入可以免费提供比较并交换(compare-and-swap)功能,并且数据本身已经具备持久性和副本。
不过,我们遇到了一系列棘手的取舍,下面通过我们尝试过的两种方案来说明。
Our first approach was a write-ahead log for all of a repository’s references, indexed by a single blob. Every update is a conditional write against that blob. It’s correct, and reads are cheap: a virtual replica asks whether the blob has changed, and if it hasn’t, it’s done. Writes are where it falls short. Only one conditional write to a blob can be in flight at a time, so even when we batch every push that arrives during a write into the next one, the repository’s write ceiling is one write per round trip to a single blob. In practice, this netted us write throughput in the mid-hundreds per second.
我们的第一种方案是为仓库的所有引用建立预写日志,并用单个 blob 为其建立索引。
每次更新都是针对该 blob 的条件写入。
这个方案是正确的,而且读取成本很低:虚拟副本只需检查 blob 是否发生变化;如果没有,就完成了。
它的不足之处在于写入。
任一时刻,针对一个 blob 最多只能有一次条件写入在进行中。
因此,即使我们把一次写入期间到达的所有推送都合并到下一次写入中,仓库的写入上限仍是每次往返单个 blob 写入一次。
实际测下来,我们的写入吞吐量落在每秒数百次的中段。
Our second approach split the references across several segment blobs, with each reference hashing to one of them. Writes to references in different segments no longer wait on each other, which raises the ceiling. But a read now has to check every segment to know whether anything changed, and it’s only as fast as the slowest segment. We’d traded write latency for read latency, and reads outnumber writes by orders of magnitude. We can visualize this by showing multiple writes and a read side by side:
我们的第二种方案将引用分散到多个分段 blob 中,每个引用都会被哈希映射到其中一个分段。
对不同分段中引用的写入不再需要相互等待,从而提高了性能上限。
但现在,读取必须检查每个分段才能确定是否有任何变化,而且速度取决于最慢的分段。
我们以读取延迟为代价,换取了写入延迟的改善,而读取量比写入量高出好几个数量级。
我们可以并排展示多次写入和一次读取来直观说明这一点:
Neither option met every requirement, so we built something else: a replicated log service dedicated to references, or the Ref-CAS service. This is the (small) consensus group we said at the end of the last post was still somewhere in the stack. Here’s how it works.
这两个方案都无法满足所有要求,因此我们构建了另一种方案:一个专门用于管理引用的复制日志服务,也就是 Ref-CAS 服务。
这就是我们在上一篇文章末尾提到的、仍然位于整个技术栈某处的那个(小型)共识组。
下面介绍它的工作原理。
Each reference update in Ref-CAS is a compare-and-swap committed by a quorum. A transaction locks only the references it touches, checks that each one still has the value the writer expects, and becomes visible once a majority of the log’s replicas have recorded it. Agents mostly push to their own branches, so most updates don’t contend, and they replicate in parallel. When two pushes do update the same reference, only one will append to the log.
Ref-CAS 中的每次引用更新都通过多数派提交,并采用比较并交换(compare-and-swap)操作。
事务只会锁定它涉及的引用,检查每个引用的当前值是否仍与写入方预期的值一致,并在日志的多数副本记录该事务后对外可见。
各个智能体主要向自己的分支推送,因此大多数更新不会发生竞争,并且会并行复制。
如果两次推送都更新同一个引用,则只有一次会被追加到日志中。
Each commit to Ref-CAS gets a log sequence number (LSN), and the repository’s version is the LSN of its latest commit. Reads start with a version check against that version. A virtual replica sends the version of the reference state it already holds. If that version is still current, the reply carries no references at all, and an unchanged repository costs a single comparison. If the virtual replica is behind, it gets back the current references and version, along with pointers to pack data and anything else that needs to be known, such as when the last garbage collection cycle occurred.
Ref-CAS 中的每次提交都会获得一个日志序列号(LSN),而仓库的版本就是其最新一次提交的 LSN。
读取操作首先会针对当前版本做一次版本检查。
虚拟副本会发送它当前持有的引用状态版本。
如果该版本仍是最新版本,回复中就不会包含任何引用;对于未发生变化的仓库,只需进行一次比较。
如果虚拟副本落后了,它就会收到当前的引用和版本,以及指向 pack 数据的指针和其他需要了解的信息,例如上次垃圾回收周期发生的时间。
Unlike the consensus group in our legacy architecture, Ref-CAS never holds or validates object data. It only stores pointers to it. A virtual replica uploads a push’s pack to Blob Storage before it commits the reference transaction. A reference only ever points at objects that are already durable.
与我们旧版架构中的共识组不同,Ref-CAS 从不持有或验证对象数据,只存储指向对象数据的指针。
虚拟副本会先将一次推送的 pack 上传到 Blob Storage,然后再提交引用事务。
引用始终只会指向已经持久化的对象。
These approaches allow us to transact reference state alone in the tens of thousands of operations per second, and provide a baseline for additional annotations on the replication log that help reads decide what to download for a given request.
这些方法使我们仅针对引用状态就能以每秒数万次的速度执行事务,并为在复制日志中添加更多注释提供基准,帮助读取操作根据具体请求判断需要下载哪些内容。
But supporting thousands of writes per second on a single repository creates a second-order problem: maintaining the performance of reads and writes. For that, we needed a different approach to compaction.
但在单个仓库上支持每秒数千次写入,会带来一个次级问题:如何维持读写性能。
为此,我们需要采用不同的压缩策略。
1.5. Decoupling storage from compute: compaction at thousands of writes per second
(将存储与计算解耦:每秒数千次写入下的压缩操作)
Every push adds a pack to Blob Storage. Git can serve a repository from many packs, but each additional pack makes lookups slightly more expensive, so the pack count can’t grow without bound. In our legacy architecture, compaction runs on the same replicas that serve traffic, and it has to run on every replica. In the new architecture, it runs entirely in the background on separate nodes, directly against durable storage, using the Ref-CAS service to notify virtual replicas of the evolving state.
每次推送都会向 Blob Storage 添加一个 pack。
Git 可以通过多个 pack 提供仓库服务,但每增加一个 pack,查找成本都会略有上升,因此 pack 的数量不能无限增长。
在我们的旧架构中,压缩操作与流量服务由同一批副本执行,而且必须在每个副本上运行。
在新架构中,压缩操作完全在后台的独立节点上运行,直接针对持久化存储执行,并通过 Ref-CAS 服务通知虚拟副本状态的变化。
| 维度 | 旧架构 | 新架构(Rubicon) |
|---|---|---|
| 写入仲裁范围 | 整个仓库 | 单个引用 |
| 压缩执行位置 | 与流量服务共用同一批副本,且每个副本都要运行 | 后台独立压缩节点,直接针对持久化存储 |
| 共识组职责 | 持有并验证对象数据 | 只保存指向对象数据的指针 |
| 压缩对服务的影响 | 占用服务副本的 CPU 与内存 | 不占用虚拟副本资源 |
Compaction nodes each claim ownership over a subset of the repository’s data via a lease, which allows us to scale compaction horizontally. Here’s how one compaction node works through a single shard of a repository’s packs:
每个压缩节点都通过租约声明对仓库数据子集的所有权,这使我们能够横向扩展压缩能力。
下面介绍一个压缩节点如何处理仓库 pack 的单个分片:
节点首先在 Blob Storage 中获取该分片的租约,因此同一时间只有一个节点会压缩该分片。
与此同时,虚拟副本会继续上传 pack,并将其列入 pack 清单。
节点读取清单以查找新的 pack,并将它们收集到一个开放段中;该段实际上是即将压缩的数据的暂存区。
当该段达到基于 pack 数量、大小或存放时间设定的上限时,节点会封存该段,使其不再接收新的 pack,然后将其中的 pack 合并为数量更少、体积更大的 pack。
压缩过程中,该分片的下一个段可以开始收集输入 pack。
在发布任何内容之前,节点会检查新 pack 包含的对象是否与输入中的对象完全相同。
随后,节点上传新 pack 并将其添加到清单中。
第二个事务会从清单中移除输入 pack,之后再进行清理并删除它们。
To scale out, this process runs independently for many shards at once. Each pack’s hash maps to a shard, so adding shards and nodes adds compaction throughput. A compacted pack that’s still under the size limit goes back into a shard by its own hash and merges again, so packs grow in tiers until they reach the limit. The net result is an effectively flat pack count while pushes climb:
为了横向扩展,这个流程会同时在许多分片上独立运行。
每个 pack 的哈希值会将其映射到一个分片,因此增加分片和节点就能提升压缩吞吐量。
压缩后仍未达到大小限制的 pack 会根据自身哈希值重新分配到某个分片并再次合并,因此 pack 会逐层增大,直到达到限制。
最终,即使 push 数量不断增加,pack 的数量也基本保持稳定:
Compaction merges packs, but it keeps every object. Garbage collection is the step that removes objects nothing references anymore, like the ones left behind by deleted branches and force-pushes. In our new architecture, garbage collection works very similarly to compaction by using dedicated workers to handle the compute-heavy portion. It also has a few special behaviors for safely removing data, which are worthy of their own discussion.
压缩会合并 pack,但会保留所有对象。
垃圾回收则会移除不再被任何内容引用的对象,例如删除分支和强制推送后遗留的对象。
在我们的新架构中,垃圾回收的工作方式与压缩非常相似:由专用工作进程处理计算密集型部分。
它还有一些用于安全删除数据的特殊行为,值得单独讨论。
Using these approaches, we’ve been able to consistently demonstrate a flat latency curve as writes climb, and we have other planned optimizations to make the compaction and garbage collection processes themselves more performant.
通过这些方法,我们一直能够稳定地证明,随着写入量增加,延迟曲线仍保持平稳;此外,我们还计划进行其他优化,以提升压缩和垃圾回收流程本身的性能。
1.6. What comes next
(接下来是什么)
我们最初提出的两个原则,让这一架构的各个部分都能独立扩展。
通过添加虚拟副本,我们可以扩展读写能力。
Ref-CAS 会分别对每个引用进行排序,而不是对整个仓库排序。
通过添加压缩节点,我们可以扩展维护能力。
我们的目标是让每个仓库——从单个开发者的业余项目到最繁忙的单体仓库——都运行在同一架构上,无需对繁忙的仓库进行特殊处理。
仓库不会一直保持小规模。
一个业余项目一夜之间就可能迎来一大批智能体,为了跟上增长,它不应该需要迁移,也不应该需要我们提供特殊处理。
采用同一套架构,也意味着我们做出的每一项改进都能同时惠及所有仓库。
This foundation lets us do a lot more. We’re focused on three areas next: scaling the architecture beyond what we can predict we’ll need today, making agents more efficient at the work they do on GitHub, and evolving Git itself for agentic development.
这一基础让我们能够做更多事情。
接下来,我们将重点推进三个领域:扩展架构,使其超越我们目前预计的需求;提升智能体在 GitHub 上执行工作的效率;以及推动 Git 本身的发展,以适应智能体驱动的开发。
1.6.1. Improving the architecture
(改进架构)
We have plans to take this architecture further. Each of these improvements lets a repository handle more activity while reducing the work behind each request:
我们计划进一步扩展这一架构。
这些改进中的每一项都能让代码仓库处理更多活动,同时减少每个请求背后的工作量:
-
Read only what’s needed. Fetch a single object from Blob Storage instead of the whole pack that holds it.
只读取必要内容。从 Blob Storage 中获取单个对象,而不是下载包含该对象的整个 pack。
-
Organize packs by type. Group commits, trees, and blobs into their own packs. Many requests then touch less data.
按类型组织 pack 文件。将提交、树和 blob 分别归入各自的 pack 文件。这样,许多请求就只需访问较少的数据。
-
Partial replicas. Serve requests without every pack on local disk.
部分副本。即使本地磁盘上没有所有 pack,也能处理请求。
-
Materialized views. Keep repository data in ready-to-use forms, like prepared archives, a near-live shallow copy of the default branch, and agent session context stored alongside each commit.
物化视图。将仓库数据保持为随时可用的形式,例如预先准备好的归档、近实时的默认分支浅副本,以及与每次提交一同存储的智能体会话上下文。
-
Global replicas. Place replicas in any region, close to the workloads that use them. Reads stay within bounded staleness of the latest write.
全局副本。可在任意区域部署副本,使其靠近使用它们的工作负载。读取结果与最新写入保持在有界陈旧度范围内。
-
Scale to zero. Grow and shrink the fleet with the repositories that are actually active.
缩容至零。根据实际活跃的代码仓库扩展或缩减集群。
1.6.2. Efficient agent operations on GitHub
(在 GitHub 上高效运行智能体)
Scaling the infrastructure is one side of supporting agents. The other side is making each agent operation cheaper: fewer bytes to fetch, fewer round trips, and less waiting on changes. Each of these fast-follow items builds on the storage work in this post:
扩展基础设施只是支持智能体的一个方面。
另一方面是降低每次智能体操作的成本:减少获取的数据量、减少往返次数,并缩短等待变更的时间。
以下每项后续优化都基于本文介绍的存储改进:
-
Work without a clone. Many agent tasks never need a full working tree. They need a handful of files and enough context to make a change. They can read both straight from the server.
无需克隆仓库即可工作。许多智能体任务根本不需要完整的工作树,只需要少数几个文件和足够的上下文来进行修改。它们可以直接从服务器读取这些内容。
-
Atomic commit API. Land a multi-file change in one operation. The whole change commits, or it’s rejected cleanly on conflict.
原子提交 API。通过一次操作提交多文件更改。整个更改要么提交成功,要么在发生冲突时被干净地拒绝。
-
Event streams. Agents and the systems around them work best with real-time information. A generic event stream lets them subscribe to changes and react as they happen.
事件流。智能体及其周边系统在实时信息的支持下运行效果最佳。通用事件流让它们能够订阅变更,并在变更发生时做出响应。
1.6.3. Improving Git for agents
(为智能体改进 Git)
We also have work to do in Git itself. GitHub has been a longtime contributor to the Git project, and we’re not stopping now. A few examples of the work we’re excited to continue:
我们在 Git 本身也还有很多工作要做。
GitHub 长期以来一直为 Git 项目贡献力量,而且今后也会继续。
以下是我们期待继续推进的一些工作示例:
-
Lightweight clones. Partial clone, shallow clone, and sparse checkout each let an agent fetch less. Each one has gaps. They don’t always work well together, either. We want to improve each piece and make them fit.
轻量级克隆。部分克隆、浅克隆和稀疏检出都能让智能体少获取一些内容,但每种方式都有其不足,而且它们之间也并非总能很好地协同工作。我们希望改进每个环节,并让它们更好地配合。
-
Remove hidden scaling costs. Git still has code paths that slow down much faster than the repository grows. Recent releases fixed several of them, but we expect to find more.
消除隐藏的扩展成本。Git 中仍有一些代码路径,其速度下降远快于仓库的增长速度。近期版本修复了其中的几个问题,但我们预计还会发现更多。
-
Faster history walks. Metadata like the commit-graph can answer merge-base and reachability questions without walking every commit. That matters most when an agent’s API call turns into Git work on the server.
更快地遍历历史记录。提交图等元数据无需遍历每个提交,就能回答合并基点和可达性问题。当智能体的 API 调用需要在服务器上执行 Git 操作时,这一点尤为重要。
-
Avoid common gotchas. Agents make the same mistakes developers do. Git can catch more of the common ones and warn before they cause damage.
避免常见陷阱。智能体也会犯开发者常犯的错误。Git 可以发现更多这类常见问题,并在造成损害之前发出警告。
Agents have changed how software gets built. GitHub and Git are changing with it. We’re excited to take this new architecture across the starting line. It’s available in a limited private preview now, with a broader rollout early next year. We can’t wait to see what teams build when the number of agents working on a repository is limited by their ideas. We’ll keep evolving Git and GitHub alongside our customers and the Git community.
智能体改变了软件的构建方式,GitHub 和 Git 也随之改变。
我们很高兴能推动这一全新架构迈过起跑线。
目前,该架构已开启有限范围的私人预览,预计明年初将扩大推出范围。
我们迫不及待地想看看,当在一个代码仓库中协同工作的智能体数量不再受限、只受团队创意所限时,大家会构建出什么。
我们将与客户和 Git 社区携手,持续改进 Git 和 GitHub。