GitHub 如何为智能体规模的 Git 流量搭建过渡架构
GitHub 介绍其把最繁忙仓库迁入 Azure 的过渡架构,将写入仲裁的串行化范围从整个仓库缩小到单个引用,以绕开跨区域约 25ms 往返带来的吞吐瓶颈。内部真实流量测试中,对不同引用的写入吞吐量提升 3–4 倍且延迟更低。目前多数仓库的五个副本已有两个位于 Azure,Azure 承担约一半 Git 流量。
GitHub Blog· Eleanor White·2026-10-22 00:00· 1 小时前AI 评分50
通往智能体规模 Git 基础设施的桥梁
AI 导读
GitHub 正把最繁忙的仓库迁入 Azure,通过将写入仲裁的串行化范围从整个仓库缩小到单个引用,解决跨区域约 25ms 往返带来的吞吐瓶颈。内部真实流量测试中,对不同引用的写入吞吐量提升 3–4 倍且延迟更低。目前多数仓库的五个副本已有两个位于 Azure,Azure 承担约一半 Git 流量。
正文 · 原文
过去一年里,随着 Git 流量呈指数级增长,GitHub 上 99.99999% 的 Git 仓库开始同时存在于两个地方:我们自己的数据中心和 Azure。其余仓库属于平台上最繁忙的一批,而它们只被一件事拖住:一次 25ms 的往返。
在早先的一篇博客文章中,我们阐述了下一代存储架构背后的两条原则:
- 最小化协调
- 将存储与计算解耦
在本文中,我们将探讨旧设计与新设计之间的直接桥梁。我们会谈到,对仲裁写入串行化方式的改变如何让最繁忙的仓库进入 Azure,以及同一步骤如何为不保留自身持久状态的副本打开大门。
我们是如何走到这一步的
迁移的时机和形态由智能体转变所驱动:流量快速增长、我们的数据中心及周边地理区域的电力容量存在硬性上限,以及物理供应链约束。在迁移开始时,即使算上所有可能的扩展,剩余容量也只够我们安全迁移,但不足以慢慢进行。
虽然我们已经为在云基础设施上扩展 Git 原型化了新的架构方案,但在那样的增长下同时改变存储架构和托管平台,风险太大。考虑到我们的规模以及该服务对社区的重要性,我们需要谨慎安排工作顺序:先迁移数据,等数据到位后再演进架构。
Git 操作是 Tier 0 服务:它们是 GitHub 的核心功能,GitHub 中几乎所有内容都依赖仓库数据。这意味着数据、服务以及每一项依赖都必须先行迁移。仓库的第一个副本于 2025 年 8 月落地 Azure 美国中部区域。如今,大多数仓库的五个副本中有两个位于 Azure,Azure 承担了约一半的 Git 流量。在大部分时间里,Azure 吸收了我们的增长,而我们自己的数据中心保持稳定。
迁移期间,流量跟随数据。大多数仓库有五个副本,以平衡持久性、可用性和规模。为最小化读取延迟,Spokes 使用优先路由将流量导向与请求位于同一区域和可用区的副本。随着副本迁往 Azure,可从 Azure 提供服务的流量份额随每个副本的增加而增长,直到最后一个副本到达,旧区域完全停止接收请求。每个仓库都会经历这一过程:
没有仓库被落下
那么,剩下的 0.00001% 仓库呢?
在我们当前的架构中,托管在多个区域的仓库写入吞吐量上限较低。Spokes 的法定人数只需多数派即可提交写入,但它会等待每个健康副本响应(直到超时)后才继续,因此慢副本会拖慢每一次写入,而不是落后。在实践中,这意味着最慢的健康副本决定了每次写入的延迟,并且由于每次写入在提交前都持有仓库级锁,它也决定了吞吐量上限。一旦副本位于目标 Azure 区域,它距离我们的数据中心约 1,000 英里,使得每次网络往返约 25 毫秒,而一次写入需要多次往返。一个已经接近吞吐量上限的仓库无法在每次串行写入中承受这种开销;每次推送都会让它进一步落后。
这正是我们的第一原则——最小化协调——发挥作用的地方。具体到这种情况:不要串行化不需要串行化的东西。Git 的一致性要求分别适用于每个引用。一旦服务器接受了对某个引用的更新,客户端之后就不应看到更旧的值,只能看到该值或更新的值。无论更新是快进、强制推送还是删除,这一点都成立。但在大多数情况下,对不同引用的更新互不依赖,可以独立应用。
因此,我们正在将法定人数的范围从整个仓库改为单个引用。
在整个引用更新过程中(包括往返)持有仓库级锁,会将法定人数变成单一的无序请求队列,一次只有一个写入在途。该队列中的每次写入耗时与最慢的健康副本一样长。随着写入速率增长,队列也会增长,每次写入的等待时间都比前一次更长。
通过按引用串行化,只有争用同一引用的写入才必须相互等待。在实践中,对仓库的大多数写入涉及不同的引用——例如,代理和开发者推送到各自的分支——因此它们不再相互等待。每次写入仍然经历跨区域延迟,但对不同引用的写入可以同时进行。这消除了仓库上的单一瓶颈。我们可以通过在相似负载下并排比较两种方法,来直观地了解排队和超时是如何累积的:
这些更改将仓库的写入吞吐量上限大致提高到与同时写入的引用数量成正比,并将最后剩余的仓库迁移到 Azure。在具有真实流量的内部测试中,对不同引用的写入始终以更低的延迟实现了 3–4 倍的吞吐量。
独立扩展读取
大多数仓库严重偏向读取,每次写入对应大约 100 到 1,000 次读取。如今,读取和写入命中相同的副本,因此它们竞争相同的资源,无法独立扩展。
将存储与计算解耦改变了这一点。一旦仓库数据持久存储在 Azure Blob Storage 中并随每次传入写入而更新,我们就可以开始将读取流量与写入法定人数解耦,并独立扩展各自所需的计算资源。例如,读取将从比我们今天提供的多得多的内存缓存中受益,但这些内存也是压缩所需的。一旦它们分离,各自获得自己的配置。
第一步是让对象存储成为 quorum 的成员,但这是一个特殊的成员,我们称之为虚拟副本。代表它的计算节点是我们的第一个完全临时性节点。它充当通往对象存储的桥梁,流式处理传入的写入,并且不处理任何读取。由于它是 quorum 的成员,写入只有在对象存储中持久化之后才会被确认。
这会给每次写入增加一点延迟,但就像跨区域往返一样,按引用串行化可以防止它成为吞吐量瓶颈。虚拟写入副本还会将不相交引用上的事务合并为对对象存储的单组网络 I/O,因此一个繁忙的仓库在许多并发写入时只需支付一次往返,而不是每次写入都支付一次。目前,虚拟写入副本还负责压缩,读取副本会继承压缩后的状态。
有了虚拟写入副本之后,我们可以根据仓库的需求按需扩展任意数量的虚拟读取副本。每个读取副本从 Azure Blob Storage 预热其缓存,并在不加入 quorum 的情况下开始接收读取流量,将对象存储作为事实来源,并在后台根据它来协调本地状态。
这一步释放了显著的读取扩展能力。在内部测试中,我们的分布式负载测试工具经常发现当前架构的上限。面对虚拟读取副本时,该工具先达到了自身的极限。它也带来了一些挑战,这些挑战将在后续步骤中解决:
- 虚拟读取副本一开始会在后台与对象存储进行协调,这使它们适合某些读取工作负载,但并非所有工作负载。要处理所有类型的读取,就需要将这种协调移到在线路径中,只获取自副本上次同步以来发生变化的引用状态和数据。
- 虽然我们通过转向按引用模型逐步提高了写入吞吐量,但 quorum 仍然是进一步提高的限制因素。
我们如何进一步推动写入吞吐量?
接下来是什么
今天的 GitHub 建立在 Spokes quorum 之上,多年来 Spokes 已经远不止是一个 quorum 协调器。它还是一个智能路由和负载均衡层,一个内部客户端用来访问和处理仓库数据的统一 API,每天处理超过 1000 亿次操作,以及一组我们称为“sweepers”的后台系统,负责仓库放置、修复以及存储节点之间的再平衡。
Spokes 服务伞下绝大多数内容同样适用于新架构,就像适用于现有架构一样。但我们也认识到,将 GitHub 带到十亿仓库的提交协议,无法将它带到下一个十亿及更远。
我们还没有完成。有了这些步骤,下一个限制就是写入吞吐量。每次写入仍然要等待各自持有仓库完整副本的副本节点,但实际上写入中需要排序的部分非常少。Git 对象是不可变的,并且以其内容命名,因此可以在无人对其达成一致的情况下存储它们。剩下的部分很小,而难点在于尽可能快地对它进行排序,同时不把瓶颈转移到别处。在白板上为此做设计是一回事。在开发者与智能体全天候写入的十亿个仓库之下实现它则是另一回事。要达到那里,还迫使我们重新思考二阶效应,比如持久引用状态存放在哪里,以及压缩和垃圾回收如何扩展。
智能体流量并没有放缓,而随着这些步骤逐步落地,每一步都会为仓库带来更多余量,无需等待最终状态。
下周预告:在 GitHub Universe 以及本系列的最后一篇文章中,我们将带你浏览那个最终状态,它对写入吞吐量意味着什么,我们为达到那里不得不解决的二阶问题,以及为什么技术栈中某处仍然存在一个(小型的)共识组。
感谢 Brian Celenza 对本文的贡献。
文章 通往智能体规模 Git 基础设施的桥梁 首次出现在 The GitHub Blog。
来源:GitHub Blog · github.blog
来源:RadarAI · aihot.news