題幹與適用場景
一個 PostgreSQL 主庫透過邏輯複製把訂單變更傳給資料倉庫與搜尋服務。主庫可能在複製消費者落後時故障。請設計計畫內與計畫外故障切換,說明如何確認備用庫上的邏輯複製槽可接管,如何避免重複或漏掉 LSN 區間,以及無法證明槽連續時如何恢復。
這道題適合後端、資料庫平台、資料基礎設施與 SRE 面試。題目要求區分 PostgreSQL 訂閱者與 Debezium 等非 PostgreSQL 消費者:前者可以使用內建 failover 選項,後者仍需在連接器、槽與對帳層建立自己的連續性證據。不要把「備用庫已同步」直接等同於「所有邏輯消費者都能無縫繼續」。
面試官考察點
強回答會先把安全目標寫成可驗證不變量:新主庫的邏輯流不能回退;消費者從已確認位置繼續,允許重複但不能靜默跳過;切換前必須知道備用庫的槽同步狀態與消費進度。候選人還應說明計畫內切換可以先停寫、排空消費者並檢查 LSN,計畫外故障則可能只能透過備份、全量快照與對帳修復。只說「把 DNS 指到 standby」沒有涵蓋複製槽、WAL 保留和非 PostgreSQL 消費者。
回答前需要澄清的問題
- 消費者是什麼? PostgreSQL subscriber、Debezium、資料倉庫匯入器與自研解碼器的進度與恢復介面不同。
- 允許重複嗎? 通常至少一次交付可以接受,目標寫入必須按 LSN、交易 ID 或冪等鍵去重;「完全一次」不能靠切換腳本口頭保證。
- 切換是計畫內還是災難復原? 計畫內可以暫停寫入並等待備用庫追平;主庫已不可用時,可能無法知道最後一個已提交且已被消費者確認的 LSN。
- 需要保留跨表交易邊界嗎? 如果下游要求訂單與訂單明細原子可見,要傳播交易邊界,而不是只比較單行 LSN。
- WAL 能保留多久? slot 落後會阻止 WAL 回收,既可能拖滿主庫磁碟,也可能在 slot 失效後造成不可恢復的間隙。
- 能否接受從快照重建? 若不能證明槽連續,必須預先準備全量快照、版本對帳和停機或降級方案。
30 秒回答框架
「我先把不變量定為:新主庫的複製槽位置不低於消費者已確認位置,恢復後 LSN 單調推進;重複事件可以重播,但不能靜默跳過。計畫內切換時暫停或限制寫入,確認 failover 槽已同步到備用庫、備用庫已領先消費者,再停連接器、記錄最後安全 LSN、提升 standby 並恢復消費者。
PostgreSQL 17 的邏輯複製 failover 支援將 failover 槽非同步同步到 standby,但切換前必須檢查槽存在、已同步、非臨時且沒有失效原因。Debezium 等非 PostgreSQL 消費者還要獨立驗證自己的 slot 和 LSN。計畫外故障若無法證明連續性,就不能建立新槽假裝接續;應從可靠備份或快照重建,並按主鍵、交易和 LSN 對帳。」
分步驟深入解答
第一步:建立槽、LSN 與消費者進度模型
邏輯複製槽代表一個可以按來源順序重播的變化流。restartlsn 表示仍需保留的最早 WAL 位置,消費者確認的進度通常透過 confirmedflush_lsn 或連接器自己的 offset 表示。兩者含義不同:前者決定 WAL 能否回收,後者表示消費者已處理到哪裡。
每個獨立消費者應使用獨立槽,或透過明確廣播層分發。多個消費者爭搶一個單次消費槽會讓未獲訊息的消費者靜默缺資料。監控槽的 WAL 保留量、確認位置、讀取延遲、最舊未處理交易與磁碟餘量;不能只看應用佇列深度。
第二步:計畫內切換先製造可證明的安全點
計畫內切換可以讓寫入進入短暫唯讀或排空窗口。先確認所有消費者的最後安全 offset,等待 standby 的實體重播位置覆蓋這些槽狀態,再檢查 failover 槽在 standby 上可用。PostgreSQL 文件要求確認槽已同步;failover_ready 需要同時滿足槽已同步、不是暫時槽且沒有 invalidation reason。
freeze_or_throttle_writes()
stop_consumers_after_recording_offsets()
wait_until(standby_replay_lsn >= required_slot_positions)
assert all(required_slots on standby are synced and valid)
promote(standby)
verify_slot_positions_are_monotonic()
restart_consumers_from_last_safe_offsets()
reconcile_sampled_rows_and_transactions()如果是 PostgreSQL subscriber,可以使用訂閱或槽的 failover 設定;如果是 Debezium,必須保存 connector offset、確認新主庫有對應槽,並驗證它能從同一 LSN 繼續。切換腳本的成功碼不能取代這些狀態檢查。
第三步:處理計畫外故障與重複投遞
主庫突然消失時,備用庫可能只複製到較早的 WAL;消費者也可能已收到事件但尚未持久化自己的 offset。安全策略是允許重播一小段重疊區間,並讓下游按來源 LSN、交易 ID 或業務冪等鍵去重。新主庫上的槽必須來自已同步狀態;沒有連續證據時,不能從「目前 WAL」新建槽繼續,因為中間 LSN 可能已被丟棄。
如果舊主庫後來恢復,不能讓它與新主庫同時接受寫入。先隔離舊主庫,確定時間線與權威主庫,再重新建立實體或邏輯複製。任何重新附著都必須從一致性快照或明確日誌位置開始,並對帳期間的交易、刪除與跨表邊界。
第四步:槽失效、WAL 堆積與補救
槽長期落後會保留大量 WAL,威脅主庫磁碟與交易 ID 安全。處置順序應先保護主庫,再判斷是否仍能從槽恢復:暫停非關鍵消費者、限制寫入或擴大臨時儲存都必須有明確預算。若槽被刪除、失效或缺少所需 LSN,建立同名新槽也不能補回歷史。
此時從最近一致性備份或全量快照重建消費者,記錄快照對應位置,隨後消費該位置之後的增量。恢復完成後按主鍵版本、交易 ID、刪除墓碑與計數範圍對帳;對帳未通過就保持降級,不把「消費者已連線」當成資料完整。
第五步:用演練與指標驗證故障切換
演練至少覆蓋:計畫內停寫切換、消費者落後、槽同步延遲、主庫突然斷電、重複訊息、舊主庫誤回流、槽失效、Schema 變更與大交易。每次記錄主庫與 standby 的時間線、槽狀態、restartlsn、confirmedflush_lsn、消費者 offset、交易邊界與業務計數。
驗收指標包括 LSN 是否單調、重複率、漏事件數、最舊未處理交易年齡、WAL 保留量、切換 RTO/RPO、消費者恢復時間,以及資料庫和下游按主鍵抽樣的版本差。故障注入後的最小不一致樣本應能從保存的備份或日誌位置重播,說明修復路徑真實可用。
高品質示範回答
「我會先定義連續性不變量:新主庫上的槽必須覆蓋消費者最後安全位置,恢復後來源端 LSN 單調增加;允許重複,但不能靜默跳過。每個非 PostgreSQL 消費者使用獨立槽,並把 connector offset、slot 狀態與業務對帳作為一組證據。
計畫內切換時,我先暫停或限速寫入,記錄所有消費者 offset,等待 standby 實體重播覆蓋槽狀態,再確認 failover 槽已同步、非臨時且沒有失效原因。停止連接器後提升 standby,驗證槽位置單調,再從最後安全 offset 恢復。PostgreSQL subscriber 可以使用內建 failover 選項;Debezium 等消費者必須獨立確認新主庫槽和 LSN。
計畫外故障允許一個可控重疊區間,目標按 LSN 或交易 ID 冪等。若無法證明槽連續,我不會建立新槽假裝接著讀,而是從一致性備份或全量快照重建,補上快照後的增量,並做主鍵、刪除和交易邊界對帳。演練中注入槽同步延遲、重複、舊主庫回流和大交易,指標同時看 WAL 保留、切換 RPO、重複/漏事件和下游版本差。」
常見錯誤
- 只切 DNS 或連線字串 → 邏輯槽和消費者 offset 不會自動連續 → 把槽狀態、LSN 與消費者進度作為切換門禁。
- 把 standby 實體同步等同於邏輯槽已就緒 → 槽同步是非同步的,可能落後消費者 → 檢查每個槽的 synced、valid 與 replay 位置。
- 多個消費者共用一個槽 → 單次消費會讓其他消費者靜默缺事件 → 每個獨立消費者使用獨立槽或廣播層。
- 新主庫直接建立新槽 → 已發生的 LSN 間隙無法補回 → 連續性未知時從快照重建並對帳。
- 把 confirmedflushlsn 當作 restart_lsn → 前者是消費進度,後者影響 WAL 回收 → 分別監控和解釋兩個位置。
- 把 exactly-once 寫進切換目標 → 崩潰窗口仍會產生重複 → 用至少一次加冪等副作用,並量化重複率。
- 舊主庫恢復後立即接回寫流 → 雙主會產生分叉與不可合併 LSN → 先隔離、確定權威主庫,再從新時間線重建。
- 只演練主庫可用性 → 槽失效、長交易與大 WAL 才會暴露資料風險 → 注入消費者落後、槽堆積和大交易。
- 看到消費者已連線就宣布恢復 → 連線成功不證明沒有漏行或漏刪 → 按主鍵、交易邊界與業務計數對帳。
追問及應對
追問一:計畫內切換一定要停寫嗎?
不一定,但不停寫會增加需要證明的窗口。若資料庫和槽同步機制能證明 standby 已覆蓋所有消費者需要的位置,可以縮短唯讀窗口;否則寧可暫時限寫,也不要用「通常很快」取代 LSN 門禁。停寫時仍要等待已提交交易完成並記錄邊界。
追問二:非 PostgreSQL 消費者如何判斷新槽真的連續?
保存 connector offset 與來源 LSN,切換前確認 standby 槽已同步到不低於該位置,切換後讀取新槽起點並做單調性檢查。若 offset 只保存訊息時間而沒有 LSN,連續性證據不足,應按快照重建或補充來源位置中繼資料。
追問三:大交易跨故障切換怎麼辦?
要區分交易已提交、未提交與已部分解碼狀態。下游若要求交易原子性,應傳播 BEGIN/COMMIT 邊界,並在恢復後丟棄未完成交易片段;若只要求行級最終一致,要聲明切換期間的中間可見性和重播規則。不能按單行到達順序推斷完整交易。
追問四:WAL 已經堆滿磁碟,能否直接刪除 slot?
只能在確認該消費者不再需要歷史,並已準備快照重建後刪除。直接刪除 slot 會釋放空間,卻也永久放棄它尚未讀取的變化。刪除前保存位置、備份和對帳計畫,恢復後驗證從新快照到目前來源位置沒有缺口。