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

The Part-Time Parliament(兼職議會)

Paxos:以多數決 quorum 讓複本日誌在節點故障與訊息遺失下維持一致,並在網路穩定時取得進展。

作者Leslie Lamport(Digital Equipment Corporation, Systems Research Center),並附有 Keith Marzullo(University of California, San Diego)撰寫的投稿說明 發表於ACM Transactions on Computer Systems 16(2),1998 年 5 月(1990 年 1 月收稿,1998 年 3 月接受) 年份1989–2001
閱讀原始論文 PDF 所有論文

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

Paxos 島的議會必須讓每位議員手上的帳冊保持一致,即使議員隨時離開議事廳、信差可能延遲、遺失或重複投遞訊息。Lamport 的答案是一套建立在三個條件之上的協定:每次投票(ballot)都有唯一編號、任兩次投票的 quorum 必定相交、而新一輪投票必須採用其 quorum 成員先前投過的最高編號那一票的法令。光是這三個條件就能證明任兩次成功的投票必然選出同一條法令,因此一致性完全不依賴時序、訊息送達,也不依賴是否只有一位領導者。進展(progress)是事後另外加上去的一層:選出主席並搭配計時器,把非同步共識的不可能性完全隔離在活性(liveness)那一半。接著替每個法令編號各跑一個協定實例,並讓第一階段對所有實例只執行一次,就得到一個複本狀態機,每條法令只需三個訊息延遲、約 3N 則訊息即可通過。

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

到 1990 年,狀態機方法(state-machine approach)已經存在十多年:Lamport 1978 年的時鐘論文提出了第一個實作任意狀態機的演算法,1984 年的成果則能容忍任意固定數量 f 的任意型(Byzantine)故障。這些演算法換來了有界時間回應與惡意容錯,代價卻很高——f 越大,硬體冗餘、通訊頻寬與回應時間的成本就越高;而且兩台伺服器無法互相通訊等同於其中一台故障,一旦故障數超過 f,各伺服器的狀態複本就會不一致。資料庫界則有 three-phase commit,協調者與參與者之間同樣要交換五則訊息,但它只能在 commit 與 abort 之間二選一。Fischer、Lynch 與 Paterson 在 1985 年已證明非同步環境下沒有任何協定能保證達成一致,因此任何關於進展的宣稱都必須附上時序假設。這篇論文寫於 1990 年,直到 1998 年才刊出,並以一段編輯說明包裝:這份投稿是在 TOCS 編輯室的檔案櫃後面被發現的。

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

  • 帳冊必須一致:若某位議員在第 132 號法令有紀錄,其他議員的帳冊就不能在第 132 號寫下不同內容;但尚未得知該法令的議員可以完全沒有紀錄。
  • 光有一致性沒有意義,因為把所有帳冊留白就能滿足,因此協定還需要一個進展條件,逼迫法令真的被通過並寫入帳冊。
  • 議員隨時可能離開議事廳,並忘記所有沒寫進帳冊的事情——這正好對應到節點當機、只有持久化狀態能存活的情境。
  • 信差不會扭曲訊息,但可能重複投遞、可能先出海半年再送達、也可能一去不回,因此通道是非同步、會遺失也會重複的。
  • 由於議員不願減少廳外的生意活動,根本無法保證任何法令終究會被通過,進展只能在「多數議員待在廳內夠久」的前提下承諾。
  • 不同議員可能都自認是主席而同時發動投票,且編號較大的投票實際上可能比編號較小的更早進行。

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

推導出一致性的三個條件

Paxos 的數學家不是先設計協定再證明,而是先對「所有曾經進行過的投票集合 B」定義三個條件:B1 要求每次投票編號唯一,B2 要求任兩次投票的 quorum 至少有一位祭司重疊,B3 要求一次投票的法令必須等於其 quorum 成員曾參與過的最晚一次較早投票的法令。引理證明只要某次投票成功,之後所有投票都帶著同一條法令;定理 1 由此推得任兩次成功投票選出的法令必然相同。協定接著被定義成「所有能維持 B1 到 B3 的動作」,這也是為什麼每條關於承諾與回報投票紀錄的規則都有顯而易見的理由。順序很重要:這些數學結果確實早於演算法,是數學家在試圖證明「不可能存在可行協定」時發現的。

相交的 quorum,而非全體同意

舊的宗教會議程序要求所有祭司到齊,等到祭司開始隨意進出,這套程序終於徹底失效。Paxos 把全體同意換成只需是一個多數集合的 quorum,而真正被用到的性質只有一個:任兩個多數集合至少有一位祭司重疊。這個交集正是 B2 成立的原因,也是任何新投票必然會看見某次已成功投票之成員所投的票的原因。論文提到這個定義後來被放寬——從簡單多數,到總權重超過一半的集合,再到依出席紀錄給定的象徵性權重——顯示演算法真正依賴的是相交性,而不是計票人數。

承諾,以及把法令留到最後才決定

祭司 q 以 LastVote(b, v) 回覆 NextBallot(b) 時,除了回報自己在編號小於 b 的投票中最高的那一票,同時也承諾往後不再對編號小於 b 的任何投票投下新的票。這個承諾把該祭司的 MaxVote 凍結住,讓主席在第一階段算出的值,到第二階段決定法令時仍然有效。關鍵在於第一輪根本不夾帶法令:主席先從多數集合的 LastVote 回覆中判斷是否已有法令可能被選定,之後才挑出要提的法令。這種「晚決定」正是 Paxos 與由 commit 協定改造而成的版本的差別,也是第一階段能跨法令重複利用的前提。

先天分離的安全性與活性

在基本協定中,每個步驟都是可做可不做的:祭司可以忽略 NextBallot 訊息,可以在被允許投票時仍選擇不投,也可能弄丟記錄當前投票進度的那張紙。不採取行動、或沒收到訊息,最多只能讓某個動作不發生,而缺少動作永遠無法讓 B1 到 B3 變成假,因此一致性在任意當機、訊息遺失與重複之下都成立。這正是論文能把進展當成完全獨立的一層(計時器加主席選舉)疊上去的原因,也是為什麼多位祭司同時自認主席只會拖慢進度、絕不會讓兩本帳冊互相矛盾。共識中所有麻煩的部分都被推進活性那一半,也就是 FLP 不可能性所在之處。

一次第一階段,服務無限多個法令

議會協定在邏輯上等於替每個法令編號各跑一個宗教會議協定實例,但由同一位主席對所有實例只執行一次步驟 1 與步驟 2。一則 NextBallot(b, n) 訊息就是所有編號大於 n 之實例的第一階段,而議員的單一回覆只需帶上他實際投過的那有限多張票,因為他不可能在無限多個實例中投過票。之後每個新請求只花掉步驟 3 到 5——BeginBallot、Voted、Success——所以一條法令只需三個訊息延遲、約 3N 則訊息;若主席把 BeginBallot 與前一條法令的 Success 合併,則降到約 2N。這個攤提正是把五訊息的一致性協定變成實用複本日誌的關鍵。

以無作用法令填補空洞

新上任的主席可能得知第 126 號法令已通過,卻對第 125 號一無所知,因為當初投過 125 號的人都不在廳內。若把新提案編成 125 號,它就會排在一條已經通過的法令之前,可能違反公民的預期。因此主席會用傳統的「橄欖節法令」填滿每個空洞,這條法令什麼也不改變,卻能維持法令排序性質:若法令 A 與 B 都是重要法令,且 A 在 B 被提出之前就已通過,則 A 的編號比 B 小。用 commit 協定的語彙來說,這個技巧就是把 abort 轉譯成日誌中一筆無害的紀錄。

狀態機的讀法

第 4 節給出對照表:議員對應伺服器、公民對應用戶端程式、現行法律對應資料庫狀態、通過法令對應執行一條狀態機命令。因為所有伺服器取得同一串命令序列,它們就會產生同一串回應與狀態變化,於是設計新系統時唯一要發明的只有狀態機本身——一個從(命令, 狀態)對映到(回應, 新狀態)的函式。容錯與同步問題交給一個早已證明正確的標準演算法處理。用論文自己的話說:函式比分散式演算法容易設計得多,也容易做對得多。

運作方式 — 具體的機制

投票、票與 MaxVote

一次投票 B 是一個四元組:法令 Bdec、非空的 quorum Bqrm、實際投票者集合 Bvot,以及取自無界有序集合的編號 Bbal。當 Bqrm 是 Bvot 的子集合時該次投票即為成功,成功投票的法令就是宗教會議選定的法令。一張票是(祭司, 投票編號, 法令)三元組,空票的編號定為負無窮大,讓每位祭司永遠有定義良好的最大值。MaxVote(b, p, B) 是 p 編號小於 b 的最大一票,對集合 Q 則取其成員的最大值;B3 說的正是:只要 MaxVote(Bbal, Bqrm, B) 不是空票,投票的法令就必須等於它。集合 B 本身是歷史變數,Paxons 稱它是只有諸神觀察得到的量,因為任何凡人都可能永遠不知道它的值。

六個步驟的投票流程

步驟 1:祭司 p 從自己專屬的編號區段中選一個大於 lastTried[p] 的編號 b,並送出 NextBallot(b)。步驟 2:祭司 q 若發現 b 大於 nextBal[q],就把 nextBal[q] 設為 b,並以 v 等於 prevVote[q] 回覆 LastVote(b, v),同時等於承諾不再對編號小於 b 的投票投下新票。步驟 3:p 收到某個多數集合 Q 的 LastVote 回覆後,依 B3 決定法令 d——取回報中最大那一票的法令,若全是空票則可任選——並向 Q 送出 BeginBallot(b, d)。步驟 4:nextBal[q] 等於 b 的議員投票,把它寫進 prevVote[q],並回覆 Voted(b, q)。步驟 5 與 6:當 Q 的每位成員都投票後,p 把 d 寫進帳冊並廣播 Success(d),收到的人各自把 d 寫進自己的帳冊。

哪些必須持久化,哪些可以遺失

基本協定存在的目的,就是把祭司必須寫在帳冊背面的資訊壓縮成三項:lastTried[p],他最後嘗試發動的投票編號;prevVote[p],他投過票的最高編號投票中的那一票;以及 nextBal[p],他送出過 LastVote 的最大 b 值。附錄把 prevVote 拆成 prevBal 與 prevDec,並加上 outcome[p],也就是寫進帳冊的法令。其餘一切——status、累積的 prevVotes 集合、quorum、投票者、當前法令——都寫在一張可能弄丟的紙上;紙一旦遺失,status 就退回 idle,該祭司單純停止主持那次投票。由於基本協定一次只主持一輪投票,並忽略自己先前投票的訊息,持久化狀態的大小與嘗試過多少輪投票無關,始終是常數。

主席選舉與時間預算

進展要求必須有人負責發動投票,但發動太頻繁同樣會阻礙進展,因為一個高編號的 NextBallot 會引出承諾,把正在進行中的投票扼殺掉。Paxons 量測過自己的系統:待在廳內的信差 4 分鐘內必定送達,待在廳內的祭司 7 分鐘內必定作出反應,因此一次來回在 22 分鐘內完成。主席只在兩種情況下重新發動投票:前 22 分鐘內沒能執行到步驟 3 或步驟 5,或是得知有人用了更大的投票編號——議員看到過期的 b 時會把 nextBal[q] 回傳給他。主席選舉只需滿足一個要求:無人進出後 T 分鐘內,廳內恰有一位祭司自認是主席;論文給的例子是選字母序最後的名字,每位祭司至少每 T 減 11 分鐘宣告一次自己在場。

多法令議會與追進度

新任主席若帳冊已完整到第 n 號法令,就送出 NextBallot(b, n),它同時是所有編號大於 n 之實例的第一階段。每位議員回覆時附上自己帳冊中所有編號大於 n 的法令,加上尚未定案法令的一般 LastVote 資訊,並請主席補送他缺少的、編號小於等於 n 的法令。主席立刻對那些法令已被 B3 決定的實例執行步驟 3,之後每收到一個新請求,就指派他仍可自由選擇的最小法令編號。前任主席留下的空洞則以橄欖節法令補滿,讓序列保持連續、可依序套用。

以法典取代不斷成長的帳冊

隨著法令數量增加,議員把帳冊改成法典,只記錄法律的現況以及最後一條已反映其中的法令編號——這就是快照加上日誌位置。帳冊完整到 1298 號的議員得知 1299 號把橄欖稅設為每噸 6 drachma 時,只要改掉稅則那一條並把標記推進;若接著聽到 1302 號,他先寫在書後,等收到 1300 與 1301 號之後才套用。為了讓短暫離開的議員不必抄整本法典就能追上進度,最近一週的法令會以清單形式留在書後。這正是壓縮合併(log compaction)再加上一段近期紀錄尾巴以支援增量復原。

任期、專家與查詢法律

曾經同時出現兩位乳酪檢查員,因為被撤換的那位還不知道自己已被替換,於是每張任命法令都夾帶提案時間與固定任期——Dikstra 擔任乳酪檢查員 3 個月,自所述時間或前任任期結束(取較晚者)起算。由於 Paxons 只能把時間判斷到 15 分鐘的精度,任期自 8:30 開始的人要等到自己觀測到 8:45 才上工,這就是帶時鐘偏移保護的 lease。查詢則由單調性條件處理:慢讀(slow read)要通過一條法令,快讀(fast read)直接回傳本地複本,實務折衷是在商業交易中一路帶著法令編號,或把每個法律領域的查詢導向該領域唯一指定的專家。只有在該領域的法律不會改變時,才允許同時存在多位專家——報稅季的稅法專家就是這樣撐過查詢洪峰的。

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

  • 定理 1 證明:只要 B1、B2、B3 成立,任兩次成功的投票必然帶有相同的法令;支撐它的引理證明了更強的敘述——任何比某次成功投票更晚的投票都帶著該次投票的法令,證法是取最小反例導出矛盾,共十一個編號步驟。
  • 定理 2 證明協定不會死結:給定一個比所有既有編號都大的 b,以及一個與所有既有 quorum 都相交的集合 Q,永遠可以在維持 B1、B2、B3 為真的前提下加入一次以該編號與 quorum 成功的投票。
  • 附錄以持久與揮發變數上的原子動作重述基本協定,並給出不變式 I,等於 I1 到 I7 的合取;I 在初始狀態為真,透過 I1、I6 的第一個合取項與定理 1 推出一致性,且被每個允許的動作保持為真,包含訊息遺失、訊息重複,以及代表弄丟紙條的 Forget 動作。
  • 在信差 4 分鐘送達、祭司 7 分鐘反應的假設下,一次請求與回覆在 22 分鐘內完成;若把大門鎖上、廳內有一位主席與一個多數集合,則 99 分鐘內必有一條法令通過並寫入所有在場者的帳冊;若主席選舉需要 T 分鐘,界限就是 T 加 99 分鐘。
  • 主席已就位後的穩定狀態下,通過一條法令只需三個訊息延遲、對 N 位議員與約 N/2 的 quorum 而言約 3N 則訊息;當主席把 BeginBallot 與前一條法令的 Success 合併時,每條法令降到約 2N 則訊息。
  • 論文把結果對照 three-phase commit:兩者在協調者與 quorum 成員之間同樣交換五則訊息,但因為宗教會議協定把法令留到第二階段才送出,議會版本的第一階段每任主席只跑一次,之後每條法令只需三則訊息。

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

  • 論文自承:基本協定對進展毫無保證——所有動作都只是被允許、從不被要求——而論文自己的註腳指向 Fischer、Lynch 與 Paterson,說明任何要達成進展條件的協定都必須量測時間的流逝。
  • 論文自承:演算法不容忍任意的惡意故障。發送互相矛盾訊息的不誠實議員,或誠實者的記憶失誤,都會讓帳冊變得不一致;而書中描述的補救手段——每六個月以冗餘法令把所有法律循環重跑一遍以達成自穩定(self-stabilizing)——作者也坦承理解不足,只能期待未來的考古挖掘找出相關手稿。
  • 論文自承:沒有有界時間回應,因此無法像 Byzantine 即時演算法那樣自然地把時間納入狀態;發生故障時一條命令可能花任意久才執行完,而較晚發出的命令也可能出現在法令序列中較早的位置。
  • 論文自承:變更成員組成很危險。第 n 號法令所使用的議會成員取自第 n 減 3 號法令當下的法律,而進展條件也只承諾「多數集合在場時」會有進展,並不保證多數集合終究會出現——寓言的結局正是一位抄寫員的筆誤,把海難溺斃的水手宣告為議會的唯一成員,此後再也無法通過任何法令,將軍 Lampson 趁亂發動政變。
  • 後續研究揭露:寓言式的寫法讓這個演算法出了名地難以吸收,這也是 Lampson 1996 年的解說、De Prisco 與 Lampson、Lynch 1997 年的形式化、Lamport 自己 2001 年的 Paxos Made Simple,乃至 2014 年 Raft 出現的原因;論文也刻意不指定主席選舉、日誌壓縮與成員變更的細節,而後來的實作者回報,這些沒被指定的部分才是工程上的絕大多數工作量。

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

Paxos 成為大型系統中容錯協調的預設答案。Google 的 Chubby 鎖服務(Burrows, OSDI 2006)本質上就是一個包著類檔案系統介面的 Paxos 日誌;而 Paxos Made Live 報告(Chandra、Griesemer、Redstone, PODC 2007)記錄了論文未指定的那些區塊——主席選舉、日誌壓縮、成員管理、磁碟毀損、測試——才是實作工作的主體。Spanner 為每個分片各配一個 Paxos group,Megastore 則以每個 entity group 一個 Paxos 實例跨資料中心複製,而 Bigtable 與 GFS 把 metadata 的一致性外包給 Chubby。Viewstamped Replication(Oki 與 Liskov, 1988)在本文的後記中被承認似乎與 Paxon 協定等價,ZooKeeper 的 Zab 則是同樣骨架、改寫成 primary-order broadcast 的版本。Raft(Ongaro 與 Ousterhout, 2014)明確以「可理解的替代品」為目標設計,卻完整保留了這副骨架:以單調遞增的 term 取代投票編號、以兼具承諾功能的投票請求取代 NextBallot/LastVote、領導者只為活性而存在;經由 Raft,這套設計進入了 etcd、Consul、CockroachDB、TiKV 與 Kubernetes 控制平面。論文的次要構想同樣流傳深遠:官員任期成為 lease,法典成為快照與壓縮合併,法令編號的單調性技巧則演化成現代共識儲存中的 read-index 與 lease read;而 Flexible Paxos(2016)後來證明只有第一階段與第二階段的 quorum 之間才真的需要相交。

論文原文 — 逐字引用

“Recent archaeological discoveries on the island of Paxos reveal that the parliament functioned despite the peripatetic propensity of its part-time legislators.”

Abstract

“However, in the Paxon Synod, having multiple presidents could only impede progress; it could not cause inconsistency.”

§2.4

“Functions are much easier to design, and to get right, than distributed algorithms.”

§4.1

術語 — 依本篇論文的用法

帳冊(Ledger)
議員的持久化紀錄,以不褪色的墨水書寫,條目一旦寫下就不能更改;書後的註記可以劃掉,而寫在紙條上的東西則可能在離開議事廳時遺失。這正是論文對穩定儲存與揮發性記憶體的區分。
法令(Decree)
編號的法律條目,也是議會達成一致的單位;國家的法律就是已通過法令的序列。在電腦系統的對照中,一條法令就是一個狀態機命令,它的編號就是它在複本日誌中的位置。
投票(Ballot)
針對單一法令的一次編號表決,由法令、quorum、實際投票的祭司集合與投票編號組成。當 quorum 中每位祭司都投了票,這次投票才算成功,成功投票的法令就是被選定的法令。
投票編號(Ballot number)
取自無界有序集合的值,且被分割成各祭司專屬的無限供給,因此兩位祭司不可能用到相同編號。編號較大代表「較晚」,但完全不代表該次投票實際上比較晚進行。
多數集合(quorum)
使一次投票得以成功的祭司集合。真正被用到的性質只有一個:任兩個多數集合至少共有一位祭司,這也是簡單多數、加權多數與依出席紀錄加權的多數都行得通的原因。
MaxVote(b, p, B)
祭司 p 所投、編號嚴格小於 b 的最大一票;若他從未投票,則為編號負無窮大的空票。對一組祭司則取其最大值,這正是主席在第一階段必須查明的量。
條件 B3
對每次投票 B 而言,若其 quorum 中有任何成員曾在較早的投票中投過票,則 B 的法令必須等於那些較早投票中最晚一次的法令。這條約束防止新一輪投票覆蓋掉可能已被選定的法令。
主席(President)
負責發動投票的單一祭司或議員,設立他的唯一理由是讓進展成為可能。同時存在多位主席只會拖慢進展,絕不會造成不一致,這也是領導者選舉不必做到精確的原因。
橄欖節法令(Olive-day decree)
一條對任何 Paxos 人都毫無影響的傳統法令,新任主席用它來填補帳冊中的空洞。它就是無作用(no-op)的日誌條目,讓法令序列保持連續,並維持法令排序性質。

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

在時間軸上查看