vatt'ghern jaskier's ballads
本文 6 個互動圖表在手機上以重點摘要呈現,互動版請以桌面瀏覽器開啟。

沒有預演、沒有事前優雅地把寫入排乾——工程師走到配電盤前注入一個「電源故障」,讓整個資料中心 region 在同一瞬間斷電。這個 region 是平常故障域的 50 到 60 倍大,而 Meta 想知道的只有一件事:當電在毫秒之間消失,再乾淨地接回來,伺服器、儲存、網路設備到底能不能自己站回原位。

把機房斷電當成測試項目——Meta 怎麼驗證瞬時斷電就緒度

「斷電」當成一個可以排進測試計劃的項目,聽起來像在拿生產環境開玩笑。但 Meta 在 2026 年 6 月公布的這篇工程文章講的正是這件事——他們把「瞬時斷電就緒度(instant power loss readiness)」當作一個要主動驗證、而不是被動等它發生的屬性。驗證的方式不是寫一份 runbook、不是在 staging 模擬,而是對運轉中的整個 region 注入電源故障、讓它立刻 de-energize,然後觀察整套系統——從機械電氣設施、到機櫃、到 compute 與 storage——能不能在電力瞬間消失時安全落地、再從零乾淨地開回來。

文章沒有揭露伺服器型號、電容容量或 hold-up 的毫秒數,所以下面凡涉及時間尺度與能量預算的細節都標記為「工程推理」。但它把兩個最值錢的東西講得很清楚:第一,為什麼模擬不夠、非得真的拔電;第二,真的拔電之後,恢復供電那一刻暴露的故障比斷電那一刻更難對付。這兩件事構成一條清楚的時間線,下面就按它走。

為什麼「真的拔電」非得排進測試項目

先界定「瞬時斷電就緒度」在驗證什麼。資料中心當然有 UPS、備援電源、發電機;正常斷電時這些裝置會撐住負載、把市電切到發電機,伺服器根本感覺不到。真正可怕的不是這種「有人接手」的斷電,而是瞬時、全域、無人接手的那種——保護機制本身也跟著掉、整個 region 在同一個時間切片裡失去電力。這正是 Meta 注入的場景:「a power supply fault being injected to cause immediate de-energization of the entire region」。

為什麼模擬不夠?因為這類故障的殺傷力來自「未知的未知(unknown unknowns)」。任何模擬都只驗證你已想到的失敗;真實系統裡致命的,恰恰是不在任何人模型裡的隱性依賴——一個被遺忘的硬編 IP、一個假設 DNS 永遠可達的健康檢查、一個在冷啟時把後端重試到打垮的指數退避。這些只在物理電力真的消失、所有元件以各自真實時序反應時才同時現形。Meta 自己說這是「chicken-and-egg problem of needing to take risk to address risk」——要消除風險,得先承擔風險。

這裡有個尺度上的關鍵數字:被測 region 是「50-60x the size of the typical fault domains」。平常的故障注入作用範圍是一個 fault domain——一排機櫃、一個 cell、一組副本。把整個 region 一次斷掉,等於把 50 到 60 個故障域同時點著。這個尺度下,平常「另一個故障域會接手」的假設全部失效:沒有別的 region 分流、沒有健康副本路由、沒有一個還活著的控制平面協調恢復。就緒度要驗證的,正是「當這些假設全部不成立時,系統能不能自己爬起來」。

「就緒度」其實有兩個彼此獨立卻常被混為一談的層面。硬體層是機電設施、配電、機櫃供電、儲能裝置承受瞬時 de-energization 與隨後 re-energization 的衝擊時能不能不壞、不起火、不留下永久損傷;軟體層是當所有機器同時冷啟,控制平面、服務發現、排程、儲存的恢復邏輯能不能把歸零的 region 重新組裝回可服務的系統。Meta 把這兩層綁在同一個動作裡驗證,因為真實災難不會好心地只考你一層——而文章後半暴露的故障幾乎全落在第二層:硬體扛得住,軟體的隱性依賴卻沒有。

這也回答了「為什麼非得對整個 region、而不是先放大一排機櫃」。有一整類故障的觸發條件就是「規模本身」:一排機櫃斷電時,控制平面在別處好端端地跑著、隨時可協調恢復;只有當控制平面自己也在斷電範圍內,「誰來協調恢復」才變成真的問題。小規模測試在定義上就驗證不了「協調者也死了」這個最關鍵的場景——這是 region 尺度不可繞過的根本原因。

斷電瞬間本身相對「乾淨」——電消失,東西停下。真正的工程難點分布在兩個時刻:拔電當下(in-memory 資料來不來得及落地)、與恢復供電當下(一個沒有任何東西在跑的 region 怎麼從零 bootstrap)。先看拔電那一刻。

拔電那一刻——hold-up 能量、PLS、與 in-memory 的最後窗口

當市電瞬間消失,機櫃並不是立刻歸零。Meta 提到兩個關鍵裝置:「batteries and Power Loss Siren (PLS)」,讓機櫃可以「persist in-memory data when racks have lost power」。斷電不是離散事件,而是一段有預算的倒數:電池(這個語境下更像機櫃級的 BBU 或 hold-up 儲能)提供一小段續命時間,PLS——斷電警報器——則是在電力消失的第一時間廣播「準備落地」的訊號源。

這段窗口要做的事,是把還停在記憶體裡、尚未安全寫入持久層的髒資料(dirty pages、未 flush 的 write buffer、in-flight 交易)盡可能落地——但它是有限的能量預算,誰先做、做多少、來不來得及,是一個排程問題。下面這個 canvas 把這段倒數攤開:電軌電壓在斷電後衰減,PLS 在 t0 觸發,續命能量像一條燃燒的預算條往下掉,幾個關鍵動作各需時間才能完成;拖動 hold-up 預算,看哪些動作能在電歸零前完成、哪些被截斷。(時間尺度與能量曲線為工程推理下的示意,非 Meta 公布數字。)

play 看續命窗口倒數 · 拖 slider 調 hold-up 能量預算 · 3 個落地動作賽跑

budget = 120u · t = 0
斷電後的續命窗口(示意,非 Meta 實測值)。上半是電軌電壓衰減曲線與 PLS 觸發點,下半三條工作棒是 flush 髒記憶體、fsync 落盤、quiesce 網路——綠色表示在電歸零前完成,紅色表示被截斷。

斷電後的續命窗口(示意,非 Meta 實測值)

斷電後 PLS 觸發倒數;flush 髒記憶體、fsync、quiesce 網路三者搶 hold-up 能量,預算不夠就被截斷。

canvas 裡那三條工作棒的順序並非任意。flush 髒記憶體排最前,因為記憶體裡的東西一旦斷電就徹底消失、無可補救;fsync 落盤次之,把寫到 page cache 但還沒落到持久媒介的東西推下去;quiesce 網路最後,因為網路狀態多半可在恢復後重建。這個排序本身就是風險工程——能量預算有限時先保最不可逆的東西。把 hold-up 預算往下拖,會看到系統按這個優先級「丟車保帥」:先犧牲可重建的網路 quiesce,再犧牲落盤,最後連最關鍵的記憶體 flush 都來不及——那一刻 table-stake 的資料遺失紅線就被觸碰了。

一個值得停下來的細節:續命窗口不是用來「繼續服務」的,是用來「乾淨地停下」的。電池與 PLS 買到的時間,目標不是讓查詢繼續跑,而是讓所有「進行一半」的狀態抵達一個可安全恢復的點——交易要麼提交、要麼回滾,write buffer 要麼落盤、要麼丟棄但有跡可循。Meta 把「data loss in storage/databases」列為 table-stake 第一條,續命窗口存在的全部理由就是守住它。

所以拔電當下暴露的故障,本質上都是「落地賽跑輸了」的變體:某服務 flush 比預期慢、某儲存層在收到斷電訊號到真正停止寫入之間有一段沒算進預算的延遲、某元件根本沒訂閱 PLS 所以不知道要落地。這些在模擬裡看不出來——模擬的「斷電」是程式碼裡一個乾淨的事件,真實世界裡斷電是物理過程,每個元件感知、反應它的時序都不一樣。但拔電那一刻再難,也是「一次性、單向」的:東西停下就停下了。真正讓 Meta 反覆踩雷的是另一個時刻——把電接回來。

開回來才是真考驗——Ouroboros 與 Boomerang

一個被瞬間斷電的 region,恢復供電時面對的是一個極端狀態:什麼都沒在跑。沒有控制平面、沒有排程器、沒有服務發現、沒有路由表——全是冷的。Meta 用來編排容器的是 Twine,控制平面包含 Scheduler、Allocator、Broker,以及一個叫 Zelos 的協調器。正常運作下這些服務彼此依賴、互相提供能力;但在「全冷啟動」時,這種互相依賴會變成致命的死結。

hover/tap 任一名詞看它在這套機制裡負責什麼 · Twine 控制平面與斷電訊號的角色表

TwineMeta 的容器編排系統,把工作負載放上機器並管理生命週期。控制平面由下面幾個服務組成。 Scheduler決定哪個工作負載放到哪台機器——容器放置的決策層。冷啟時得在沒有完整叢集視圖下重建這些決策。 Allocator把機器資源(CPU、記憶體、容量)配給排程出來的工作負載,是 Scheduler 決策的落地執行者。 Broker控制平面與被管理工作負載之間的中介層,傳遞指令與狀態。它既發協調訊號、也在全斷時被同一訊號波及。 Zelos控制平面裡的協調器,讓上述服務在分散環境下達成一致。它的存活直接決定 region 能否被重新組裝。 PLSPower Loss Siren,斷電警報器。在電力消失的第一時間廣播訊號,讓機櫃啟動把 in-memory 資料落地的續命程序。 UEunavailability events,region 範圍的非同步訊號,標記「某物不可用」並編排關機與恢復。Boomerang 正源於它的作用域在全斷時失控膨脹。

這個「全是冷的」狀態,是任何長年運行的線上系統幾乎從不經歷的。控制平面服務通常被一個個重啟,很少全部同時死、再全部同時從空白起;久而久之「啟動順序」的正確性就沒人持續驗證。隱性依賴於是悄悄累積:某服務在 init 時直接去讀另一服務的 endpoint、某元件假設配置中心已可達。這些假設在熱系統裡永遠成立、永遠不報錯,是一種只在「全冷啟動」邊界條件下才會引爆的技術債。

第一類死結,Meta 借了神話意象命名:Ouroboros,咬住自己尾巴的蛇。控制平面服務啟動時需要另一個控制平面服務已經在跑,而那個又需要第一個——「control plane services cannot start without themselves」。熱系統裡這從不是問題,因為它們一直都在;只有把一切歸零、要求它們從虛空中同時長出來時,循環依賴才會把整個 bootstrap 卡死。Meta 的解法是用 Belljar CI 把循環依賴在上線前就抓出來,再配一套「Twine recovery kit」在恢復時主動「break any circular dependencies」——給冷啟動一條不依賴完整控制平面的引導路徑。

這套解法跟作業系統的 bootstrap 同構。機器開機時,CPU 不可能等「完整的 OS 已經在跑」才開始——它先跑一段最小、不依賴任何高層服務的引導程式(bootloader)把核心拉起來,再逐層喚醒其餘子系統。recovery kit 扮演的就是這個 bootloader:提供一條不依賴完整控制平面的最小引導路徑,把「先有雞還是先有蛋」的循環用一顆人工放進去的蛋打破。Belljar CI 則把這件事左移——讓循環依賴在 commit 階段就被抓到,而非等到某次真實斷電才在 region 規模上爆出來。下面這張圖把依賴環、以及 recovery kit 在哪裡剪斷它畫出來——點任一節點看它在環裡的位置。

click 任一節點看它在依賴環裡的位置 · recovery kit 在哪裡剪斷循環

Ouroboros——控制平面服務無法在沒有自己的情況下啟動 控制平面服務 A 啟動前要先讀 B 控制平面服務 B 啟動前要先讀 A Twine recovery kit 注入人工的蛋 · bootloader A 等 B B 等 A · 死結 recovery kit 先給 A、B 一個最小可用態,剪斷互等
控制平面服務 A:熱系統裡 A 早就在跑,「啟動 A 要先有 B」從不被執行。只有全冷啟動時,A 第一次真的得在「B 還不存在」時啟動——隱性依賴才從假設變成阻塞。
控制平面服務 B:與 A 對稱。B 啟動時去讀 A 的 endpoint,而 A 此刻也正卡在等 B。兩者互為前置,誰都先動不了——這就是 Belljar CI 要在 commit 階段用依賴圖抓出來的循環。
Twine recovery kit(注入的蛋):只在恢復時登場——像 bootloader 一樣,先給 A 與 B 各一個不依賴對方的最小可用態,讓其中一個先站起來,循環就被從外部剪斷。這條路徑必須獨立於完整控制平面,否則它自己也會掉進同一個環。

互動圖表

Ouroboros:服務 A 等 B、B 等 A 形成死結;recovery kit 像 bootloader 注入最小可用態剪斷循環。

第二類更微妙,叫 Boomerang effect:「the generator of a critical signal being impacted by the same signal」。Meta 用一種 region 範圍的非同步訊號——「unavailability events (UE)」——協調整個 region 的關機與恢復。問題是,發出這些 UE 的正是 Twine 控制平面服務本身;而 UE 在傳播時,「ended up shutting down the orchestrator control plane services themselves」。協調者發訊號去關別人,訊號繞一圈回來把協調者自己關掉了——迴力鏢。

Boomerang 之所以難,是它在熱系統的任何測試裡都不會出現。平常 UE 是標記「某物不可用」的局部訊號,作用域小、協調者永遠在訊號的作用域之外;只有當斷電讓「整個 region」都進入 UE 的作用域,協調者才第一次落進自己廣播的訊號半徑裡。下面這張圖把這個迴圈畫出來——點任一節點看它在迴圈裡的角色。

click 任一節點看它在迴圈裡的角色 · 3 個環節 · UE 訊號的自噬路徑

Boomerang——協調者落進了自己廣播的訊號半徑 Twine 控制平面 Scheduler · Allocator Broker · Zelos UE 的產生者 UE 訊號匯流排 region-wide async 關機目標服務 原本預期的作用域 ① 發出 UE 編排關機 ② 預期:只打到目標 ③ 全 region 斷電時,UE 也打回協調者自己 熱系統裡協調者在作用域外;全斷時它落進了作用域內
Twine 控制平面(UE 的產生者):Scheduler、Allocator、Broker、Zelos 協調整個 region 的容器放置與生命週期。它們既是「發出關機 UE 的人」,也在全斷時變成「被關機 UE 波及的人」——Ouroboros 與 Boomerang 都源自這個雙重身分。
UE 訊號匯流排(region-wide async):unavailability events 非同步、跨 region 廣播。設計時假設它的作用域是局部的——標記「某物不可用」讓人繞開。這個非同步性正是 Boomerang 的溫床:訊號發出後協調者無法保證自己不在接收端。
關機目標服務(原本預期的作用域):UE 本來只該打到這裡。但「作用域只含目標、不含協調者」只在局部故障時成立;當斷電讓整個 region 都進入「不可用」,目標集合膨脹到把協調者也吞了進去。

互動圖表

Boomerang:控制平面發出的 UE 訊號在全 region 斷電時繞回來把協調者自己關掉。

Ouroboros 與 Boomerang 其實是同一結構缺陷的兩面:控制平面既是協調的主體、又是被協調的客體。Ouroboros 是這個雙重身分在啟動時的表現(要啟動 A 得先有 A),Boomerang 是它在關機編排時的表現(A 發出的關機令把 A 自己關了)。兩者都只在「全域同時歸零」時才浮現——這正是為什麼非得真的拔整個 region 的電。

// 為什麼只有「全 region 斷電」才暴露 Boomerang
def emit_unavailability(scope):
    targets = services_in(scope)
    for s in targets:
        send_UE(s)        // 預期:orchestrator 不在 targets 裡

// 局部故障:scope = 一排機櫃 / 一個 cell
//   orchestrator 在 scope 之外 → 安全
emit_unavailability(scope = one_fault_domain)

// 全 region 斷電:scope = 整個 region(50-60 個故障域)
//   orchestrator 也在 scope 之內 → UE 繞回來把它自己關掉
emit_unavailability(scope = entire_region)   # Boomerang

Boomerang 的修法文章沒展開,但從結構可推出形狀(工程推理):要讓協調者不被自己發的 UE 關掉,要麼把它排除在 UE 作用域外、給一個「免疫」標記,要麼讓恢復路徑上的協調者改走不經過 UE 匯流排的帶外(out-of-band)通道自我引導。兩種做法都是把「協調控制平面」與「被協調的資料平面」在訊號層面解耦——讓協調者站回訊號半徑之外。這跟 Ouroboros 的修法是同一思路的兩種投影:在恢復這個特殊階段,協調者必須有一條不把自己捲進去的獨立通道。下面這張表把兩類死結並排——點欄位標頭可依該欄重排。

click 欄位標頭依該欄重排 · 兩類復電死結的觸發、機制與修法並排

失敗類別 出現階段 觸發條件 核心機制 修法形狀
Ouroboros 冷啟動 全域歸零、控制平面要從虛空同時長出 A 要先有 B、B 又要先有 A 的循環依賴卡住 bootstrap Belljar CI 提前抓循環+recovery kit 注入最小引導路徑
Boomerang 關機編排 斷電讓整個 region 落進 UE 作用域 協調者發出的 UE 繞回來把它自己關掉,訊號自噬 協調者排除在 UE 作用域外,或改走帶外通道引導

互動圖表

Ouroboros 卡在冷啟動、Boomerang 卡在關機編排;兩者都只在全 region 歸零時才浮現。

這類「保護機制反噬」並非 Meta 獨有,而是大規模系統的通病。電網就有同構現象——保護電驛為隔離故障而跳脫,連鎖跳脫卻把更大範圍一起拖黑(2003 年北美大停電即經典案例)。軟體層的 Boomerang 是同一範式的化身:一個本意「縮小爆炸半徑」的訊號,在規模夠大時反成了擴大爆炸半徑的媒介。它告訴你該下功夫的地方——不是讓保護機制更敏感,而是確保它的觸發不會把自己也包含進去。

恢復供電當下還有一層 Meta 點到但沒展開的事:MTTR(mean time to recovery)。文章說這次測試觀察到的 MTTR「mirrored typical MTTR seen during real incident scenarios」——刻意製造的斷電,恢復時間跟真實事故差不多。這既證明測試逼真到接近真實災難,也意味著「從零 bootstrap 一個 region」本來就慢,慢本身就是要持續壓縮的指標。下面這條時間線把驗證計劃從第一次小規模拔電、到對 production region 拔電的推進攤開。

drag 把手沿時間線移動 · 4 個推進階段 · 從 pre-prod 拔到 production region

階段 1
拖把手以檢視每個推進階段的風險邊界與目標。

互動圖表

驗證沿 pre-prod→shadow→首次 production→大型 production 四階段推進,每級修好死結再往上爬。

從一次性實驗變成常態:表決定範圍,而非僥倖

把「對整個 region 拔電」變成可重複的測試項目,靠的不是膽量,而是把什麼後果可接受、什麼絕對不行事先寫死。Meta 把失敗分成兩堆:一堆是 table-stake——絕對不能發生,發生了就是測試失敗;另一堆是可接受的代價——它們會發生、也被預期發生,只要落在預先界定的門檻內就算過關。這個劃分是整個計劃能跑起來的前提:沒有它,「真的拔電」就只是一次無法評分的賭博。下面這組卡片把紅線兩側並排。

click 任一項看它落在紅線哪一側 · 3 條 table-stake + 3 項可接受代價

紅線兩側——哪些是 table-stake,哪些是可接受代價

TABLE-STAKE · 觸碰即失敗 可接受代價 · 門檻內過關 儲存/資料庫的資料遺失 續命窗口守的就是這條紅線 資料中心設施永久損壞 機電不得因瞬態實體受損 影響溢出到 region 之外 爆炸半徑須框在單一 region 瞬時服務錯誤 恢復後自行收斂 機櫃故障(門檻內) predefined threshold 內可容忍 routing 表 bounded staleness 短暫過時、最終收斂
資料遺失(table-stake):第一條紅線。續命窗口、PLS、in-memory 落地的整套機制都是為了讓它不被觸碰;一旦真的遺失,這次測試就不是「學到東西」而是「造成事故」。
設施永久損壞(table-stake):機電設施承受瞬時 de-energization 與隨後 re-energization 的衝擊時不得有任何永久性實體損傷。這也是為什麼第一波測試從 pre-prod 開始——先確認硬體扛得住。
跨 region 擴散(table-stake):整個驗證的安全假設是「爆炸半徑等於一個 region」。若斷電把鄰近 region 也拖下水,這個測試本身就成了跨區事故——正是要事先擋死的。
瞬時服務錯誤(可接受):斷電期間請求失敗、復電初期錯誤率升高都被預期會發生。判準不是「零錯誤」,而是「恢復後錯誤自行消退」——這才讓「不做事前優雅排乾」的逼真前提成立。
機櫃故障(可接受,有門檻):復電時部分機櫃起不來是正常的,只要落在 predefined threshold 內。關鍵是「有界」:超過門檻就翻轉成失敗訊號。
routing 表 bounded staleness(可接受):路由表在恢復期間會短暫過時,被明確列為可接受。重點同樣是「有界」——過時是暫時的、最終會收斂。

互動圖表

table-stake 三條:資料遺失、設施損壞、跨 region 擴散;可接受代價:瞬時錯誤、門檻內機櫃故障、routing 過時。

這張紅線表的工程價值遠超出這一次測試。多數團隊的 chaos experiment 失敗在同一步:注入故障後憑感覺判斷「還行吧」。Meta 把判準前置成兩堆明確的桶,每一次拔電就產生一個二元的、可記錄的結論,而不是一段模糊的體感。而把它變成常態的最後一塊,是「incremental」:Meta 不是某天直接對最大的 production region 拔電,而是沿 pre-prod → shadow → production 的階梯往上爬,每一級都先把上一級暴露的死結(Ouroboros、Boomerang)修掉、把 Belljar CI 與 recovery kit 補強,再往高風險的下一級推。這正是對「chicken-and-egg:要承擔風險才能消除風險」的回應——不是一次性吞下全部風險,而是把它切成可逐級消化的份量。

把瞬時錯誤、門檻內機櫃故障、bounded staleness 劃進可接受,並不等於它們不重要——而是它們被刻意換成了「能換到逼真度」的籌碼。如果為了讓測試零干擾而事先把流量遷走、把寫入排乾,那這場斷電就不再逼真,暴露不出真正的故障模式。Meta 選擇承擔有界的瞬時損害,去買「測試結果等同真實災難」的可信度。這個取捨對任何做生產環境混沌工程的團隊都是核心問題:你願意付多少真實代價,去換一個真實的答案——而把「可接受」這一側量化成有門檻的桶,正是為了確保這筆交易不會在某次測試裡悄悄超支。

MTTR 與真實事故相當,最大的價值是把這個指標從「事後統計」變成「可實驗的變數」。你不能為了量測恢復時間而製造事故,但你可以隨時重做這場斷電——於是每一次拔電都是一個量測 region 從零恢復要花多久的機會,把恢復路徑裡的瓶頸(哪個服務冷啟最慢、哪段引導序列化得不必要)逐次找出來壓縮,MTTR 就能從「跟真實事故一樣慢」往「比真實事故快」推進。

文章結尾留了一個鉤子:下一步是「validating regions with live client traffic against instantaneous failures」——目前這套驗證雖對 production region 拔了電,但還沒在「同時承載真實客戶請求」的條件下做。帶上 live traffic 後,續命窗口要守的就不只是靜態 in-memory 資料,還有 in-flight 的客戶請求,而 Boomerang 之外可能還有與真實流量耦合的新故障模式等著被逼出來。Meta 說會用「同樣的 incremental 策略」推進——這句話本身,就是這篇文章最該被記住的方法論。

The lesson:就緒度不是寫在 runbook 裡的承諾,而是要真的拔一次電才知道有沒有的事實——前提是你得先把「什麼後果絕對不行、什麼後果可接受」量化到能當場評分,再把這場高風險實驗切成 pre-prod 到 production 的階梯逐級吞下。