The bridge to agent-scale Git infrastructure
TL;DR · AI 摘要
Title: The bridge to agent-scale Git infrastructure URL Source: Published Time: 2026-10-21T09:00:00-07:00 Markdown Conte...
核心要点
- 主题聚焦:The bridge to agent-scale Git infrastructure
- 来源:The GitHub Blog,建议结合原文判断细节。
- AI 分析暂不可用,本条为保底评分与摘要。
过去一年间,随着 Git 流量呈指数级增长,GitHub 上 99.99999% 的代码仓库同时存在于两个位置:我们的自有数据中心和 Azure。其余仓库则是平台上最繁忙的仓库,它们唯一的瓶颈就是 25 毫秒的往返延迟。
在之前的博客文章中,我们阐述了下一代存储架构的两大原则:
- 最小化协调开销
- 将存储与计算解耦
在本文中,我们将探讨新旧架构之间的过渡方案。我们将说明通过改变多数派写入序列化机制如何将最繁忙的仓库迁移至 Azure,同时解释这一改进如何为无需持久状态的副本打开大门。
我们的演进路径
迁移的时机和方式由代理化转型驱动:快速增长的流量、数据中心及周边区域电力容量的硬性上限,以及物理供应链的限制。在迁移初期,即使考虑所有可能的扩容方案,剩余容量仍仅能保证安全迁移,而无法从容推进。
尽管我们已为云基础设施上的 Git 扩展原型化了新的架构方案,但同时变更存储架构和托管平台在增长压力下风险过高。鉴于我们的规模和服务对社区的重要性,必须谨慎安排工作顺序:优先迁移数据,待数据到位后再优化架构。
Git 操作是零级服务:这是 GitHub 的核心功能,GitHub 几乎所有功能都依赖于仓库数据。这意味着数据、服务及所有依赖项必须优先迁移。2025 年 8 月,首批仓库副本已部署至 Azure 中央美国区域。如今,大多数仓库的五个副本中有两个位于 Azure,Azure 承载了约一半的 Git 流量。在此期间,Azure 吸收了我们的增长,而我们的自有数据中心流量保持稳定。
迁移过程中,流量始终跟随数据流动。大多数仓库通过五个副本实现耐久性、可用性和扩展性的平衡。为最小化读取延迟,Spokes 采用优先路由策略,将流量导向与请求相同区域和可用区的副本。随着副本逐步迁移至 Azure,Azure 可服务的流量比例持续增长,直至最后一个副本迁移完成,旧区域彻底停止接收请求。每个仓库都经历这一过程:
无一仓库被遗漏
那么,剩下的 0.00001% 仓库又该如何处理?
在我们当前的架构中,如果一个仓库托管在多个区域,其写入吞吐量上限会更低。Spokes的共识机制只需要多数节点确认即可提交写入,但会等待所有健康的副本响应(最多超时时间)后才会继续,因此慢速副本会延迟所有写入操作,而不是落在后面。实际上,这意味着最慢的健康副本决定了每个写入的延迟,而由于每个写入在提交前会持有仓库级锁,它也设定了吞吐量上限。一旦副本位于目标Azure区域,它距离我们的数据中心大约1000英里,每次网络往返大约需要25ms,而一次写入需要多次往返。对于已经接近吞吐量上限的仓库,无法在每次串行写入时吸收这种延迟,每次推送都会进一步落后。
这就是我们第一条原则“最小化协调”的作用所在。具体到这个场景:不要对不需要串行处理的操作进行串行化。Git的强一致性要求适用于每个引用单独处理。一旦服务器接受对某个引用的更新,后续任何客户端都不应看到更旧的值,只能看到该值或更新后的值。这一规则适用于快进、强制推送或删除操作。但在大多数情况下,对不同引用的更新彼此之间没有依赖关系,可以独立应用。
因此,我们将共识机制的作用范围从整个仓库调整为单个引用。
对整个引用更新(包括网络往返)持有仓库级锁,会将共识机制变成一个单一的无序请求队列,每次只处理一个写入。该队列中的每个写入耗时取决于最慢的健康副本。随着写入速率增加,队列也会增长,每个写入等待时间都会比前一个更长。
通过按引用进行串行化处理,只有争夺同一共享引用的写入需要相互等待。实际上,大多数仓库写入操作涉及不同的引用——例如代理和开发人员向自己的分支推送——因此它们不再需要相互等待。每个写入仍然会经历跨区域延迟,但对不同引用的写入可以同时进行。这消除了仓库的单一瓶颈。我们可以通过在相似负载下对比两种方法的排队和超时情况,直观看到差异:
Video 6 这些改进使仓库的写入吞吐量上限大致与同时写入的引用数量成比例提升,并将最后剩余的仓库迁移至Azure。在内部使用真实流量进行的测试中,对不同引用的写入操作在更低延迟下持续实现了3-4倍的吞吐量提升。
独立扩展读取能力
大多数仓库的读取操作远多于写入,每写入一次会有100到1000次读取。目前,读取和写入操作访问相同的副本,因此会竞争相同资源,无法独立扩展。
将存储与计算解耦改变了这一现状。一旦仓库数据持久化存储在Azure Blob Storage中,并通过每次写入更新,我们就可以开始将读取流量与写入共识机制解耦,并独立扩展每个部分所需的计算资源。例如,读取操作可以显著受益于比当前提供更多内存缓存,但这些内存同样需要用于压缩操作。一旦分离,两者都可以获得独立的配置。
第一步是将对象存储作为仲裁成员,但这是一个特殊的成员,我们称之为虚拟副本。代表它的计算节点是我们的第一个完全临时节点。它作为对象存储的桥梁,流式传输传入的写操作,但不处理读取操作。由于它是仲裁成员,写操作在对象存储中持久化之前不会被确认。
这会为每个写操作增加一点延迟,但就像跨区域往返一样,按引用序列化保持了它不会成为吞吐瓶颈。虚拟写副本还将不相交引用上的事务合并为一组网络I/O操作发送到对象存储,因此繁忙的仓库只需为许多并发写操作支付一次往返费用,而不是每个写操作都支付一次。目前,虚拟写副本还处理压缩,读副本继承压缩后的状态。
有了虚拟写副本后,我们可以按需为仓库扩展尽可能多的虚拟读副本。每个读副本从Azure Blob Storage预热其缓存,并在不加入仲裁的情况下开始处理读取流量,以对象存储作为真实来源,并在后台将其本地状态与之同步。
这一步显著提升了读取扩展能力。在内部测试中,我们的分布式负载测试工具经常发现当前架构的瓶颈。在虚拟读副本上,工具首先达到了自身的限制。它也带来了一些后续步骤解决的挑战:
- 虚拟读副本最初在后台与对象存储同步,这使它们适合某些读取工作负载,但并非所有工作负载。要处理所有类型的读取,需要将同步操作内联进行,仅获取自副本上次同步以来更改的引用状态和数据。
- 尽管我们通过转向按引用模型逐步提高了写入吞吐量,但仲裁仍然是进一步提升的限制因素。
我们如何进一步提高写入吞吐量?
接下来的内容
今天的GitHub是基于Spokes仲裁构建的,多年来Spokes已经远远超越了仲裁协调器的角色。它还是一个智能路由和负载均衡层,一个内部客户端每天处理超过1000亿次操作时使用的统一API,以及一组我们称为“sweepers”的后台系统,负责处理仓库在存储节点之间的放置、修复和重新平衡。
Spokes服务下的绝大多数功能同样适用于新架构,也适用于现有架构。但我们认识到,使GitHub达到10亿个仓库的提交协议无法将我们带向下一个10亿及更远的地方。
我们还没有完成。在完成这些步骤后,下一个限制因素是写入吞吐量。每次写入仍然需要等待副本,而每个副本都保存着仓库的完整拷贝,但实际上大部分写入操作并不需要严格的顺序。Git 对象是不可变的,且通过其内容进行命名,因此可以无需共识即可存储。剩下的部分很小,而难点在于如何尽可能快速地对这部分进行排序,同时不将瓶颈转移到其他地方。在白板上设计这样的系统是一回事,而在数以亿计的仓库下实现则是另一回事。要达到这一目标,我们还被迫重新思考了一些次级影响,例如持久化引用状态的存储位置,以及压缩和垃圾回收的扩展性问题。
代理流量并未减缓,而每个步骤的实施都会为仓库带来更多的容量提升,而无需等待最终状态的完成。
下周即将推出的内容: 在 GitHub Universe 以及本系列的最后一篇文章中,我们将带您深入了解这一最终状态,它对写入吞吐量的影响,我们为实现这一目标必须解决的次级问题,以及为什么在系统某处仍然存在一个(小型)共识组。
感谢 Brian Celenza 对本文的贡献。
作者
高级软件工程师
资深软件工程师
在 GitHub 上探索更多内容
文档
一站式掌握 GitHub 所有内容的权威资源。
GitHub
在 GitHub 上构建未来,这里是任何人都能从任何地方构建任何事物的平台。
客户案例
了解使用 GitHub 构建产品的公司和工程团队。
GitHub Universe 2026
10 月 28-29 日,加入我们在旧金山或在线举办的 GitHub Universe,这是我们的旗舰开发者大会,汇聚了人、代理和全球代码。