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

The Google File System

一套把商用硬體故障視為常態、檔案視為巨大、並把並行 append 當成一級原子操作的叢集檔案系統。

作者Sanjay Ghemawat、Howard Gobioff、Shun-Tak Leung(Google) 發表於SOSP 2003 年份2003
閱讀原始論文 PDF 所有論文

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

GFS 把數 GB 的大檔案切成固定 64 MB 的 chunk,預設在數百台商用 Linux 機器上存三份複本,由單一 master 把所有 metadata 放在記憶體中,而所有檔案資料則由 chunkserver 直接送給用戶端。master 完全不碰檔案資料:它只負責發出 chunk handle 與複本位置,並把一份 60 秒的 lease 授予某個複本使其成為 primary,再由該 primary 獨自決定所有複本套用變更的序列順序。在此之上 GFS 加了 record append,由 GFS 自行挑選位移、保證記錄至少被原子地寫入一次,讓數百個 producer 不必經過分散式鎖管理器就能往同一個檔案追加。代價是刻意放寬的一致性模型:並行寫入留下的區域是 consistent 但 undefined,append 之間可能夾帶 padding 與重複記錄,這些都由應用層以 checkpoint 與可自我驗證的記錄格式吸收。實際生產叢集分別有 342 與 227 台 chunkserver、用掉 55 TB 與 155 TB 磁碟,而 master 的 metadata 只有 48 MB 與 60 MB,每秒只需處理 200 到 500 個操作。

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

GFS 之前的參照系是 AFS、xFS、Frangipani、GPFS 與 Lustre:這些系統要嘛在用戶端大量快取,要嘛拿掉中央伺服器改用分散式演算法來維護一致性與管理,要嘛追求 POSIX 相容。它們預設機器大致可靠、檔案大致不大、而所謂變更就是在某個位移覆寫位元組,於是冗餘等於 RAID、正確性等於快取一致性。Google 真實的工作負載一次違反了以上全部:資料集動輒數 TB、由數十億份網頁文件組成;數千顆便宜磁碟故障頻繁到連無聲的資料毀損都成了每週例行事件;而寫入端幾乎只做 append。用戶端快取幾乎沒有價值,因為工作是串流掃過遠大於任何快取的資料集;而 POSIX 相容介面要付出的複雜度,解的是 Google 根本沒有的問題。GFS 由同時掌握檔案系統與其上所有應用的團隊寫成,這讓「協同設計 API 與應用」成為正當選項,而不是取巧。

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

  • 元件故障是常態而非例外:在數百甚至數千台商用機器上,應用程式錯誤、作業系統錯誤、人為疏失,加上磁碟、記憶體、接頭、網路與電源供應的故障,幾乎保證任何時刻都有東西壞掉,而且其中一部分永遠無法復原。
  • 以傳統標準來看檔案極為巨大:數 GB 的檔案是常態,快速成長的資料集動輒數 TB、由數十億個物件構成,因此關於 I/O 操作大小與區塊大小的既有假設全部失效,而管理數十億個 KB 級小檔案就算做得到也極不實際。
  • 幾乎所有變更都是追加而非覆寫,檔案一旦寫完就只被讀取、而且多半是循序讀取,因此針對隨機寫入做最佳化是白費力氣,在用戶端快取資料區塊也失去了吸引力。
  • 數百個 producer(每台機器一個)需要並行追加到同一個檔案,這些檔案被當成 producer-consumer 佇列或多路合併的目標;若用一般指定位移的寫入來做,用戶端就得付出複雜又昂貴的同步成本,例如引入分散式鎖管理器。
  • 對批次處理工作而言,持續的高吞吐量遠比單次讀寫的低延遲重要,這與傳統檔案系統長年最佳化的目標正好相反。
  • 單一中央 master 是憑全域知識做放置與複寫決策最簡單的辦法,但若不把它從一般讀寫路徑中積極排除,它必然成為瓶頸。

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

單一 master、只管 metadata

GFS 只有一個 master,掌管命名空間、檔案到 chunk 的對應以及複本位置,全部常駐記憶體;而所有檔案資料的位元組都在用戶端與 chunkserver 之間直接流動。集中化之所以可行,是因為 master 每次操作要做的事極少,只回傳 chunk handle 與複本位置,而且用戶端會把答案快取起來,所以實測叢集上 master 每秒只承受 200 到 500 個操作。它在成本上也划算:每個 64 MB 的 chunk 只佔不到 64 bytes 的 metadata,檔名再用前綴壓縮,因此數百 TB 的儲存量只需要數十 MB 的 master 記憶體。更重要的是能力上的回報:擁有全域視野的單一 master 能做出精細的 chunk 放置、重新複寫與再平衡決策,而去中心化的 metadata 方案得付出昂貴的協調成本才能達成同樣的事。

64 MB chunk 搭配延遲配置

檔案被切成固定 64 MB 的 chunk,比傳統檔案系統的區塊大上好幾個數量級,每個複本就是一個普通的 Linux 檔案,需要多少才長多少。這個尺寸同時壓低三種成本:用戶端與 master 的往返次數,因為一次查詢就涵蓋 64 MB 的循序 I/O;連線開銷,因為用戶端可以對同一個 chunk 的多次操作維持一條長期的 TCP 連線;以及 master 記憶體,因為 metadata 隨 chunk 數量而非位元組數成長。延遲空間配置化解了最明顯的質疑,也就是內部碎片,因為沒用到的空間根本不會被實際配置出來。剩下的代價是小檔案的熱點問題:論文自己就踩到過,一個可執行檔被存成單一 chunk 的檔案,然後同時在數百台機器上啟動。

用 lease 下放變更順序

master 並不親自排序每一次寫入,而是把某個 chunk 的 lease 授予其中一個複本,也就是 primary;由這個 primary 對它收到的所有變更(可能來自多個用戶端)指派連續的序號。所有複本都依這個序號順序套用變更,因此複本會收斂到相同內容,而 master 完全不在寫入路徑上。全域變更順序因此由兩層構成:master 選擇的 lease 授予順序,加上同一份 lease 之內由 primary 指派的序號;這樣的兩層排序不需要複本之間跑任何共識協定。lease 初始逾時為 60 秒,續約請求搭便車在本來就會交換的 HeartBeat 訊息上;即使 master 與 primary 失聯,只要舊 lease 過期,它就能安全地把新 lease 授予另一個複本。

資料流與控制流解耦

寫入協定把昂貴的部分(搬位元組)和便宜的部分(決定順序)拆開。用戶端先把資料推送給所有複本,之後才聯絡 primary,因此資料路徑可以純粹依網路拓撲來排程,而控制路徑則跟著 lease 所在的位置走。資料沿著一條線性的鏈路以管線方式傳遞而非以樹狀散開,每台機器都轉送給拓撲上最近、尚未收到資料的那台機器,而且一收到就立刻開始轉送,不等整批收完,如此每台機器的全部對外頻寬都只服務一個接收者。理想傳輸時間因而是 B/T + RL,而不是 R 倍的 B/T,這正是三份複本能在 100 Mbps 的商用網路上負擔得起的原因。

原子的 record append

record append 反轉了一般的寫入介面:用戶端只提供資料,由 GFS 決定位移,並保證該記錄至少一次以一段連續位元組的形式落地,然後把位移回傳給用戶端。這消除了並行追加者需要分散式鎖管理器的理由,因為沒有人需要對「目前的檔尾在哪」達成共識,指派位移的永遠只有 primary 一方。由於保證是 at-least-once 而非 exactly-once,失敗的 append 直接重試即可,留下的 padding 或重複記錄變成應用層的問題,而不是一筆分散式交易。回報是數百個分散在不同機器上的 producer 可以共寫同一個檔案,吞吐量只受存放最後一個 chunk 的那些 chunkserver 的網路頻寬限制,基本上與寫入者數量無關。

與應用協同設計的寬鬆一致性

GFS 把一個檔案區域稱為 consistent,若所有用戶端不論讀哪個複本都永遠看到相同資料;稱為 defined,若它是 consistent 而且用戶端能完整看到某次變更所寫入的內容。序列化的成功寫入會產生 defined 區域;並行的成功寫入只會是 consistent 但 undefined,因為所有複本雖然套用了相同順序,結果卻混雜了多次變更的片段;失敗的變更則讓區域變成 inconsistent,因而也是 undefined。這份較弱的契約正是 master 得以退出資料路徑、record append 得以免除共識的原因。論文主張這只有在應用被協同設計的前提下才划算:應用只追加不覆寫、以 checkpoint 讓讀者只處理已知為 defined 的前綴、並在記錄中嵌入 checksum 與唯一識別碼,讓共用函式庫能濾掉 padding 與重複。

把故障當日常而非例外

因為數千顆磁碟的叢集永遠有東西壞著,GFS 把偵測與修復做進穩態運作裡,而不是塞進例外處理路徑。chunkserver 以 64 KB 區塊為單位對自己的資料做 checksum,回傳任何資料前先驗證,所以毀損不會在機器之間擴散;閒置時掃描非活躍 chunk,連沒人讀的冷資料腐化也抓得到。master 依優先順序持續重新複寫低於目標份數的 chunk、跨機架與磁碟使用率再平衡複本,並在例行的背景命名空間掃描中回收孤兒 chunk 與已刪除的檔案。伺服器不論如何終止都能在數秒內恢復狀態並啟動,日常關機就是直接殺掉行程,於是復原路徑就是正常路徑,永遠處於被驗證的狀態。

運作方式 — 具體的機制

讀取路徑:一次 master 往返,之後全靠快取

用戶端依固定的 64 MB chunk 大小,把應用指定的位元組位移換算成檔案內的 chunk index,然後把檔名與 chunk index 送給 master。master 回覆對應的 chunk handle 與各複本位置,用戶端以(檔名, chunk index)為 key 把它快取起來,並帶一個有限的逾時。接著用戶端直接向最近的複本發出請求,內容只有 chunk handle 與 chunk 內的位元組範圍;在快取失效或檔案重新開啟之前,對同一個 chunk 的後續讀取完全不需要再找 master。實務上用戶端會一次要求多個 chunk,master 也會順帶附上緊接在後的 chunk 資訊,幾乎不增加成本就省下未來多次往返。

寫入路徑:環繞 lease 的七個步驟

用戶端先問 master 哪台 chunkserver 持有該 chunk 的 lease、其他複本在哪;若無人持有,master 就挑一個複本授予。用戶端可用任意順序把位元組推送到所有複本,等到每個複本都確認收到後,才向 primary 送出寫入請求;primary 對它收到的所有變更(可能來自多個用戶端)指派連續序號,並依序套用到自己的本地狀態。primary 把請求轉發給每個 secondary,各自依相同序號順序套用,secondary 回覆完成,primary 再回覆用戶端,並把任何複本上發生的錯誤一併回報。發生錯誤時,寫入可能已在 primary 與任意子集的 secondary 上成功,該區域因而處於 inconsistent 狀態,所以用戶端程式碼會先重試步驟三到七數次,再退回從頭重寫;跨越 chunk 邊界的大寫入會被拆成多個這樣的操作,可能與其他用戶端交錯,這正是區域最後變成 consistent 但 undefined 的成因。

資料流:線性管線鏈路

控制流從用戶端到 primary 再到各 secondary,但資料是沿著一條精心挑選的 chunkserver 鏈路線性推送,讓每台機器的全部對外頻寬服務單一接收者,而不是分給多個對象。每台機器把資料轉送給網路拓撲上最近、尚未收到的那台機器;由於 Google 的拓撲夠單純,距離可以直接由 IP 位址準確估算,這樣就能盡量避開壅塞的跨交換器連線。chunkserver 把收到的資料先放進內部 LRU 緩衝快取,直到被用掉或被淘汰;而且一收到就立刻開始轉送,不等整批傳完,於是推送在全雙工 TCP 上形成管線,送出資料並不會降低接收速率。把 B 位元組送到 R 個複本的理想耗時是 B/T + RL;當 T 約為 100 Mbps、L 遠低於 1 ms 時,1 MB 大約 80 ms 就能送達所有複本。

primary 上的 record append 邏輯

用戶端先把記錄推送到該檔案最後一個 chunk 的所有複本,再向 primary 送出請求,其餘流程沿用一般寫入的控制流,只在 primary 多加一點邏輯。primary 檢查追加後是否會超過 64 MB 的 chunk 上限;若會,就把自己的複本填補(pad)到上限、要求各 secondary 同樣填補,並回覆用戶端請改到下一個 chunk 重試——這也是為何單筆記錄被限制在 chunk 上限的四分之一以內,好把最壞情況的碎片控制在可接受範圍。若記錄放得下(常見情形),primary 就在自己的位移追加,指示每個 secondary 在完全相同的位移寫入,最後回覆成功。任一複本失敗時用戶端會重試,因此各複本可能留下重複或不完整的記錄,GFS 明白表示不保證複本逐位元組相同;唯一的保證是:一旦回報成功,該資料必定已在某個 chunk 的所有複本的同一位移上寫入,由此也推得日後任何記錄都會取得更高的位移或落到不同的 chunk,即使之後換了別的複本當 primary 也一樣。

master 的 metadata:記憶體、operation log 與 checkpoint

master 把命名空間、檔案到 chunk 的對應,以及每個 chunk 的複本位置全部放在記憶體,這讓 master 操作很快,也讓它便宜到可以定期完整掃描自身狀態,藉此驅動垃圾回收、重新複寫與再平衡。只有前兩者是持久化的:它們以附加方式寫進本機磁碟上的 operation log 並複寫到遠端機器,任何用戶端操作都要等對應的日誌記錄在本地與遠端都刷到磁碟後才回覆,並會批次多筆記錄一起刷以攤提成本。複本位置刻意不持久化;master 在啟動時以及 chunkserver 加入時輪詢每台 chunkserver,之後靠 HeartBeat 維持最新,因為 chunkserver 對自己磁碟上有哪些 chunk 才有最終決定權,而磁碟可能讓 chunk 憑空消失。當日誌超過一定大小,master 就把狀態做成緊湊的類 B-tree checkpoint,可直接映射進記憶體而不需額外解析;它在另一條執行緒中對新的日誌檔建立,因此不會延遲進行中的變更,幾百萬個檔案的叢集約一分鐘可完成。復原時只需載入最後一份完整 checkpoint 並重播其後的日誌,不完整的 checkpoint 會被偵測並跳過。

命名空間鎖與 copy-on-write 快照

GFS 沒有每個目錄的資料結構,也不支援硬連結或符號連結;命名空間邏輯上是一張從完整路徑名到 metadata 的查找表,在記憶體中以前綴壓縮存放,每個節點配一把讀寫鎖,且採延遲配置、用完即釋放。對 /d1/d2/.../dn/leaf 的操作會對每一層前綴目錄名取讀鎖,並對完整的 leaf 名稱取讀鎖或寫鎖;因此同一目錄下的多個檔案建立可以並行進行(各自只對目錄名取讀鎖),而對該目錄做快照則會取得寫鎖,因而與它們正確序列化。鎖的取得遵循一個全序:先依命名空間樹的層級,同層再依字典序,藉此避免死結。快照會先撤銷被涵蓋 chunk 上尚未到期的 lease,使後續任何寫入都必須回頭找 master,接著把操作寫入日誌並複製來源的 metadata,讓快照檔案指向同一批 chunk;第一次要寫入共享 chunk C 時,master 發現參照計數大於一,就在同一批 chunkserver 上建立 C',讓資料在本機複製而不必過網路(磁碟速度約為 100 Mb 乙太網路的三倍),再對 C' 授予 lease,用戶端渾然不覺剛剛發生過複製。

故障處理:版本號、複製、checksum 與垃圾回收

每個 chunk 都帶一個版本號,master 在每次授予新 lease 時遞增它,並在通知任何用戶端之前,與最新的那些複本一起持久化記錄下來;曾經離線的 chunkserver 重啟後回報較舊的版本,其複本就會在 master 回應用戶端請求時被當成不存在,並在例行垃圾回收時移除。當可用複本數低於目標值,master 依三項因素排定複製優先序:距離目標份數多遠、是否屬於仍存活(而非剛刪除)的檔案、以及是否正阻塞用戶端進度;同時限制全叢集與單台 chunkserver 的同時複製數量,並節流每次複製的讀取頻寬。每台 chunkserver 以 64 KB 區塊搭配 32 bit checksum 獨立驗證自己的資料,checksum 存在記憶體並持久化記錄,回傳任何資料前先驗證,append 時則以增量方式更新;不符時回傳錯誤給請求方並通報 master,由 master 從良好複本複製一份新的,再指示壞掉那台刪除自己的複本。閒置時 chunkserver 會掃描非活躍 chunk,避免冷資料的毀損騙過 master、讓它誤以為複本數足夠。刪除是延遲的:檔案先被改名成帶刪除時間戳的隱藏名稱,預設三天後才真正清除;之後在命名空間掃描中找出孤兒 chunk,各 chunkserver 再透過 HeartBeat 得知自己回報的哪些 chunk 已不在 master 的 metadata 中。

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

  • 在一個由 1 台 master、2 台 master 複本、16 台 chunkserver 與 16 個用戶端組成的測試叢集上(雙 1.4 GHz PIII、2 GB 記憶體、100 Mbps 網卡、兩台交換器間 1 Gbps 連線),16 個用戶端從 320 GB 檔案集隨機讀取 4 MB 區段時,整體讀取速率達 94 MB/s,約為 125 MB/s 網路上限的 75%;單一用戶端則為 10 MB/s,是其 12.5 MB/s 單機上限的 80%。
  • 16 個用戶端各以 1 MB 為單位寫入 1 GB 到各自的新檔案時,整體寫入速率達 35 MB/s,約為理論上限 67 MB/s 的一半;該上限來自每個位元組都必須寫到 16 台 chunkserver 中的 3 台。單一用戶端只有 6.3 MB/s,作者歸因於自家網路堆疊與複本推送管線配合不佳。
  • 對單一共享檔案做 record append,一個用戶端時為 6.0 MB/s,到 16 個用戶端時也只降到 4.8 MB/s,因為吞吐量受限於存放檔案最後一個 chunk 的那些 chunkserver 的網路頻寬,而非追加者的數量。
  • 兩個生產叢集分別有 342 與 227 台 chunkserver,已用 55 TB 與 155 TB 磁碟(以三份複本計,約相當於 18 TB 與 52 TB 的實際檔案資料),共 992k 與 1550k 個 chunk;但 master 的 metadata 只有 48 MB 與 60 MB,平均每個檔案約 100 bytes,印證了 master 記憶體在實務上不會限制系統容量。
  • 這兩個叢集的 master 負載為每秒 200 到 500 個操作,並未成為瓶頸,其中 FindLocation 佔 master 請求的 64.3% 與 65.8%;叢集 A 在前一週持續維持 580 MB/s 的讀取速率,而其網路配置可支撐 750 MB/s。
  • 在叢集 B 上殺掉一台持有 15,000 個 chunk、600 GB 資料的 chunkserver,在預設限制(最多 91 個同時複製、每個複製最多 6.25 MB/s)下,23.2 分鐘內全部恢復到目標複本數,等效複寫速率 440 MB/s;另一次同時殺掉兩台各約 16,000 個 chunk、660 GB 的 chunkserver,使 266 個 chunk 只剩單一複本,這些 chunk 以較高優先序在 2 分鐘內全部恢復到至少兩份複本。

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

  • 論文自承:單一 master 使系統總容量受限於其記憶體能裝下多少 metadata,且所有變更與背景工作都由一個 master 行程執行;重啟後的 master 通常還要 30 到 60 秒才能從所有 chunkserver 取回 chunk 位置資訊,這段期間功能受限。shadow master 只提供讀取且會落後 primary,因此目錄內容之類的 metadata 可能短暫過時。
  • 論文自承:一致性模型確實很弱。並行的成功寫入留下 consistent 但 undefined 的區域,失敗的變更留下 inconsistent 區域,record append 可能插入 padding 與重複記錄,而 GFS 明確不保證複本逐位元組相同;因此應用必須自行寫出可自我驗證、自帶識別碼的記錄,並自行過濾重複。
  • 論文自承:單一用戶端的寫入吞吐量只有理論上限的一半左右,肇因於網路堆疊;而 64 MB 的 chunk 會把小而熱門的檔案變成熱點——當初一個可執行檔被存成單一 chunk 的檔案並同時在數百台機器上啟動,就必須用更高的複本數與錯開啟動時間來補救。
  • 論文自承:三份複本比 xFS 或 Swift 消耗更多原始儲存空間,而 parity 與 erasure code 當時仍只在探索階段;延遲式垃圾回收會拖慢空間回收,讓儲存吃緊時想微調用量的使用者相當困擾;此外它沒有 POSIX API、沒有用戶端快取,也不支援硬連結或符號連結。
  • 後續發展揭露的限制:當 Google 轉向對延遲敏感的互動式工作負載,單一 master 與 64 MB chunk 成了擴展天花板,後繼系統 Colossus 以存放在 Bigtable 上的分片式 metadata 服務取代單一 master,並以 Reed-Solomon erasure coding 取代三份複本;HDFS 繼承了完全相同的單一 NameNode 瓶頸,必須靠 Federation 與 quorum journal 高可用機制繞開,而多寫入者的 record append 則不受歡迎到 HDFS 始終未曾採用。

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

GFS 成了 Google 整個資料堆疊的儲存地基:MapReduce 以 GFS 檔案讀寫輸入與輸出,Bigtable 把 SSTable 與 commit log 存在 GFS 上,因此這個 append-only、at-least-once 的底層同時形塑了那兩篇論文。在 Google 之外,Hadoop 的 HDFS 幾乎是直接的再實作:單一 metadata 伺服器把命名空間放在記憶體並搭配 edit log 與 checkpoint、大區塊(64 MB,後來 128 MB)存三份複本、用戶端沿著儲存節點鏈路的寫入管線,以及帶背景驗證的區塊 checksum。GFS 的自家後繼者 Colossus 保留了 chunk 與 lease 的骨架,但把單一 master 換成存放在 Bigtable 上的分片式 metadata 服務,並用 Reed-Solomon erasure coding 取代三份複本,正是論文 5.1.2 節點名的未來方向。GFS 確立的結構樣式——把 metadata 與大量資料分離、讓 metadata 小到能塞進記憶體、讓用戶端直接與儲存節點對話、把修復當成例行背景工作——如今在 QFS、Ceph、Windows Azure Storage 以及幾乎所有雲端物件儲存中都是標準做法。最具爭議的遺產是 record append:HDFS 始終沒有採用多寫入者追加,GFS 用它服務的多 producer 佇列後來轉由 Kafka 之類的專門日誌系統承接,而 GFS 的寬鬆一致性也成了「以語意換吞吐量」最經典的教學案例。

論文原文 — 逐字引用

“第一,元件故障是常態而非例外。”

§1

“因此,全域變更順序首先由 master 選擇的 lease 授予順序決定,並在同一份 lease 之內由 primary 指派的序號決定。”

§3.1

“GFS 並不保證所有複本逐位元組相同,它只保證資料至少被以一個原子單位寫入一次。”

§3.3

術語 — 依本篇論文的用法

Chunk
檔案被切出的固定 64 MB 片段,在 chunkserver 上就是一個普通的 Linux 檔案,並採延遲配置、需要多少才長多少。每個 chunk 會複寫到多台 chunkserver,預設三份,且可針對命名空間的不同區域設定不同的複寫層級。
Chunk handle
master 在建立 chunk 時指派的 64 bit 全域唯一且不可變識別碼。用戶端與 chunkserver 以 handle 加位元組範圍來指名資料,而不是用路徑名,這正是資料流量完全不需要經過 master 的原因。
Lease 與 primary
master 授予某個 chunk 複本的一份租約,初始逾時 60 秒,可搭 HeartBeat 續約,被授予者即成為 primary。primary 為該 chunk 的所有變更挑選序列順序,其餘複本一律照此順序套用。
Record append
一種變更操作:用戶端只提供資料,由 GFS 自行選擇位移,保證至少一次原子地追加,並把該位移回傳給用戶端。單筆記錄被限制在 chunk 上限的四分之一以內,好把最壞情況的碎片維持在可接受範圍。
Consistent 區域
所有用戶端不論從哪個複本讀取,都永遠看到相同資料的檔案區域。並行的成功寫入會產生 consistent 但非 defined 的區域,因為所有複本雖套用了相同順序,結果仍混雜了多次變更的片段。
Defined 區域
在 consistent 之上,用戶端還能完整看到某次變更所寫入內容的區域。序列化的成功寫入會產生 defined 區域,成功的 record append 所佔的區段也是;應用則靠 checkpoint 得知哪一段前綴屬於 defined。
Operation log
master 唯一持久化的 metadata 記錄,任何變更在本地與遠端都刷到磁碟之前都不會對用戶端可見。它同時是一條邏輯時間線,為並行的 metadata 操作定義全序;檔案與 chunk 也永久以其建立時的邏輯時間來識別。
Chunk 版本號
每個 chunk 一個的計數器,master 在每次授予新 lease 時遞增並持久化,且發生在通知任何用戶端之前。曾離線而漏掉變更的 chunkserver 重啟後會回報較低的版本,其複本在 master 回應用戶端時被視為不存在,並在垃圾回收時清除。
Shadow master
唯讀的 master 複本,套用與 primary 相同的成長中 operation log,通常只落後不到一秒。它在 primary 停擺時維持 metadata 的讀取可用性;由於檔案內容是從 chunkserver 讀的,過時的只可能是 metadata,不會是檔案內容。

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

在時間軸上查看