跳至主要內容
論文精煉 · Distributed SQL

Spanner: Google's Globally-Distributed Database

第一個把時鐘不確定性攤在時間 API 上、再用等待把它耗掉,從而讓全球分散式交易具備外部一致性的資料庫。

作者James C. Corbett、Jeffrey Dean、Michael Epstein、Andrew Fikes 等 23 人,Google, Inc. 發表於OSDI 2012(第 10 屆 USENIX Symposium on Operating Systems Design and Implementation) 年份2012
閱讀原始論文 PDF 所有論文

一口氣講完 — 整篇論文的濃縮

Spanner 把資料分片到遍布全球資料中心的多組 Paxos 狀態機上,再於這些 Paxos group 之上疊加兩階段鎖定與兩階段提交,提供通用的分散式交易。它最核心的創新是 TrueTime:這個時間 API 回傳的不是單一時間點,而是保證涵蓋呼叫當下絕對時間的區間 [earliest, latest],其瞬時誤差界 epsilon 在生產環境中通常介於 1 到 7 毫秒。協調者 leader 挑選一個不小於 TT.now().latest 的提交時戳 s,並讓資料在 TT.after(s) 為真之前都不可見——這就是 commit wait——迫使提交時戳與真實時間順序一致,使 Spanner 在全球規模上達成外部一致性(等價於 linearizability)。由於每個版本都帶著全域可比較的時戳,唯讀交易與 snapshot read 得以在任何夠新的複本上無鎖執行,schema 變更也能以一個未來時戳,在可能多達數百萬個參與群組之間原子提交。Spanner 以美國境內五個複本承載 Google 的廣告後端 F1,取代了那套上一次重新分片就耗掉兩年以上人力的手動分片 MySQL。

在這篇論文之前 — 它所降落的世界

到了 2011 年,Google 的儲存堆疊分裂成兩半。Bigtable 擴展性極佳,但跨資料中心只有最終一致的複寫,也沒有跨列交易,長期招來 schema 複雜且不斷演進的團隊抱怨。Megastore 恰好提供這些團隊要的東西——半關聯式資料模型與同步複寫——Google 內部至少有 300 個應用在用它,包括 Gmail、Picasa、Calendar、Android Market 與 AppEngine,儘管它在單一 Paxos group 上每秒數次寫入吞吐量就會崩潰。既要規模又要交易的團隊只能繞道:Percolator 在 Bigtable 上補出跨列交易,F1 這個攸關營收的廣告後端則跑在依客戶手動分片的 MySQL 上;那份未壓縮資料只有數十 TB,但上一次重新分片耗掉超過兩年,還得跨數十個團隊協調測試。當時主流看法——Bigtable 與 PNUTS 的作者都在其中——認為通用的兩階段提交昂貴到不值得提供。而且業界沒有任何辦法談論「同一個全域時間」,於是跨資料中心的一致快照或原子 schema 變更,根本沒有明確定義可言。

問題 — 當時真正壞掉的地方

  • 對 schema 複雜且不斷演進、或希望在廣域複寫下仍有強一致性的應用來說,Bigtable 很難用:它跨資料中心只做最終一致複寫,也不支援跨列交易。
  • Megastore 給了應用想要的半關聯式模型與同步複寫,但它沒有長壽命 leader,不同複本發出的寫入即使邏輯上不衝突,在 Paxos 協定裡也必然衝突,單一群組的吞吐量在每秒數次寫入就崩潰。
  • F1 的廣告資料庫是手動分片的 MySQL,每個客戶被釘在固定 shard 上;隨著客戶與資料成長,重新分片耗掉超過兩年的高強度投入,團隊只好把部分資料塞進外部 Bigtable 以抑制成長,代價是犧牲交易語意與跨全部資料查詢的能力。
  • 要指派真正反映序列化順序的提交時戳,在單機上很簡單,跨資料中心卻毫無意義:機器時鐘彼此相差的量未知,而傳統時間介面只回傳一個時間點,完全不表達不確定性。
  • 應用需要細粒度控制資料離使用者多遠、複本彼此相距多遠、要維持幾份複本,但扁平的鍵值儲存無法讓系統得知哪些列屬於同一組、應該一起搬動。
  • 有些操作必須對整個資料庫是原子的——例如參與者數量等於資料庫群組數(可能上百萬)的 schema 變更,或一致性備份——這無法用標準交易表達;而 Bigtable 的 schema 變更只在單一資料中心內原子,且會阻擋所有操作。

核心概念 — 主要貢獻,以及它們為何成立

TrueTime:會報告不確定性的時鐘

TT.now() 回傳的是 TTinterval,一個保證涵蓋呼叫當下絕對時間的區間 [earliest, latest],而非單一時間點;區間寬度的一半就是瞬時誤差界 epsilon。TT.after(t) 與 TT.before(t) 只是便利包裝,分別回答 t 是否確定已過、t 是否確定尚未到來。關鍵在於不確定性從此成為演算法可以明確推理的一等公民,而不是一個會無聲失效的隱含假設。它之所以可行,是因為 Google 能用 GPS 接收器與原子鐘廉價地把 epsilon 壓小,而且當這個界限惡化時,受損的只有延遲,正確性永遠不受影響。

以 commit wait 換取外部一致性

提交由兩條規則支配。Start:協調者 leader 在提交請求抵達後計算 TT.now().latest,指派一個不小於它的提交時戳 s。Commit Wait:在 TT.after(s) 為真之前,任何客戶端都看不到這筆交易提交的資料。兩者合起來把 s 夾在提交請求抵達的絕對時間與提交變成可見的絕對時間之間,於是若 T1 在真實時間上先於 T2 開始前提交,就必有 s1 小於 s2——這正是外部一致性,等價於 linearizability。代價是每筆讀寫交易期望至少多等 2 epsilon,而 Spanner 把這段等待與 Paxos 通訊重疊起來。

全域有意義的時戳讓讀取免鎖

由於每個版本的時戳在整個 universe 中都可比較,一次在時戳 t 的讀取,看到的就恰好是全資料庫在 t 之前所有已提交交易的效果。唯讀交易因此只需選定一個時戳,然後以完全不加鎖的 snapshot read 執行,既不會擋住寫入者,也不會被寫入者擋住。snapshot read 可在任何夠新的複本上執行,所以吞吐量隨複本數成長,而不是全部壓在 leader 身上。而且時戳一旦選定,除非該時戳的資料已被垃圾回收,提交就無可避免,客戶端可以省掉那種必須把結果緩衝起來的重試迴圈。

directory 作為配置的單位

directory 是一組共用相同前綴的連續鍵,也是 Spanner 配置、複寫與搬移資料的粒度。同一個 directory 裡的資料共用一套複寫組態,所以應用只要為 directory 貼標籤,就能讓使用者 A 的資料在歐洲有三份複本、使用者 B 的資料在北美有五份。管理者在「複本數量與型態」和「地理位置」兩個維度上提供一份具名選項的菜單,應用從菜單中挑選組合,乾淨地把政策與機制分開。一個推論是:Spanner 的 tablet 與 Bigtable 的 tablet 不同,它是可容納多段不連續 row space 的容器——這是刻意的設計,好讓經常一起被存取的 directory 能放在一起。

半關聯式 schema 與 INTERLEAVE IN

Spanner 對外是有 schema 的資料表與類 SQL 語言,但每張表都必須宣告一組有序的主鍵欄位,而且每個資料庫都必須由客戶端以 INTERLEAVE IN PARENT 切分成一或多個資料表階層。頂層 directory table 中鍵為 K 的一列,加上所有以 K 為字典序開頭的後代資料表列,就構成一個 directory,並在磁碟上實體交錯存放;ON DELETE CASCADE 則讓刪除父列連帶刪除子列。這使得應用層的資料模型與實體局部性單位變成同一個東西:schema 本身就宣告了哪些列會被一起存取。論文直言,沒有這樣的宣告,Spanner 就無從得知最重要的局部性關係。

在 Paxos 之上疊兩階段提交

每個分片的複本集合構成一個帶長壽命 leader 的 Paxos group;只碰一個群組的交易可以完全略過 transaction manager,因為 lock table 加上 Paxos 已經提供了交易性。跨群組交易則在各參與者 leader 之間跑經典兩階段提交,但每一筆 prepare 與 commit 紀錄本身都經 Paxos 複寫,所以協調者機器故障不會像教科書版 2PC 那樣卡死協定——把 2PC 跑在 Paxos 上,正是為了緩解它的可用性問題。論文明確反駁「通用 2PC 太貴」的主流看法:與其逼所有應用繞開交易來寫程式,不如讓程式設計者在瓶頸浮現時再處理過度使用交易的效能問題。此外,驅動提交的是客戶端而非伺服器,避免把寫入資料在廣域鏈路上送兩次。

不阻塞的原子 schema 變更

schema 變更不能用一般交易來做,因為參與者數量等於資料庫的群組數,可能高達數百萬。Spanner 改為明確指派一個未來的時戳 t 給 schema 變更交易,並在 prepare 階段登記下來。一般讀寫因為隱含依賴 schema,會與這個已登記的 t 同步:時戳早於 t 的照常進行,時戳晚於 t 的則必須擋在該變更之後。整套設計沒有 TrueTime 就毫無意義,因為要讓數千台伺服器一致地認定「在 t 發生」,前提是大家共享一個有界的 t 概念。

運作方式 — 具體的機制

spanserver 軟體堆疊

一次 Spanner 部署稱為 universe,其下切成多個 zone;zone 是管理部署與實體隔離的單位,也是資料可複寫的位置集合,每個 zone 有一個 zonemaster、一百到數千台 spanserver,以及供客戶端尋址的 location proxy,另有單例的 universe master(狀態主控台)與 placement driver(以分鐘為時間尺度自動搬移資料)。每台 spanserver 負責 100 到 1000 個 tablet,tablet 實作的是一袋從 (key:string, timestamp:int64) 到 string 的對應,狀態存放在 Colossus(GFS 的後繼者)上的類 B-tree 檔案與一份 write-ahead log。每個 tablet 之上恰好架一個 Paxos 狀態機,其 metadata 與 log 就存在同一個 tablet 裡;複本集合即 Paxos group,寫入必須在 leader 發起 Paxos,讀取則可直接從任何夠新的複本 tablet 取得,實作採用 pipeline 以因應廣域延遲,但 Paxos 仍依序套用寫入。身為 leader 的複本還額外維護 lock table,把鍵值範圍對應到兩階段鎖定狀態(長壽命 leader 正是有效管理這張表的前提),以及擔任 participant leader 的 transaction manager,其狀態同樣存在底層 Paxos group 中,因此也被複寫。

directory、配置與 movedir

directory 是架在鍵值袋之上的分桶抽象,而 movedir 是在 Paxos group 之間搬移它們的背景工作;由於 Spanner 尚未支援 Paxos 內組態變更,增減複本也是靠 movedir 完成。movedir 刻意不做成單一交易:它先登記搬移已開始,在背景複製資料且不打斷進行中的讀寫,直到只剩下極少量資料時,才用一筆交易原子地搬走剩餘部分並更新兩個群組的 metadata。一個 50MB 的 directory 可預期在數秒內搬完。若 directory 成長得太大,Spanner 會把它切成 fragment,不同 fragment 可由不同 Paxos group(因而不同伺服器)提供服務,movedir 實際搬移的正是 fragment 而非整個 directory。

TrueTime 的實作

每個資料中心部署一組 time master 機器,每台機器則跑一個 timeslave daemon。多數 master 配備 GPS 接收器與專屬天線並實體分散放置;其餘稱為 Armageddon master,配備原子鐘,其故障模式與 GPS 的天線故障、無線電干擾、偽造訊號、閏秒處理錯誤等互不相關。各 master 之間持續互相比對時間來源,也拿自身參考源的走時速率與本機時鐘交叉檢查,一旦偏差過大就自我剔除;同步之間,Armageddon master 以保守的最壞情況漂移公布緩慢增長的不確定性,GPS master 公布的不確定性則接近零。每個 daemon 會輪詢鄰近 GPS master、較遠 GPS master 與部分 Armageddon master 的混合集合,套用 Marzullo 演算法的變形來偵測並剔除說謊者,再據以同步本機時鐘;若機器的頻率偏移超過由元件規格推導的最壞界限,也會被剔除。兩次輪詢之間,epsilon 以套用的 200 微秒/秒漂移率成長,在 30 秒的輪詢週期內形成 0 到 6 毫秒的鋸齒,再加上約 1 毫秒與 time master 之間的通訊延遲。

讀寫交易的執行路徑

交易中的寫入先在客戶端緩衝到提交為止,所以交易內的讀取看不到自己的寫入——這在 Spanner 可行,因為讀取會回傳所讀資料的時戳,而未提交的寫入還沒有時戳。讀取送往相關群組的 leader,由它以 wound-wait 取得讀鎖以避免死結,客戶端則持續送 keepalive,免得參與者 leader 讓交易逾時。提交時,客戶端選定一個協調者群組,並把協調者身分與該參與者的緩衝寫入一起送給每個參與者 leader,避免資料在廣域鏈路上跑兩趟。非協調者的參與者 leader 先取得寫鎖,挑一個大於它先前指派過的所有時戳的 prepare 時戳,透過 Paxos 寫下 prepare 紀錄,再把該時戳回報給協調者。協調者 leader 也先取寫鎖但跳過 prepare,挑一個不小於所有 prepare 時戳、大於收到提交訊息當下 TT.now().latest、也大於自己先前指派過的時戳的提交時戳 s,透過 Paxos 寫下 commit 紀錄,然後等到 TT.after(s) 成立(期望至少 2 epsilon,通常與 Paxos 通訊重疊),才把 s 送回客戶端與各參與者;各參與者記錄結果、在 s 套用,最後釋放鎖。

safe time 與在特定時戳提供讀取

每個複本都維護 t-safe,也就是它保證已跟上的最大時戳;只有當讀取時戳不超過 t-safe 時,該複本才能服務這次讀取。t-safe 取兩個量的最小值。Paxos safe time 就是已套用的最高 Paxos 寫入的時戳,之所以成立,是因為群組內時戳單調遞增,而 Paxos 依序套用寫入。transaction manager safe time 在沒有「已 prepare 但未 commit」的交易時為無限大;否則等於該群組中所有已 prepare 交易的最小 prepare 時戳減一,因為這些交易的結果、以及它們影響的狀態,都還處於未定。整套機制建立在兩個不變式上:disjointness,同一個 Paxos group 中每位 leader 的租約區間與其他 leader 互不重疊,並且是用 TrueTime 而非額外的同步 log 寫入來保證;monotonicity,群組內指派給 Paxos 寫入的時戳即使跨 leader 交替也單調遞增,做法是要求 leader 只能在自己的租約區間內指派時戳。

唯讀交易的時戳指派

唯讀交易必須事先宣告不含寫入,執行分兩階段:先指派讀取時戳,再把所有讀取當成該時戳的 snapshot read,送到任何夠新的複本上執行。Spanner 要求每筆唯讀交易提供 scope 運算式,用來概括整筆交易會讀到哪些鍵;獨立查詢的 scope 由系統自動推導。若 scope 落在單一 Paxos group,客戶端直接找那個 leader,而它可以做得比 TT.now().latest 更好:在沒有已 prepare 交易時,可把讀取時戳設為 LastTS(),即該群組最後一筆已提交寫入的時戳,這自然滿足外部一致性,因為交易就排在那筆寫入之後。若 scope 橫跨多個群組,最完整的作法是與所有 leader 跑一輪協商、依 LastTS() 決定時戳,但目前實作採較簡單的路線,直接在 TT.now().latest 讀取,代價是可能要等 safe time 推進。

避免無謂等待的各項精修

在原始定義下,單單一筆已 prepare 的交易就會把整個群組的 transaction manager safe time 釘住,連完全不衝突的讀取也一併擋掉;Spanner 用細粒度的「鍵值範圍到 prepare 時戳」對應解決這種假衝突,而這份資訊可以放進本來就把鍵值範圍對應到鎖 metadata 的 lock table,讀取只需針對真正衝突的範圍做檢查。LastTS() 有對稱的弱點——剛提交的交易會逼一筆毫不衝突的唯讀交易採用更晚的時戳——同樣的按範圍對應即可解決,但論文註明這項最佳化當時尚未實作。Paxos safe time 在沒有寫入時無法推進,因此每個 leader 維護 MinNextTS(n),即它可能指派給 Paxos 序號 n 加一的最小時戳;已套用到 n 的複本可以把 Paxos safe time 推進到 MinNextTS(n) 減一,而租約互不重疊正是讓這些承諾跨 leader 仍然有效的關鍵。leader 預設每 8 秒推進一次 MinNextTS,也可應 slave 要求隨時推進,所以最壞情況下,閒置群組的健康 slave 只能服務時戳早於 8 秒前的讀取。

論文證明了什麼 — 量測數據與證明

  • 在 4 核心、4GB 排程單位、50 個 Paxos group 與 2500 個 directory 的微基準測試中,單一複本上 4KB 寫入耗時 14.4 +/- 1.0 毫秒,關閉 commit wait 時為 9.4 +/- 0.6 毫秒,因此 commit wait 約佔 5 毫秒、Paxos 延遲約 9 毫秒;到 3 與 5 個複本時延遲大致持平且標準差更小,因為較大的 quorum 比較不受單一慢速 slave 影響。
  • 吞吐量的分化完全符合預期:snapshot read 幾乎隨複本數線性成長,1、3、5 個複本分別為 13.5、38.5 與 50.0 Kops/秒;寫入吞吐量則為 4.1、2.2、2.8 Kops/秒,因為每筆寫入的工作量隨複本數線性增加。
  • 兩階段提交在規模上仍堪用:實驗跑在 3 個 zone、每個 zone 25 台 spanserver 上,平均延遲為 1 個參與者 17.0 毫秒、5 個 31.5 毫秒、50 個 42.7 毫秒、100 個 71.4 毫秒,直到 200 個參與者才陡升到 150.5 毫秒;在 100 個參與者以內,99 百分位仍在 132 毫秒以下。
  • 可用性測試以 5 個 zone、每 zone 25 台 spanserver、1250 個 Paxos group、100 個客戶端合計每秒 5 萬次讀取、所有 leader 集中在同一個 zone 進行:殺掉非 leader 的 zone 對讀取吞吐量毫無影響;先交接 leadership 再殺掉 leader zone 只掉 3 到 4 個百分點;毫無預警地硬殺 leader zone 則讓完成率幾乎歸零,約 10 秒後完全恢復,恰好等於 Paxos leader 租約長度。
  • 在相距最遠達 2200 公里的資料中心、數千台 spanserver 上量測的 TrueTime epsilon,於 90、99 與 99.9 百分位都維持在低檔;生產環境中 epsilon 呈約 1 到 7 毫秒的鋸齒、平均接近 4 毫秒,3 月 30 日網路改善後長尾明顯下降,4 月 13 日約一小時的異常升高則追查出是兩台 time master 因例行維護而關機。
  • F1 生產環境連續 24 小時的量測顯示:215 億次讀取平均 8.7 毫秒,3120 萬次單站點提交平均 72.3 毫秒,3210 萬次多站點提交平均 103.0 毫秒;F1 中超過一億個 directory 只有單一 fragment,因此絕大多數客戶的讀寫保證只落在單一伺服器上,而 fragment 數達 100 到 500 的 directory 只有 7 個,全都是次級索引資料表。

限制與取捨 — 論文自承的,以及後來被發現的

  • 論文自承 commit wait 為每筆讀寫交易額外收取約 2 epsilon 的延遲,寫入效能因此被時鐘品質所限;作者指出,對於在鄰近資料中心之間複寫的應用,epsilon 可能明顯影響效能,並表示他們看不出把 epsilon 壓到 1 毫秒以下有什麼不可跨越的障礙。
  • 數項實作上的權宜之計被直接寫進論文:每筆 Paxos 寫入都記錄兩次,一次在 tablet 的 log、一次在 Paxos log;Spanner 不支援 Paxos 內組態變更,因此複本成員異動必須走 movedir;細粒度版本的 LastTS() 最佳化雖已設計卻尚未實作。
  • 作者表明,節點內的資料結構在複雜 SQL 查詢上效能相對不佳,因為它們原本是為簡單鍵值存取而設計;此外 Spanner 當時沒有自動次級索引,F1 只能用 Spanner 交易自行打造一致的全域索引。
  • 可用性受 10 秒 Paxos leader 租約所限:leader zone 非優雅地失效時,完成率幾乎歸零,直到租約到期、新 leader 選出為止;論文也承認縮短租約雖有幫助,代價卻是更多的租約續約網路流量。
  • 後續發展揭露了論文視為理所當然的深層依賴:TrueTime 需要每個資料中心都有 GPS 接收器與原子鐘,這是多數營運者沒有的硬體;正因如此,CockroachDB 等後繼者改用 hybrid logical clock 加上不確定性視窗,並接受偶發的讀取重試作為替代。

它後來變成什麼 — 繼承這個想法的系統

Spanner 把「規模與交易只能二選一」從教條變成一個標好價格的工程取捨,而那個價格就是幾毫秒的 commit wait;它也讓外部一致性從理論性質變成可出貨的產品特性。Google 於 2017 年以 Cloud Spanner 商品化,之後又發表了長在它之上的 SQL 層論文,F1 本身則演化成獨立的分散式查詢引擎;同一套時戳機制也支撐著 Google 的一致性備份與一致性 MapReduce 執行。CockroachDB 幾乎照單全收這套架構——以共識複寫的 key range、其上的分散式 SQL、跨 range 的兩階段提交——但把 TrueTime 換成 hybrid logical clock 加最大時鐘偏差區間,以讀取重試取代 Spanner 的專用硬體。YugabyteDB 走了類似的 hybrid logical clock 路線,TiDB 則採用承襲自 Percolator 的集中式時戳 oracle 這條姊妹路徑。Spanner 同時重新點燃 CAP 論戰:Google 後來公開主張 Spanner 技術上屬於 CP、實務上等同 CA,因為在 Google 自家網路上跑 Paxos 讓網路分割罕見到可以忽略,這個論述如今已是跨地域 SQL 系統的標準說法。最後,論文結尾那句要求——分散式演算法不該再依賴鬆散同步的時鐘與貧弱的時間 API——已被 AWS Time Sync 的 ClockBound 與 Aurora DSQL 所印證,它們正是以 TrueTime 當年的方式,把有界的時鐘誤差揭露給應用程式。

論文原文 — 逐字引用

“它是第一個把資料分散到全球規模、並支援外部一致分散式交易的系統。”

Abstract

“若不確定性很大,Spanner 就放慢腳步,把那段不確定性等過去。”

§1

“作為一個社群,我們在設計分散式演算法時,不該再依賴鬆散同步的時鐘與貧弱的時間 API。”

§8

術語 — 依本篇論文的用法

Universe
一次 Spanner 部署。由於 Spanner 是全球性地管理資料,實際運行的 universe 只有寥寥數個;Google 當時跑的是測試/實驗用、開發兼生產用,以及純生產用三個 universe。
Zone
universe 之下管理部署與實體隔離的單位,大致相當於一次 Bigtable 部署,也是資料可複寫的位置粒度。一個 zone 含一個 zonemaster 與一百到數千台 spanserver,並可在系統運行中新增或移除。
Spanserver
實際對客戶端提供資料服務的伺服器程序。每台 spanserver 負責 100 到 1000 個 tablet,在每個 tablet 上跑一個 Paxos 狀態機;在它擔任 leader 的 tablet 上,還額外維護 lock table 與 transaction manager。
Tablet
Spanner 的儲存容器,實作一袋從 (key, timestamp) 到字串的對應,底層由 Colossus 上的類 B-tree 檔案與 write-ahead log 支撐。與 Bigtable 的 tablet 不同,它不必是 row space 中單一段字典序連續的分割,因為它可以容納多個 directory。
Directory
一組共用相同前綴的連續鍵,由 directory table 的一列加上該鍵之下所有交錯存放的後代列構成。它是資料配置、複寫組態與跨 Paxos group 搬移的單位;過大的 directory 會被切成 fragment。
TTinterval 與 epsilon
TT.now() 回傳 TTinterval,即保證涵蓋呼叫當下絕對時間的區間 [earliest, latest]。epsilon 是區間寬度的一半,也就是瞬時誤差界,在 Google 生產環境中通常呈 1 到 7 毫秒的鋸齒,平均接近 4 毫秒。
Commit wait
協調者 leader 必須等到 TT.after(s) 對提交時戳 s 成立,才允許任何複本讓該交易提交的資料被看見。這保證了提交變成可見時,s 已確定位於絕對時間的過去,期望代價至少是 2 epsilon。
External consistency
等價於 linearizability 的保證:若交易 T1 在交易 T2 開始之前提交,則 T1 的提交時戳小於 T2 的提交時戳。Spanner 是第一個在全球規模上提供此保證的系統。
Safe time
複本保證已跟上的最大時戳,也就是它能服務讀取的最新時戳。它取兩者最小值:Paxos safe time(已套用的最高 Paxos 寫入之時戳),以及 transaction manager safe time(群組中任一已 prepare 交易的最小 prepare 時戳減一)。

在時間軸上的位置 — 這篇論文在整段故事中的座標

在時間軸上查看