GitHub 重做 Git 存储:单仓库扛住 5000 个智能体,每秒 7200 次推送
原文信息:本文译自《Inside GitHub's new Git storage architecture》,作者 Karthik Raman、Brian Celenza,发布于 2026 年 11 月 5 日。原文链接:https://github.blog/engineering/architecture-optimization/inside-githubs-new-git-storage-architecture/ 。原文未注明许可信息。本文为翻译,译者在文末补充了译者注。
译者导语:编码智能体一拥而上,Git 仓库的写入压力从“偶尔几十次”变成了“持续几千次”。GitHub 的答案不是给旧架构打补丁,而是把存储层拆开重做:对象随便写,引用才需要排队。下面是官方工程博客的全文翻译。
上周在 GitHub Universe 大会上,我们宣布了新的 Git 存储架构:每秒能处理数千次写入,读取又能按需扩展。内部测试显示,单个仓库可以支撑 5000 个并发智能体、每秒 7200 次推送,同时读取流量保持稳定的低延迟。这套架构是 GitHub 支撑数亿开发者做智能体开发的基础,而且我们是一边让 GitHub 正常运行、一边在底下把它建起来的。
智能体改写了“忙仓库”的样子
过去一年,智能体改变了 GitHub 上一个繁忙仓库的样子。我们这个系列的第一篇文章讲了新一代 Git 存储架构的两条原则:尽量少做协调、存储与计算分离;上一篇文章讲了过渡方案:把 99.99999% 的仓库迁入 Azure、把写仲裁的范围缩小到单个引用、搭建起以 Azure Blob Storage 为底座的虚拟副本。
上一篇文章还留了一个观察:一次写入里,真正需要排序的部分其实很少。Git 对象不可变、按内容寻址,两个写者可以同时存对象,不用先商量好。只有引用更新需要达成一致,而引用更新本身很小。虚拟副本是顺着这个思路迈出的第一步,也是把仓库接进新架构的办法。
其余架构都建立在这个观察之上。只需要引用更新达成一致,Git 基础设施里原来紧紧绑在一起的三件事——存数据、响应请求、维护仓库——就可以各自独立扩展。对开发者来说,Git 还是 Git:推送、拉取、建分支的方式不变,保护机制也不变。下面我们先讲设计背后的需求,再跟着一次请求走一遍系统,最后深挖其中两个组件。
两个维度的规模
我们从两个维度看 GitHub 平台的规模:
- 横向规模:多少开发者和智能体能同时在很多仓库上工作。这方面的不少做法经过多年实战检验,会原样带进新架构:租户分区、租户流量的隔离与公平调度、按负载路由,等等。
- 纵向规模:多少开发者和智能体能同时在一个仓库上工作。智能体写的代码越来越多,单个仓库会变得更大、更忙,所以大部分需求都来自纵向。
今天 GitHub.com 上最忙的仓库,一秒内最多也就几十次写入,而且通常是短暂的峰值。越来越多工作交给并行、全天候运行的智能体之后,我们预计这些峰值会在明年内变成持续的写入速率。每一次推送还会带起更多读取:CI 要跑、部署系统要拉新提交、其他智能体要取来做上下文。读会跟着写放大,整体流量随写入一起涨。
这个趋势已经在曲线顶部的仓库上出现了。下图是一个仓库 3 月到 5 月的日请求量,三个月里涨了约 4 倍——3 月日均 4400 万次请求,5 月日均 1.81 亿次,此后一直沿着指数曲线往上走。
(原文配图:单个仓库 2026 年 3 月至 5 月的日请求量折线图,见原文链接)
我们必须为智能体开发继续以这个速度加速的世界做规划。与其按下一个台阶来规划,我们把目标定为至少再扩大两个数量级。对单个仓库来说,新架构需要做到:
- 每秒接受数千次写入。
- 支持多个写节点,原因有二:经验告诉我们,给收到的包建索引、检查对象连通性是写入里最耗 CPU 的部分,而且开销随写入大小增长,单个节点很快就会算力见顶;把这部分工作分摊到多个节点,还能提高容错、缩短节点故障后的恢复时间。
- 尽量压低提交延迟——智能体每推一次都要等结果,才能继续下一步。
- 按引用顺序记录变更,并且能快速跨引用拉平成一条线:落后的虚拟副本追赶进度时,只下载响应当前请求真正需要的数据。
- 没变化时读要便宜——按读写比来看,没变化才是常见情况。
- 每个组件能独立扩展。
除此之外,系统整体还要更可靠、能扩展到全球。
“卢比孔”计划
2026 年 5 月,Azure 迁移刚完成不久,我们就开始设计能满足这些需求的方案,试了好几种。在所有方案里,包数据都放在 Azure Blob Storage 里,有些方案还试过把引用状态也放进去。下面会讲到我们遇到的取舍,最终选定的设计是在保证 GitHub 这个量级的平台所要求的正确性、持久性、可用性和延迟的前提下,吞吐量最大的一个。我们叫它“卢比孔”(Rubicon),取的是行业跨过智能体开发这道分水岭、一去不回的意思。
自上而下看 Rubicon,很多东西保持不变:来自 Git 客户端、API 和网页界面的请求先到 Spokes,它仍然负责路由和负载均衡,提供 GitHub 其余部分读写仓库数据用的内部 API;它还是放置与均衡服务,维护横向规模所需的分区、隔离和租户公平性。Spokes 之下的数据面则是全新的。
(原文配图:架构图,Git、API 与网页请求经 Spokes 到达每仓库一组虚拟副本;虚拟副本只持有缓存状态,Ref-CAS 存引用与包清单,Azure Blob Storage 存包数据,压实节点在后台合并包并更新清单,见原文链接)
Spokes 把每个请求路由到分给该仓库的一小组虚拟副本上。虚拟副本是一支统一的可丢弃计算节点池,既能读也能写,或者两者都做;它的本地磁盘只是缓存,随时可以重建。
Spokes 会尽量把一个仓库的流量路由到同一批虚拟副本上,这样缓存是热的、延迟低;但正确性并不依赖请求落在哪个虚拟副本上。一个虚拟副本挂了,其他副本继续服务,替补节点随着流量到来慢慢把缓存填满。
仓库一忙,就加虚拟副本。另外还有一支压实节点队伍在后台工作,直接对 Blob Storage 和 Ref-CAS 操作,合并每个仓库的包、清除不可达对象。这项工作从不在虚拟副本上跑,所以虚拟副本的 CPU 和内存几乎可以全部用来响应请求。
持久状态只放在两个地方:
- Git 对象,以包和辅助读加速数据的形式,放在 Azure Blob Storage 里。
- 引用,以及组成仓库的包的清单(包清单),放在一个独立的复制日志服务里,我们叫它 Ref-CAS(Reference Compare-and-Swap,引用比较并交换)。
跟着一次写和一次读走一遍:
- 写:虚拟副本给收到的包建索引、上传到 Blob Storage,然后向 Ref-CAS 提交一笔事务,把包列进清单、更新引用。之后压实节点再把这个包和其他包合并起来,保持读取速度。
- 读:虚拟副本先到 Ref-CAS 核对引用版本,再从 Blob Storage 把需要的包拉进缓存,从缓存里响应。
新架构里还有很多具体做法,从虚拟副本上的软件栈,到针对不同场景(比如很多小仓库 vs. 一个大仓库)调优的流量路由与工作负载调度。下面深挖两个组件,分别对应最初的两条设计原则。
只让引用更新走共识:Ref-CAS
Azure Blob Storage 这类对象存储服务非常擅长自己分内的事,是源码存储这类数据密集型应用的天然选择:持久、带副本、容量要多少有多少、单位字节成本低、只要流量分散在足够多的键上,聚合吞吐量极高。所以新 Git 存储架构里几乎所有持久数据都放在 Azure Blob Storage 里,包括:
- 推送、压实和垃圾回收写入的包和索引。
- 各种读加速器(多包索引、可达性位图和一些自定义格式),由维护流程从这些包里生成。
- 压实和垃圾回收用来续跑或审计工作的记录,以及压实节点认领工作用的租约。
- 高频重复请求的缓存响应。
- 仓库引用状态的检查点——但这不是用来响应请求的权威状态。
(原文配图:单个仓库在 Azure Blob Storage 里的数据示意图,推送、压实、维护、垃圾回收与 Ref-CAS 写入其中,读取按精确的键取用,见原文链接)
这些数据大多是一次写入一个独立的键、之后再也不变,正好适合 Blob Storage。
引用恰恰相反。一个繁忙仓库的引用是块很小的状态,却有很多写入同时抢着更新。这种访问模式用 Blob Storage 不好服务,最早的原型让我们明白了原因。对象存储直觉上是这类负载的合理选择,在一定规模内也确实能跑:条件写入自带比较并交换,数据本身已经持久且带副本。但我们试下来,遇到了一组难办的取舍,下面用试过的两种做法来说明。
第一种做法:给仓库的全部引用写一份预写日志,用单个 blob 做索引。每次更新都是针对这个 blob 的一次条件写入。它是对的,读也很便宜:虚拟副本问一句 blob 变了没有,没变就完事了。问题出在写上:同一时间只能有一笔针对这个 blob 的条件写入在途,即使把一笔写入期间到达的所有推送都打包进下一笔,仓库的写入上限也还是一次往返一个 blob。实际测下来,每秒只有几百次写入。
第二种做法:把引用分到几个分段 blob 里,每个引用按哈希落到其中一个分段。不同分段的引用写入不再互相等待,上限提高了。但读现在要检查每个分段才知道有没有变化,速度取决于最慢的那个分段。我们拿写延迟换了读延迟,而读的数量级比写大得多。
(原文配图/视频:多笔写入与一次读取并排对比,展示两种做法的取舍,见原文链接)
两种做法都没同时满足所有需求,于是我们自己做了一个专门管引用的复制日志服务,也就是 Ref-CAS。这就是上一篇文章结尾说的、栈里某处仍然存在的那个(很小的)共识组。它的工作原理是这样的:
Ref-CAS 里的每一次引用更新,都是一次由仲裁确认的比较并交换。事务只锁定它触及的引用,检查每个引用仍然是写者期望的值,一旦日志的大多数副本记录下来,更新就对外可见。智能体大多往自己的分支上推,所以多数更新互不冲突,可以并行复制;两笔推送同时更新同一个引用时,只有一笔能追加进日志。
(原文配图/视频:Ref-CAS 比较并交换示意,见原文链接)
Ref-CAS 的每一次提交都有一个日志序列号(LSN),仓库的版本就是最近一次提交的 LSN。读取先拿版本做核对:虚拟副本把自己持有的引用状态版本发过去。如果版本还是最新的,回复里不带任何引用,没变化的仓库只花一次比较的代价;如果虚拟副本落后了,它会拿回当前的引用和版本,以及指向包数据等信息的指针,比如上次垃圾回收是什么时候跑的。
和老架构里的共识组不同,Ref-CAS 从不持有或校验对象数据,只存指向数据的指针。虚拟副本先把推送的包上传到 Blob Storage,再提交引用事务——引用指向的对象一定已经持久化了。
这样一来,只交易引用状态一项,每秒就能做到几万次操作,还给复制日志打上了额外的标注,帮读取决定一次请求要下载什么。
但单仓库每秒数千次写入又带来一个二阶问题:怎么维持读写性能。为此,压实需要换一种做法。
压实搬到后台:推送越多,包数不涨
每一次推送都会往 Blob Storage 里添一个包。Git 可以用很多包来服务一个仓库,但每多一个包,查找就贵一点,包的数量不能无限增长。老架构里,压实跑在服务流量的副本上,而且每个副本都要跑一遍;新架构里,压实完全在后台的独立节点上跑,直接对持久存储操作,通过 Ref-CAS 把状态变化通知虚拟副本。
压实节点通过租约认领仓库数据的一个子集,这样压实可以横向扩展。一个压实节点处理单个分片的流程如下:
(原文配图/视频:单个压实节点处理一个分片的步骤示意,见原文链接)
节点先在 Blob Storage 里给这个分片上锁,保证同一时间只有一个节点在压实它。与此同时,虚拟副本继续上传包、往清单里登记。节点读清单找出新包,收进一个开放的分段——相当于一块等着被压实的暂存区。分段按包数量、大小或存续时间达到设定上限后就封口,不再接受新包,然后把里面的包合并成更少、更大的包。压实进行时,下一个分段已经可以开始收自己的输入包。发布之前,节点要核对新包包含的对象与输入完全一致,然后上传新包、登记进清单;第二笔事务把输入包从清单里移除,清理工作稍后删掉它们。
要扩容,这套流程可以同时在很多分片上独立跑。每个包按哈希映射到一个分片,分片和节点加得越多,压实吞吐量就越高。压实出来的包如果还没到大小上限,会按自己的哈希回到某个分片、再次合并,包就这样逐级长大,直到触及上限。最终效果是:推送不断往上爬,包的数量基本保持平稳。
(原文配图/视频:推送量上升时包数量保持平稳的示意,见原文链接)
压实只合并包,不丢对象。真正删东西的是垃圾回收——清理删除分支、强制推送后没人再引用的对象。新架构里的垃圾回收和压实很像,也用专职 worker 承担计算量大的部分;安全删数据的细节有些特殊做法,值得单独写一篇。
靠这些做法,我们已经能稳定地做到写入往上爬、延迟曲线保持平坦,后面还有计划好的优化,让压实和垃圾回收本身更快。
接下来三件事
最开始的两条原则,让架构的每个部分都能自己扩展:加虚拟副本就能扩展读写;Ref-CAS 按引用逐个排序,而不是给整个仓库排序;加压实节点就能扩展维护。我们的目标是让每个仓库——从一个人的业余小项目到最忙的单体仓库——都跑在同一套架构上,忙的仓库不需要特殊对待。仓库不会一直小,一个 side project 可能一夜之间接上一整支智能体队伍,不应该为此再做一次迁移或特殊处理。一套架构也意味着我们做的每一次改进,能同时惠及所有仓库。
有了这个底座,能做的事还有很多。我们聚焦在三个方向:把架构扩展到超出今天可预测的需求、让智能体在 GitHub 上的每次操作更高效、让 Git 本身为智能体开发进化。
把架构再往前推
每项改进都能让仓库承载更多活动,同时减少每次请求背后的工作量:
- 只读需要的那部分:从 Blob Storage 里取单个对象,而不是取装着它的整个包。
- 按类型组织包:提交、树、数据块各自成包,很多请求触及的数据就更少。
- 部分副本:不用把所有包都放在本地磁盘上也能响应请求。
- 物化视图:把仓库数据保持在随时可用的形态里,比如预先准备好的归档包、默认分支接近实时的浅拷贝、跟在每次提交旁边的智能体会话上下文。
- 全球副本:把副本放到任何地区,靠近实际产生负载的地方;读取与最新写入之间的滞后保持在有界范围内。
- 缩到零:fleet 随真正活跃的仓库伸缩。
让智能体的每次操作更便宜
扩展基础设施是支持智能体的一面,另一面是让每次智能体操作更便宜:少取字节、少跑往返、少等变更。以下每项快速跟进的工作都建立在这篇讲的存储工作之上:
- 不用克隆也能干活:很多智能体任务根本不需要完整的工作树,只要几个文件和足够的上下文做修改。这些可以直接从服务器上读。
- 原子提交 API:一次操作提交多文件改动,要么整个改动提交,要么在冲突时干净地拒绝。
- 事件流:智能体和周围的系统最需要实时信息。通用的事件流让它们订阅变更、随变随动。
让 Git 为智能体进化
Git 本身也有工作要做。GitHub 是 Git 项目长期的贡献者,不会就此停下。几件我们很想继续做的事:
- 更轻的克隆:部分克隆、浅克隆、稀疏检出都能让智能体少取数据,但每种都有短板,合起来用也不总是顺手。我们想把每一块都改进,并让它们更好地配合。
- 去掉隐藏的扩展成本:Git 里还有些代码路径,慢下来的速度比仓库长大的速度快得多。最近几个版本修掉了其中几处,我们预计还会找到更多。
- 更快的历史遍历:提交图这类元数据不用走遍每次提交,就能回答合并基点和可达性问题。这在智能体的 API 调用在服务器上变成 Git 操作时最重要。
- 避开常见坑:智能体犯的错和开发者一样。Git 可以多拦住几个常见错误,在造成损害之前先警告。
智能体改变了软件的建造方式,GitHub 和 Git 也在跟着变。我们很高兴把这套新架构带过起跑线:现在是有限的内测,明年初会扩大推广。等不及想看看,当一个仓库上能同时工作的智能体数量只受想法限制时,大家会做出什么东西来。我们会和客户、Git 社区一起,继续让 Git 和 GitHub 进化下去。
译者注
- 7200 次/秒、5000 并发是内测数字,不是生产实测:这两个数字来自 GitHub 自己的内部测试,“两个数量级”则是设计目标,不是已测得的结果。架构目前只在有限内测中,全面推广要到明年初。引用时别写成已经上线的能力。
- 最值得借鉴的是那道分界线:不是所有状态都值得上共识。Git 对象不可变、按内容寻址,并发写不用协调;只有引用更新才需要比较并交换。做分布式系统设计时,先分清哪些状态必须排序、哪些可以并行写,是这篇最通用的启发——GitHub 试过的两种“半吊子”方案(单 blob 日志、分段 blob)正好说明,省掉这一步会在读或写上付出代价。
- 和华为云的结合点:代码托管(CodeArts Repo)与流水线(CodeArts Pipeline)面临同样的放大效应:一次推送会触发 CI、部署、其他智能体的拉取,读的数量级远大于写。读要便宜、写要能扩展、维护不能占用服务节点,这三条对自建代码托管与流水线平台同样适用;“按引用分片、后台压实、引用状态单独走日志服务”的思路可以直接对照。
相关阅读(同方向旧文,纯文本链接): - AGENTS.md 规则怎么写编码智能体才照做:条件句遵守率最低,换个模型最佳写法就变:https://bbs.huaweicloud.com/blogs/7259cfd7c46f4df99a88172537d14b22 - 编码智能体写测试有何不同:11.4 万个 Java PR 显示,它更爱新增测试、少改旧测试:https://bbs.huaweicloud.com/blogs/2f9380ec6d274727b31fc2a63bf9d97f
原文:Inside GitHub's new Git storage architecture,Karthik Raman、Brian Celenza,GitHub Blog,2026-11-05。原文链接:https://github.blog/engineering/architecture-optimization/inside-githubs-new-git-storage-architecture/
- 点赞
- 收藏
- 关注作者
评论(0)