任何規模的 Git
大規模託管 Git 儲存庫簡直是一場惡夢。Linus Torvalds 設計 來自地獄的資訊管理工具 第一版時 (這其實就是 Git 的標語,可以查一下) ,心中有非常明確的使用情境:他自己的需求。他想取代當時用來開發 Linux Kernel 的分散式版本控制系統 BitKeeper。當然,替代方案也必須是分散式的。Kernel 是個不尋常的軟體專案;它極度去中心化,眾多維護者各自負責不同的子系統。分散式版本控制系統自然非常適合這種工作流程。
二十年後,Git 已成為業界標準,但事實上,它的分散式特性與其說是優勢,不如說是阻礙。一般的開源軟體專案並不採用去中心化的工作流程,一般公司更是如此。它們享有分散式模型的諸多優勢 (例如可離線工作、延後推送等) ,但同時也高度依賴集中式託管服務。而事實證明,託管 Git 儲存庫極其困難。
Git 有什麼難處?
大規模託管 Git 儲存庫的挑戰,源自 Git 本身的設計:作為_分散式_版本控制系統,儲存庫的所有副本都完全相同。Git 伺服器上的儲存庫,與開發人員筆記型電腦上的儲存庫並沒有任何本質差異。乍看之下,這似乎讓託管 Git 儲存庫變得很簡單 (只要在磁碟上的儲存庫前架設 HTTP 伺服器,就能運作成 Git 伺服器!) ,但實際上,可擴充性和可靠性方面存在許多棘手的挑戰,情況恰恰相反。
在一般的 Git 儲存庫中,您的程式碼和中繼資料 (檔案、提交、tree) 會被壓縮並儲存在_packfile_中。這是一種簡單的二進位序列化格式,方便在本機處理,卻不適合在伺服器上大規模管理。Packfile 是 Git 儲存和 Git 網路傳輸的基礎建構單元。當您向儲存庫 push 資料,或從中 fetch 資料時,資料都會以 packfile 的形式傳輸。
這是 Git 設計使然,但認為它不必如此也很合理。畢竟,您無法控制 Git 用戶端 (至少無法在惹惱使用者並增加大量摩擦的前提下做到) ,但在自己的伺服器內部,您可以做_任何_想做的事。沒有任何限制要求您使用 packfile——Linus 不會過來檢查。唯一的限制是,所有 Git 操作都必須透過網路接收和傳送_packfile_。
多年來,嘗試大規模託管 Git 儲存庫的公司發現,這種以_packfile_為基礎的設計,嚴重限制了可用性和可擴充性。Packfile 是大型二進位檔案,Git 必須透過檔案系統才能存取。只是在磁碟上的儲存庫前架設 HTTP 伺服器,這種簡單做法的擴充性上限很低。理想情況下,您會希望儲存庫存在於多個磁碟和多台機器上 (這可讓您並行執行多項 Git 操作,並在伺服器當機時維持儲存庫可用) 。但該怎麼做?
大致上有三種可行方法,複雜度依序遞增:分散檔案系統、分散 packfile,或分散 Git 本身。
不使用 packfile 的 Git
Git 是內容定址的資料儲存庫。Git 儲存庫中的所有物件 (blob、tree、提交等) 皆以其內容的 SHA-1 作為索引鍵。這在直覺上很適合對應到分散式鍵值儲存庫 (索引鍵是 SHA-1,值則是實際物件) ,也能以乾淨的方式擴充儲存庫的儲存容量。但實際上並不可行。
問題在於:Git 儲存庫的實際結構是一個有向無環圖 (簡稱 DAG) 。您可以透過 SHA 查詢任何物件,但即使是對儲存庫執行最簡單的操作,也必須逐步走訪 DAG。
如果想執行列出儲存庫近期變更之類的操作,就必須處理其中的提交。處理某個提交時,您會取得指向其 tree 根節點的指標。從該 tree 中,可取得指向每個檔案和子 tree 的指標。從原始提交中,則可取得指向其父提交的指標 (也就是歷程中位於它之前的提交) 。關鍵在於,走訪過程的每一步,都必須先擷取前一個指標,才能得知下一個指標的值。如果每次擷取都需要往返分散式儲存庫一次,成本很快就會變得極高。
這種在物件層級分散 Git 的做法過去已嘗試過許多次,卻經常無法擴展到大規模使用。最有希望的實作,是我的前導師 Shawn Pearce 在 Google 版本控制系統團隊工作時嘗試的方案。他的方法是將物件儲存在分散式雜湊表中。這要歸功於 JGit——一個以 Java 撰寫的自訂 Git 實作。和任何經典 Java 程式庫一樣,JGit 提供了足夠多的介面、factory 和介面 factory,得以抽象化一般 Git 儲存庫的所有細節,包括以 DHT 取代磁碟上的 packfile。雖然系統確實能運作,且對一般 Git 操作來說效能已足夠理想,但 Git 通訊協定的限制 (無論伺服器端如何儲存資料,都仍必須透過網路傳送 packfile) 使得 git clone 的效能差到最終只能完全捨棄這個設計。
GitHub 與檔案系統
Git 開始走出 Linux Kernel 的小圈子幾年後,一家充滿衝勁的新創公司在舊金山誕生。GitHub 於 2008 年成立,是一個社交程式設計平台,並採用了極具遠見的標語:「Git 儲存庫託管:不再痛苦不堪。」我不是在開玩笑,你可以查查看。早在 2008 年,就已有廣泛共識認為,儘管 (或許正因為) Git 採用分散式設計,仍需要以集中式方式託管 Git 儲存庫,才能讓它更易於使用,而這件事非常棘手。GitHub 決心改變這點。
其平台起初是 (而且大致至今仍是) Rails 單體式應用程式。最早的版本運行在一台效能強大但仍是單一的機器上,搭配 Ruby 伺服器,以及存放在旁邊磁碟上的儲存庫副本。擴充 Rails 應用程式很容易:部署更多執行個體即可。但在這個特殊情況下,由於涉及 Git,他們很快就遇到我們在此試圖解決、且一再出現的問題:如果 Rails 應用程式需要存取磁碟上的 Git 儲存庫,要如何部署更多執行個體?
作為一群精打細算的異類,GitHub 早期的系統工程師嘗試了最簡單、可能解決擴充問題的方法。他們的想法是,若專注於分散 檔案系統 (而不是 packfile 或 Git 本身) ,就能維持 Rails 應用程式不變,並將時間投入為持續成長的使用者群推出更多功能,而不是對 Git 做些奇怪的事。非常務實。但行不通。
團隊針對 Git 資料的分散式檔案系統嘗試了許多方法:最顯而易見的是使用 NFS,將所有儲存庫存放在集中式伺服器上,但很快就被捨棄。Git 的預設實作對檔案系統語意 (鎖定、寫入撕裂、讀取、同步……) 做了許多假設,這些假設可確保在速度較慢的開發者筆電本機檔案系統上有不錯的效能,卻完全未考慮它們在網路檔案系統上的行為。它既慢又問題百出。
後續又嘗試了一些 (坦白說,事後看來很可怕的) 在區塊層級複寫檔案系統的技術。曾短暫部署 GFS。也曾較長期地採用以 DRBD 為基礎的部署。它們全都碰壁了。它們在日常營運上 糟糕透頂,效能也不足以彌補這點。一切都歸結於磁碟上 packfile 的設計。
我們已經看過 Git 的圖狀資料結構如何讓往返成本高得難以接受。遺憾的是,類似的原理也適用於底層的磁碟資料。DAG 中物件的配置方式,與它們在 packfile 中的放置方式毫無關聯。產生 packfile 時的關鍵啟發式方法是盡可能縮小檔案大小;物件隨機分散於整個 pack 中,會經過壓縮,而且更關鍵的是,它們很少以完整形式儲存。大多數物件都是以同一個 packfile 中另一個物件的差異形式儲存。讀取單一物件時,在圖狀資料結構中經過許多邏輯跳轉後,也必須在磁碟格式中進行實體跳轉。
這種跨越數 GB 資料的隨機走訪,必須在儲存庫上的每一項 Git 操作中進行,根本無法與網路檔案系統良好搭配 (無論是在檔案或區塊層級複寫) 。若不想慢到幾乎停滯,唯一的方法就是能在本機快取整個檔案。但同一個檔案系統中有數十萬個儲存庫時,快取並不可行。
最終,GitHub 的系統工程師硬著頭皮放棄將檔案系統分散化。他們開始開發 RPC 系統,讓儲存庫能存放在專用檔案伺服器上,並更新 Rails 應用程式,以遠端執行所有操作。這帶來了相當程度的水平可擴充性,但沒有解決可用性問題,也未改善最繁忙儲存庫的效能。畢竟,每個儲存庫仍只存放在單一機器上。
Spokes 與一致性
Spokes 最初由 GitHub 於 2013 年左右開發,後來已成為業界標準。大多數 Git 託管服務的架構中,都採用了 Spokes 方法的某種變體 (針對 Git 儲存庫的應用程式層級複寫) 。Spokes 多年來成效良好的主要原因,在於它做出了三項根本性的選擇,而這些選擇已隨時間證明是最佳做法:
- 它不分散 Git 本身,而是在 packfile 層級運作。
- 它將所有資料以真正的 Git 儲存庫形式,儲存在本機 NVMe 磁碟上。
- 它複寫 Git 資料,並讓所有副本持續保持一致同步。
如同我們剛才討論的,packfile 具有隨機讀取模式,因此將原生 Git 儲存庫儲存在 NVMe 磁碟上,幾乎是確保所有基本 Git 操作維持高速的必要條件。這也能讓複製作業維持高效率,因為不必將資料轉換為 Git 用戶端預期的格式。它也讓您能專注於在 Git 之上打造產品,而不是自行維護一個能操作那些特殊儲存庫的 Git 分支版本。
讓所有資料副本持續保持一致同步,同樣至關重要。這是得從慘痛經驗中才會明白的事:Git 用戶端 確實 無法妥善處理最終一致性。如果您的本機 Git 用戶端推送了一次提交,卻在擷取後立即無法讀取它,這就麻煩了。Git 會因此非常困惑。如果您在一百個執行器上執行 CI 管線,其中三個在複製儲存庫後找不到應該測試的提交,這也很麻煩,而且會帶來非常糟糕的使用者體驗。
無論是在用戶端還是後端,使用 Git 儲存庫的最終一致性檢視都充滿許多陷阱。因此,Spokes 付出極高的複雜度代價,以確保系統始終完全一致。接下來看看這究竟意味著什麼。
Spokes 是一個以_共識_為基礎的分散式系統。它會在不同伺服器上儲存 Git 儲存庫的多個副本。每當您推送新資料時,協調器會將推送作業扇出,讓儲存庫的每個執行個體都收到一份副本。這個「扇出」會透過稱為 3PC (三階段提交) 的經典共識演算法進行同步,因此只有在多數節點確認後,推送才會被接受。
在進一步討論 Spokes 如何使用 3PC 前,我們必須先了解 Git 推送如何運作。Git 推送包含兩個元件:packfile 與_參考交易_。我們已經談過的 packfile 包含要推送至儲存庫的物件 (blob、tree,以及包含變更的提交) 。交易則是透過更新一個或多個參考資料 (例如您正在處理的分支) ,使其指向剛推送的提交,藉此實際將變更發布到儲存庫。
這種分離在此非常方便,因為在指向推送提交的參考資料更新前,該提交不會可見 (以 Git 的術語來說是「可達」) 。這表示我們可以透過同時將 packfile 扇出至所有主機 (此處無須同步) ,_然後_對參考交易執行三階段提交,來為推送實作共識;參考交易比 packfile 小得多,同步速度也更快。Git 本身支援準備參考交易:它可以取得參考資料的鎖定、驗證現有值是否符合預期,並持續保留鎖定,直到收到交易的提交或中止指令。
透過此設計,我們確保每次推送都會在所有複本間完全同步。讀取作業 (擷取、複製) 便可安全地路由至_任何一個複本_,因為每個複本始終都是最新狀態。
這基本上就是 Spokes 的運作方式,而且在過去 13 年來運作得相當良好。當然,Spokes 並不完美——沒有任何系統是完美的。到了 2026 年,人們使用 Git 儲存庫的方式已發生巨大變化,我們也在過程中學到許多建構分散式系統的重要教訓。時間與經驗顯示,Spokes 的哪些選擇最終證明是最佳做法,哪些則不是。
後來證明至關重要的一項缺陷,是 3PC 的水平可擴充性受限。Spokes 最初推出時,每個儲存庫使用三個複本是最佳平衡點。三個複本足以服務一般儲存庫,還有餘裕容量;同時也具備足夠備援,即使一台機器故障,仍能繼續接受推送。
到了 2026 年,情況已大不相同。企業的平均儲存庫如今已是龐大的 monorepo。三個複本不足以承載這類儲存庫的流量,尤其是 CI 流量。當然,除了令人畏懼的規模化下的尾端延遲外,沒有任何因素阻止 Spokes 使用超過三個複本。三階段提交能非常優雅地對應至 Git 交易模型,但作為共識演算法,它有根本性的限制:每個步驟的延遲都取決於叢集中最慢的伺服器。叢集加入的複本越多,推送吞吐量就越差。
這項可擴充性限制也同樣適用於另一種情況。當 Agent 大規模使用 Git 儲存庫時,往往不使用 monorepo,而是建立大量小型儲存庫,其中許多是一次性的,而且大多幾乎不會被使用。Spokes 在這方面表現不佳,因為每個這類儲存庫仍需要三個複本。這三個大多閒置的複本無法縮減,否則系統便不再完全一致,也可能發生資料遺失。使用三階段提交時,下限總是太高,上限又太低。
另一項缺陷事前無從察覺,卻在親身經歷後令人痛苦地明白:Spokes 在大規模環境下可能相當_難以維運_。由於磁碟上的儲存庫始終是共識的權威資料來源,每個儲存庫的每個複本都_極其重要_。你必須將儲存庫視為_寵物,而非牲畜_。
首先,這表示你必須確切知道每個儲存庫的位置。這會增加對外部資料庫的相依性 (以及潛在的可用性問題) ;該資料庫必須維護一份龐大的路由表,將每個儲存庫對應至存放其複本的每台機器。每個儲存庫也必須計算總和檢查碼,並持續更新表中的檢查碼,以確保儲存庫在磁碟上保持有效。一旦儲存庫出現問題 (相信我,問題隨時都會發生——Git 在實務上可能非常挑剔) ,你就必須偵測到問題,並排程修復工作,讓它恢復健康狀態。而且必須非常迅速!因為,重申一次,磁碟上的儲存庫是權威資料來源。損毀的複本和遺失的複本一樣糟糕。如果三個複本中有兩個損毀,系統就無法再接受推送:因為沒有法定人數。
Continuity
Continuity (簡稱 Cnt) 是我們在 Cursor 開發的 Git 儲存系統,理念非常明確:汲取 Spokes 做對的一切,並修正多年後我們如今已知的問題。
Cnt 是一套簡單的系統 (系統若不簡單,就不可能易於操作) 。其核心基礎元件是預寫日誌,我們將其儲存在相容於 S3 的物件儲存體中。在正式環境中,我們直接使用 S3,但其設計可部署至任何雲端。
當儲存庫收到推送時,我們會將推送作為 WAL 項目儲存在 S3 中。**在推送完全持久化之前,我們絕不會確認該推送。**每次推送都會儲存為獨立物件;我們會將推送的 packfile 寫入磁碟,並同時上傳至 S3。然而,上傳 WAL 項目並不代表將其發布。只有在儲存庫的本機副本上成功準備好其參考交易,並在 WAL 索引檔中記錄指向該 WAL 項目的指標後,推送才會可見;該索引檔本身也是儲存體中的一個物件。這確保所有推送皆可線性化。
我們盡量避免每次推送只進行一次 S3 寫入,因為在繁忙的儲存庫中,這會依據 S3 PUT 操作的延遲,對推送吞吐量設下硬性上限。透過經過仔細調校的批次處理實作,加上只需將參考交易與單一本機儲存庫同步,而非與多個複本的法定人數同步,我們的系統能以磁碟所允許的速度接收推送。
儲存庫的本機副本當然是儲存在高速 NVMe 磁碟上的一般 Git 儲存庫。我們採用與 Spokes 相同的做法,因為我認為 Spokes 在這點上做得完全正確。這讓我們能重複利用 Git 社群出色的 OSS 成果,包括上游 Git 用戶端及其眾多效能最佳化。讓我們能專注於推出新功能,而不是對 Git 做些奇怪的事。
共識
我們已經看到,Spokes 叢集難以維運的一個原因,是必須精確掌握每個儲存庫在各伺服器上的位置。Cnt 的做法則截然不同。每個儲存庫存放在哪裡?答案是「任何地方」。這根本不重要!我們將儲存庫視為磁碟上的熱快取,但唯一的權威資料來源始終是 S3 中的預寫日誌。系統是無狀態的,沒有路由表 (也沒有需要維運的關聯式資料庫——真是太棒了) 。如果在主機上存取儲存庫時,本機磁碟上沒有該儲存庫,我們就從 WAL 將其具現化。我們可以非常有效率地做到這點,但當然不希望_一直_這麼做,因為太浪費資源。在正式環境中,我們使用 rendezvous hashing,將儲存庫 ID 對應至預期存放該儲存庫的節點清單。路由儲存庫所需的所有狀態,只有儲存庫 ID 和叢集中目前健康的節點集合。但即使這些狀態不同步 (例如某個節點變得不健康) ,也完全沒問題。我們只要在下一個節點上將儲存庫具現化即可。
那共識呢?選舉呢?某個儲存庫的主要伺服器是哪一台?這也不重要!這裡沒有狀態,也不需要共識。任何伺服器都可以是主要伺服器。所有對預寫日誌的更新,都會透過 S3 上的原子比較並交換 (CAS) 操作同步,因此任何儲存庫執行個體接收 push 都是安全的。同樣地,如同路由機制,讓任意伺服器擔任主要伺服器並不是最高效率的做法 (這會導致 CAS 重試,可能延遲 push) ,因此實務上,我們一律選擇同一台伺服器作為主要伺服器,也就是 rendezvous hashing 排名清單中的第一台。但在一些特殊情況下——部署、容錯移轉或短暫的網路異常——我們不必在意究竟哪台伺服器是主要伺服器。系統設計成在降級時始終正確,在健康時始終快速。
複寫
將預寫日誌存放在 S3 中,為系統擴充帶來無限可能。我們可以擁有 任何 數量的複本,因為 S3 的可擴充性無可匹敵,所有複本都能直接從中追上進度。我們透過在叢集中傳送 gossip UDP 封包來進行樂觀式複寫。每次推送後,這些封包都會包含讓各個複本直接從 S3 追上進度所需的全部中繼資料。「這太瘋狂了,」我聽見你隔著時空、在螢幕前喃喃自語。「UDP 並不是可靠的傳輸協定。」當然不是。分散式系統中沒有任何東西可靠!網路不可靠、路由不可靠,拓撲也不可靠。但沒關係:這並不重要。每個複本都知道自己已追上的 WAL 索引最後一個版本的 ETag。當你在複本上執行讀取操作時,我們會使用預期的 ETag 向 S3 發出條件式 GET 請求。不含本文的 304 回應 (這幾乎是即時操作——平均不到 10ms,因為這是僅涉及中繼資料的 S3 操作) 表示資料已是最新狀態,我們可以立即提供擷取或複製服務。200 回應則會帶有最新版 WAL 索引,我們會先據此追上進度,再提供讀取服務。
即使複寫 UDP 封包遺失,或因拓撲變動而送抵錯誤的伺服器,也無關緊要。所有複本上的所有讀取結果都完全一致,因為都會根據作為唯一真實來源的 S3 進行驗證。系統的設計目標是:降級時始終正確,健康時始終快速。
這帶來兩項重要影響。首先,因為系統始終一致,在其上打造基礎架構變得非常簡單。我們的代理、Web 介面和用戶端,始終看到儲存庫全域一致的檢視。再者,由於系統可向 兩個 方向擴展,每個儲存庫都能配置恰到好處的複本數量。大型 monorepo 可部署至數百個複本,以承擔所有 CI 工作負載。由代理建立的數百萬個小型儲存庫,每個只需一個複本即可提供服務;由於 S3 是唯一真實來源,我們不需要超過一個複本來確保可用性。事實上,閒置的儲存庫甚至連一個複本都不需要:當複本一段時間未收到流量時,我們會將其從節點磁碟中垃圾回收,並在下次收到擷取請求時,直接從 WAL 再次具現化。
壓縮
預寫日誌需要定期壓縮。不能讓日誌無限制地增長:完整還原時必須重播每筆記錄,因此記錄越多,成本越高。
巧合的是,標準 Git 儲存庫也需要定期壓縮,儘管 Git 並非以 WAL 為基礎。我們已經看到,Git 儲存庫的基本儲存單位是 packfile。每次將內容推送至儲存庫的遠端副本,或擷取到本機副本時,都會建立新的 packfile。這無法無限擴展:每個 packfile 都有自己的索引,可讓 Git 有效率地查找其中的物件,但這種查找效率僅限於單一 packfile。如果要查找特定物件,而儲存庫中有 100 個 packfile,就必須開啟每個 packfile 的索引並查找該物件,直到在其中一個 packfile 中找到為止。一項原本高效率的操作,若必須執行數百或數千次,也就不再高效率。
現代 Git 已非常擅長解決這個問題;現在支援多 pack 索引與漸進式幾何壓縮。但最終還是必須面對現實,重新封裝磁碟上的 Git 儲存庫。過去,這一直是 Spokes 等系統的可用性問題,因為重新封裝非常耗用 CPU,即使以漸進方式執行也是如此,而且系統中的所有副本都必須執行。若不慎在同一儲存庫的兩個以上 Spokes 節點上觸發維護操作,很容易導致該儲存庫進行容錯移轉。
在此,我們將壓縮成本攤提。只有主要伺服器會執行壓縮,而壓縮結果會套用至磁碟上的儲存庫 以及 WAL。由於所有副本都會跟隨 WAL,因此也會跟隨壓縮事件。副本不會重新封裝;它們只需從 S3 下載已壓縮的 pack,以頻寬換取 CPU 資源。
擴展
複寫與壓縮是決定 Git 儲存系統在負載下效能的兩項關鍵因素。如同剛才所見,兩者密不可分:儲存庫每秒接收的推送越多,讀取效能就越差,因為每次推送的 packfile 都必須經過壓縮,Git 操作才能維持高效率。若要複寫這些推送,壓縮作業也必須複寫,或在每個副本上各自執行。
Continuity 採用 WAL 優先的設計,提供完全一致的水平可擴充性:您可以部署任意數量的副本,唯讀 Git 操作的吞吐量會隨副本數量線性成長。由於叢集中的所有副本都完全一致,我們得以擴展 Git 協定 (複製、擷取) 以及 Origin 在儲存庫之上執行的所有 RPC 操作 (Web UI 互動、REST API、所有代理式介面等) 。
我們已使用最多 100 個副本進行合成壓力測試,觀察到讀取作業穩定地線性擴展,且推送吞吐量沒有任何下降。
叢集的推送吞吐量取決於我們將 S3 上的 WAL 更新到多低的延遲。使用 S3 Standard 時,我們可在壓縮資料並將壓縮後的資料複寫至所有其他節點的同時,維持最高每秒 120 次推送。我們也已在 S3 Express One Zone 上部署高效能叢集,其 PUT 操作延遲低得多。在該環境中,我們每秒可接收超過 300 次推送,實際瓶頸在於 Git 壓縮磁碟上資料的速度。我們正研究創新的磁碟資料配置方式,以降低壓縮的影響:我們的目標是在不放寬嚴格耐久性與一致性保證的前提下,持續最佳化 Git 儲存庫接收程式碼的速度。
- S3 Standard
- S3 Express One Zone
Cursor 的 monorepo everysphere 的推送/複製吞吐量。
所有推送皆可線性化,並會在確認前持久化至外部儲存空間。
所有複製皆完全一致。
WAL 作為真相來源
S3 是一項出色的技術。由 S3 API 開創的 blob 儲存概念,已成為大型資料儲存系統極為強大的基礎元件,當然也適用於託管 Git 儲存庫。這裡提出的設計在許多方面都很創新,但並非第一個將 packfile 儲存為 blob 的方案。Azure DevOps (Microsoft 自家 GitHub 的競爭對手) 擁有一套非常成功的 Git 儲存系統,將 packfile 儲存在 blob 儲存體中,並將參考資料儲存在關聯式資料庫 (MS SQL Server) 中。這類系統存在許多取捨。關聯式資料庫能很好地擴展以處理大型參考交易,但也表示您必須自行營運關聯式資料庫。我們深信,Git 資料的一致性比任何其他考量都更重要。這正是促使我們設計出不依賴外部資料庫的 WAL 型系統的關鍵。
正式環境中的 Git 儲存庫可能出現許多問題:靜態資料損毀、重新封裝期間的錯誤、推送期間的競爭條件,構成了一大堆邊緣案例。其中大多數已在 Git 上游專案中解決,但並非全部。沒有任何系統毫無錯誤,即使是 OSS 且廣泛部署的系統也不例外。我們的一致性模型確保能追蹤儲存庫中發生的每一項基本操作。**在推送完全持久化至 WAL 前,我們絕不確認該推送。我們會將 所有 推送線性化。我們存取的每個儲存庫的每個檢視,始終完全一致。**由於每次推送都記錄在 WAL 中,我們可以查看儲存庫曾經歷的每一個狀態。我們擁有所有推送和重新封裝作業的完整來源資料。我們可以將每個複本倒轉或快轉。我們不必與任何外部資料庫同步任何狀態,無論該資料庫僅儲存參考資料,還是儲存所有物件資料。當我們遇到 Git 中的錯誤時 (問題不在於會不會遇到,而是何時遇到) ,可以精確找出發生了什麼事並將其還原。除了 Git 中既有的錯誤外,我們引入的新錯誤極少,因為整個過程中的所有 Git 操作,都是使用現成工具在磁碟上的一般 Git 儲存庫中執行。
Origin
我們深知託管他人原始碼責任重大。我想每位閱讀並理解這篇部落格文章的人,也同樣清楚這一點。一家公司若開發人員無法向 Git 儲存庫推送或從中拉取,就可能完全停擺。CI 系統停機五分鐘所造成的生產力損失很難以金額量化,但無論以何種標準衡量,都是極其龐大的數字。
Agent 從根本上改變了我們使用軟體的方式,而在許多方面也讓這種情況變得更糟:更多程式碼、更多 PR、更多 CI 執行。版本控制是這一切的核心,而且可能是最難在一夕之間改變的事。
過去數月來,我們在 Cursor 內部一直面臨這些難題,並投入大量心力打造一個能為我們解決這些問題,也希望能為客戶解決同樣問題的平台。目前,我們專注於提供最順暢的轉換途徑,協助您提升可靠性、效能與擴充規模,並讓遷移過程盡可能輕鬆無痛。
Origin 並非實驗;它是由深刻理解這些挑戰之艱鉅程度的人,累積數十年打造這類系統的經驗所成就。我們擁有已被證實有效的工程與營運理念,並堅定承諾隨著版本控制環境的演進持續精進。
我們希望您能信賴我們和我們的平台。