@cursor_ai: 我们正在让Git托管变得更可靠、高性能和可扩展。这篇文章追溯了20年的Git基础设施,并……
摘要
Cursor AI 介绍了如何通过重新设计其Git存储系统 Origin,使其像数据库一样运作,来提升Git托管的可靠性、性能和可扩展性。
查看缓存全文
缓存时间: 2026/08/18 22:35
我们正致力于让 Git 托管服务变得更可靠、更高效、更具扩展性。
本文回溯了 20 年来的 Git 基础设施演进历程,并阐述这段历史如何引导我们将 Git 存储服务 Origin 设计和运维得如同数据库一般。
https://t.co/UW7jHuItSX
规模化Git
来源:https://cursor.com/blog/git-at-any-scale 大规模托管 Git 仓库堪称一场噩梦。当 Linus Torvalds 设计地狱级信息管理器的首个版本时(没错,这确实是 Git 的原始宣传语,请查证 https://github.com/git/git/commit/e83c5163316),他心中有非常明确的用例:满足自身需求。他希望替代用于开发 Linux 内核的分布式版本控制系统 BitKeeper。当然,替代方案也必须是分布式的。内核是一个独特的软件项目;它具有极高的去中心化特性,其众多子系统由不同的维护者管理。分布式版本控制系统天然契合这种工作模式。
二十年后的今天,Git 已成为行业标准,但事实证明,其分布式特性在某种程度上弊大于利。普通开源软件项目并不采用去中心化工作流。普通企业当然更不会如此。他们利用分布式模型的诸多优势(例如离线工作、延迟推送等),但非常依赖中心化的托管服务。而事实证明,托管 Git 仓库是一项极其艰巨的任务。
https://cursor.com/blog/git-at-any-scale#whats-hard-about-gitGit的挑战何在?
大规模托管 Git 仓库的挑战源于 Git 自身的设计:作为分布式版本控制系统,意味着仓库的所有副本都是相同的。Git 服务器上的仓库与开发者笔记本电脑上的仓库并无本质区别。虽然乍看之下这似乎使 Git 仓库托管变得简单(只需在磁盘上的仓库副本前部署一个 HTTP 守护进程就能搭建 Git 服务器),但实际上存在诸多扩展性与可靠性挑战,使得情况截然相反。
在常规 Git 仓库中,你的代码和元数据(文件、提交、树)被压缩存储在打包文件中——这是一种简洁的二进制序列化格式,在本地机器上处理很方便,但在服务器端大规模管理时却并不理想。打包文件是 Git 存储和Git 网络传输的基础构建模块。当你向仓库推送或获取数据时,数据都是以打包文件的形式传输的。
这虽是 Git 的设计初衷,但认为必须如此未免武断。毕竟,你无法控制 Git 客户端(除非不惜惹恼用户并增添诸多操作摩擦),但在服务器内部,你可以为所欲为。没有任何规定强制你必须使用打包文件——Linus 不会来检查。唯一的限制是:所有 Git 操作通过网络传输时,确实需要发送和接收打包文件。
多年来,尝试大规模托管 Git 仓库的企业发现,这种基于打包文件的设计对可用性和扩展性构成了重大限制。打包文件是必须存在于文件系统上的大型二进制文件,Git 才能访问它们。仅在磁盘仓库前部署 HTTP 服务器这种简单方案存在明显瓶颈。理想情况下,你希望仓库存在于多块磁盘和多台服务器上(这能并行执行多项 Git 操作,并在服务器宕机时保持仓库可用)。但如何实现呢?
实现这一目标大致有三种方法,复杂度依次递增:分布式文件系统、分布式打包文件,或分布式 Git 本身。
https://cursor.com/blog/git-at-any-scale#git-without-packfiles无打包文件的Git
Git 是一个内容寻址的数据存储。Git 仓库中的所有对象(二进制大对象、树、提交等)均以其内容的 SHA-1 哈希值作为键。这直观上非常契合分布式键值存储(键是 SHA-1,值是实际对象),可为仓库存储扩展提供清晰方案。但这实际上行不通。
问题在于:Git 仓库的实际布局是有向无环图(简称 DAG)。你可以通过 SHA 值查找任何对象,但要执行仓库中最基础的操作,必须逐步遍历 DAG。
COMMIT DAGTREE /main → c8f3?commit·c8f3NETWORK↓ OLDER COMMITSobjects 0/54 · round‑trips 0key/value storeaa42a112c8f3e816f0214b70e8c487ab19b4d5c277b22dc83f7dc43040c22d6e21aa729a0db50f627cf191fe3e81b19092d080a5f31134e06c811f6e7a196e42e147b9086a70ce192ee45d83f7a9b02ec2a70c49b8e25ac074b1a93d47dd9b519d2a8c14d431dc31ef09e205
例如要列出仓库最近的变更,必须处理其提交记录。处理提交时,会获得指向其树根的指针。通过这个树,可获取指向每个文件和子树的指针。从原始提交中,还能获得指向其父提交(历史中前一个提交)的指针。关键在于,在这种遍历的每个步骤中,你都必须在获取前一个指针后,才能知道下一个指针的值。如果每次获取都需要分布式存储的一次往返传输,性能消耗会急剧增加。
这种在对象层分布式 Git 的方法已被多次尝试,往往在扩展性上失败。最有前景的尝试来自我以前的导师 Shawn Pearce 在 Google 版本控制系统团队的工作。他的方法是将对象存储在分布式哈希表中 (https://www.eclipse.org/lists/jgit-dev/msg01189.html)。这得益于 JGit——一个 Java 实现的定制 Git 库。如同所有优秀的 Java 库,JGit 提供了足够的接口和工厂来抽象普通 Git 仓库的所有细节,包括用分布式哈希表替换磁盘上的打包文件。虽然该系统运行正常,对常规 Git 操作的性能尚可,但 Git 协议的限制(无论服务器端如何存储数据,网络传输仍要求打包文件)导致 git clone 性能极差,最终该方案被弃用。
https://cursor.com/blog/git-at-any-scale#github-and-filesystemsGitHub与文件系统
Git 开始突破 Linux 内核圈几年后,一家初创公司在旧金山诞生。GitHub 成立于 2008 年,作为社交编程平台,其口号极具前瞻性:“Git 仓库托管:不再是麻烦事。”我并未开玩笑,请查证 (https://web.archive.org/web/20080514210148/http://github.com/)。早在 2008 年,业界已形成广泛共识:尽管(或者说正因为)Git 的分布式设计,实际需要中心化方式托管 Git 仓库才能使其易于使用,而这过程极其痛苦。GitHub 立志改变现状。
其平台最初(至今大部分仍是)是一个 Rails 单体应用。最初版本运行在单台(虽然性能强劲)服务器上,配备 Ruby 服务器和磁盘上的仓库副本。扩展 Rails 应用很简单:部署更多实例。但在此特殊情况下,由于涉及 Git,他们很快面临我们试图解决的这个反复出现的问题:如果 Rails 应用需要访问磁盘上的 Git 仓库,如何部署更多副本?
作为节俭的极客团队,GitHub 早期系统工程师尝试了最简单的解决方案来应对扩展挑战。他们的思路是,如果专注于分布式文件系统(而非打包文件或 Git 本身),就能保持 Rails 应用不变,将时间用于为不断增长的用户群开发更多功能,而非折腾 Git 的底层结构。非常务实,但行不通。
团队尝试了多种分布式文件系统方案:最明显的方法——使用 NFS 将所有仓库存储在中心化服务器上——很快被否定。默认的 Git 实现对文件系统语义(锁、撕裂、读取、同步等)做了大量假设,这些假设确保了在缓慢的开发者笔记本本地文件系统上的合理性能,但完全未考虑网络文件系统下的行为表现。该方案既慢又不稳定。
后续还尝试了(坦白说,回顾起来相当可怕)块级复制文件系统的技术。短暂部署过 GFS (https://en.wikipedia.org/wiki/GFS2)。更长久的部署基于 DRBD (https://en.wikipedia.org/wiki/DRBD)。它们都碰壁了。这些方案日常运维极其痛苦,且性能补偿不足。这一切都归结于磁盘上打包文件的设计。
我们已了解 Git 的图状数据结构导致往返传输代价高昂。不幸的是,类似原则也适用于底层磁盘数据。DAG 中对象的布局与它们在打包文件中的存储方式毫无关联。生成打包文件时的关键启发式原则是最小化体积:对象被随机放置、压缩存储,且关键的是很少完整存储。大多数对象以同一打包文件中另一个对象的增量形式存储。读取单个对象时,在经历图数据结构中的多个逻辑跳转后,还需经历磁盘格式中的物理跳转。
COMMIT DAGTREE /HEAD·mergecommit·c8f3root /tree·f021server.tsblob·f7a9pack.tsblob·a112README.mdblob·c430Cargo.tomlblob·ef09main~1commit·9d2aroot /tree·e8c4server.tsblob·d431pack.tsblob·b8e2README.mdblob·21aaCargo.tomlblob·92d0featurecommit·74b1root /tree·7a19server.tsblob·b02epack.tsblob·7cf1README.mdblob·5d83Cargo.tomlblob·e147merge basecommit·5ac0root /tree·2d6eserver.tsblob·8c14pack.tsblob·0f62README.mdblob·a93dCargo.tomlblob·3e81refactorcommit·3f7droot /tree·6c81server.tsblob·e205pack.tsblob·19b4README.mdblob·6a70Cargo.tomlblob·d5c2parsercommit·aa42root /tree·91feserver.tsblob·4b70pack.tsblob·f311README.mdblob·2dc8Cargo.tomlblob·80a5docscommit·ce19root /tree·0db5server.tsblob·729apack.tsblob·6e42README.mdblob·b190Cargo.tomlblob·47ddbootstrapcommit·87abroot /tree·40c2server.tsblob·1f6epack.tsblob·c2a7README.mdblob·9b51Cargo.tomlblob·34e0initialcommit·2ee4root /tree·b908server.tsblob·dc31pack.tsblob·77b2README.mdblob·e816Cargo.tomlblob·0c49↓ OLDER COMMITSpack‑7d9a.pack00005041434B00000002000000369667706B001087CB98F791BBB1A90DFB95AEA39C9096002008C2A7180F97ACF200DBBC9098D5BBD10030B5A94CB2E61004EC96B098E17C9BE5B700404A19E5C49FA44FE62B3225C446E744410050C5EB8990E84A607C5C5A3D9996324D5F0060633898968A2B4DF3066395013955B4960070A15A6898EA976BE046C3ACDE98A86F5E00803BE84A97E68450D47936985069F5B25F00902FBDE5D4C28ECFE63DB350B871E7F33A00A0BAF109D0E8F7FF556E74DAB7E6E2E71E00B058DD984BFA29344429F2E52F415382E600C0C71AD04782E703FECCE51940E862FA0600D004826A0596C94054F246984BD218C3AC00E04B2C9506ED4A02967A5071F17D9793A000F01B73670B98B79D58807A0B14E6DC5E5301002FEF9814BBB9A6E8617EE5D4B43D5AE601108478CF63F0E7F3D8BA541492E8E8FCF701205929E078E6629739693698F03127F80B0130295BE516B79BE8E68A9D1BCD23E744210140B1A5543CE88D7CD27941A14CE6C614EB0150F0E1982CE621266DD61CE5CECCF7DEE60160212997678DE7FD3C7B66EFF2E85F579E0170C2D51BAEE51554918799BF9B3F89732901808F7ED201FA87C2FD
这种跨越数 GB 数据的随机遍历(仓库的每个 Git 操作都必须执行)在网络文件系统(无论文件级还是块级复制)上难以高效运行。唯一能避免严重减速的方法是将整个文件缓存到本地。但当同一文件系统中存在数十万个仓库时,缓存方案不可行。
最终,GitHub 系统工程师屈服于现实,放弃了分布式文件系统。他们开始开发 RPC 系统,使仓库能存在于专用文件服务器上,并更新 Rails 应用以远程执行所有操作。这提供了良好的横向扩展能力,但并未解决可用性问题,也未提升最繁忙仓库的性能。毕竟,每个仓库仍仅存储在单台服务器上。
https://cursor.com/blog/git-at-any-scale#spokes-and-consistencySpokes与一致性
Spokes 最初由 GitHub 于 2013 年左右开发,现已成为行业标准。大多数 Git 托管服务都在架构中采用了 Spokes 的变体(Git 仓库的应用层复制)。Spokes 多年来表现良好的主要原因是其做出了三项基本选择,这些选择经过时间验证已被证明是最优的:
- 不分布 Git 本身,而是在打包文件层工作。
- 将所有数据以实际 Git 仓库形式存储在本地 NVMe 磁盘上。
- 复制 Git 数据,但保持所有副本始终完全同步。
由于我们刚讨论过的跨打包文件的随机读取模式,将原始 Git 仓库存储在 NVMe 驱动器上基本上是确保所有基础 Git 操作保持快速的必要条件。同时这也保持了克隆效率,因为无需将数据转换为 Git 客户端期望的格式。这还让你能专注于在 Git 之上构建产品,而非维护能操作特殊仓库的 Git 分叉版本。
关键的是,保持所有数据副本始终完全同步也极为重要。这一点你只有亲身经历才会明白,Git 客户端确实无法与最终一致性良好协作。如果本地 Git 客户端推送提交后立即无法通过获取读取到该提交,那就是坏消息。Git 会感到非常困惑。如果你在百个运行器上运行 CI 管道,其中三个运行器在克隆仓库后找不到待测试的提交,也是坏消息。这同样是非常糟糕的用户体验。
无论是在客户端还是后端,基于 Git 仓库的最终一致性视图操作存在诸多风险。因此,Spokes 付出了极高的复杂度成本来确保系统始终保持完全一致。让我们具体分析其含义。
Spokes 是一个基于共识的分布式系统。它通过在不同服务器上存储 Git 仓库的多个副本来工作。当你推送新数据时,协调器会将你的推送分发,使仓库的每个副本都接收一份拷贝。这种“分发“通过经典的三阶段提交(3PC)共识算法实现同步 (https://en.wikipedia.org/wiki/Three-phase_commit_protocol),仅当多数节点确认后,推送才会被接受。
QUORUM · 4/5tx #42RESTORE ALLCOORDINATORcollecting votesPARTICIPANT 1waitingPARTICIPANT 2waitingPARTICIPANT 3offlinePARTICIPANT 4waitingPARTICIPANT 5waiting1 · VOTING2 · PRE‑COMMIT3 · DO COMMIT
在深入探讨 Spokes 如何使用 3PC 之前,我们需要了解 Git 推送的工作原理。Git 推送包含两个组成部分:打包文件和引用事务。打包文件(前文已讨论)包含你推送到仓库的对象(包含你变更的二进制大对象、树和提交)。事务则通过更新一个或多个引用(例如你工作的分支)指向你刚推送的提交,从而将你的变更实际发布到仓库。
这种分离在此场景中非常方便,因为推送的提交最初并不可见(Git 术语中称为“可达“)
相似文章
@swyx: Cursor/Graphite 的 @TomasReimers 刚刚宣布了 Origin——@cursor_ai 期待已久的 Git 竞争对手,适用于AI Agent工作流可扩展…
Tomas Reimers 宣布了 Origin,这是 Cursor AI 期待已久的 Git 竞争对手,专为可扩展的AI Agent工作负载设计,支持 API 和 MCP 扩展,并内置合并冲突和故障解决功能。
@cursor_ai:Origin,Cursor 的代码托管平台,现已支持超过 20 种集成。将你的仓库连接到 CI、部署……
Origin 是 Cursor 的代码托管平台,现已支持超过 20 种集成,用户可以将仓库连接到用于 CI、部署、代码审查、安全、监控和项目管理的工具。
Cursor Origin(1分钟阅读)
Cursor Origin 是一个兼容 Git 的平台,专为 AI 代理规模的开发而设计,旨在通过优化并行 AI 代理工作流来与 GitHub 竞争。
@birdabo:Cursor Origin 初览 - 为智能体工作流量身定制 - 每小时 296,000 次克隆 - 每秒每仓库 22 次提交…
Cursor Origin 是一款专为智能体工作流打造的新版本控制与基础设施产品,具备高速克隆、自动合并冲突解决和一键 GitHub 导入等功能,现正逐步向 Cursor 用户推出。
GitHub服务降级影响Cursor Origin,其新Git平台
GitHub服务降级影响了Cursor的Origin平台及相关服务,引发了一起事件,该事件经调查后恢复解决。