本文整理自 IvorySQL 2025 生態大會暨 PostgreSQL 高峯論壇的演講分享,演講嘉賓:張晨,公眾號《ZhangChen-PDU》主理人。
前言
在數據庫運維中,災難恢復始終是保障業務連續性和系統可靠性的核心環節。隨着數據庫規模和複雜性的增加,傳統工具在極端場景下的侷限性愈發明顯,因此需要更專業、高效的解決方案來應對數據損壞或不可啓動的情況。
PDU 的快速介紹
在數據庫運維場景中,災難恢復一直是考驗系統可靠性與底層機制理解的重要環節。對於 Oracle 等商業數據庫來説,已有成熟的內部工具如 DUL 可用於在數據庫無法啓動時直接從數據文件中提取數據。而在 PostgreSQL 生態中,長期缺乏類似的工具,這也正是 PDU 誕生的初衷。
PDU 是什麼
PDU(PostgreSQL Data Unloader)是專門用於 PostgreSQL 係數據庫的災難恢復工具,旨在解決數據庫無法正常啓動、單表文件損壞或歸檔丟失等極端情況下的數據恢復問題。其核心功能主要包括:
- 從 wal 中恢復 delete/update 的原數據。
- 數據庫無法啓動時直接從數據文件中提取數據。
- 支持單表/整庫/自定義數據文件級別的恢復。
- 提供事務級/時間區間級數據恢復。
為什麼需要 PDU
在災難恢復場景下,主流數據庫的能力存在明顯差異。
- Oracle 擁有成熟工具體系,如官方 DUL 與國內 ODU,可在數據庫無法啓動時直接從數據文件中恢復數據並生成可導入的 dump 文件。
- PostgreSQL 的 pg_filedump 工具功能有限,僅能導出已知表結構的單個數據文件,缺乏定製化能力,遇到複雜問題難以應對,且對生產環境適配不足,例如僅通過 xmax != 0 判定元組狀態,準確性有限。
- PG 系國產數據庫 在災備工具方面幾乎空白,多數數據庫修改了底層數據文件結構,pg_filedump 無法兼容,且缺乏專業恢復工具,在災難場景下常束手無策。
基於這一現狀,PDU 的開發旨在填補 PG 係數據庫在災難恢復中的空白。
關於 pg_filedump 更詳細的技術解析可參考👉作者往期文章
PDU 如何使用
PDU 的使用非常簡便,其軟件組成僅包含兩個部分:可執行文件與配置文件。使用步驟如下:
第一步:填入數據目錄與歸檔目錄
第二步:進入 pdu 可執行文件
第三步:通過簡化命令 b 執行自動初始化
第四步:自由操作
以下為演示圖片:
PDU 程序崩潰瞭如何反饋
PDU 採用 C 語言開發,能夠直接複用 PostgreSQL 內核的數據結構與函數,從而提高數據解析的效率與兼容性。在程序運行過程中,如果出現核心轉儲或者崩潰(core dumped),用户可通過操作系統配置生成 core 文件 ulimit -c unlimited,將 core 文件提供給開發者以復現問題並進行排查。
數據字典的快速初始化
tuple 數據讀取基本原理
在數據庫無法啓動的情況下,PDU 仍能完成數據字典的初始化,這一過程基於對 tuple 數據讀取方式的分析與實現。
在 PostgreSQL 中,每個數據頁(Page)開頭的頁頭(Page Header)存儲了頁的基本信息。頁頭中各字段都有固定的字節長度與偏移量,例如 pd_lower 佔 2 個字節,表示空閒空間的起始位置,即最後一個行指針(Line Pointer)的結束地址。在數據文件中,每行顯示 8 個字節,通過偏移量即可確定各結構的具體位置。
數據頁的核心構成
數據頁主要由兩部分組成:目錄(Line Pointer 數組) 和 數據區域(Tuple Data)。目錄用於記錄每條元組在頁內的偏移量、標誌位及長度信息,每個行指針佔 4 個字節,用於定位對應的數據記錄。
獲取到單條 ltemld 之後,在偏移量 lp_off 獲取 tuple。
從 tuple 中定位數據區域
當定位到具體元組後,可通過行指針獲取其頭部信息(Tuple Header)。其中,t_hoff 表示元組頭長度,用於計算實際數據的起始位置;lp_len 表示元組總長度。由於 PostgreSQL 的數據存儲中列與列之間不存在分隔符,因此在解析時必須嚴格按照數據類型定義逐字節讀取,確保解析出的長度與目錄記錄一致,才能確認數據讀取正確。
5 張基表
聚焦於系統在解析存儲文件中 5 張基表 時的處理機制。通過讀取並解析這些基表中的 tuple,系統能夠還原數據庫對象的定義信息,包括表結構、字段屬性以及對象間的依賴關係。該過程為數據字典的初始化提供了底層支撐,是數據庫啓動階段恢復核心元數據的關鍵步驟。
- 從 global/1262 中獲取數據庫的 oid 得到對應目錄。
- 從“數據庫 oid/2615”“數據庫 oid/1259”“數據庫 oid/1249”“數據庫 oid/1247”5 張基表中獲取整庫的所有記錄。
- 按照上圖的對應關係拼湊出每張表的所有信息。
PDU 數據字典最終形態
PDU 在獲取數據字典後,會將解析結果存儲在可執行目錄下的 meta 原數據目錄中。系統會為每個數據庫生成對應的 \_tables.txt 文件,例如檢測到數據庫 alldb,則會在 meta 下生成 clothes_space_tables.txt 文件。
文件中每一行代表一張表,包含表 ID、文件號(默認一致)、TOAST ID 及文件號、模式 ID、表名、列名、列類型名、列數量與類型長度等。最後兩列為列長度和類型對齊方式,其中列長度為 -1 表示變長類型(如 varchar、numeric),定長類型(如 int、float)則對應具體字節長度。對齊方式用於控制字節排列,以保障解析與計算的準確性。
下圖為數據字典最終形態:
踩坑指南
在 PostgreSQL 數據文件和 TOAST 文件的脱離數據庫讀取過程中,有幾類情況需要格外注意,以避免解析錯誤或數據丟失。
- drop 列需在初始化與數據解析階段進行特殊處理。
- reltoastrelid 不是 filenode,只是 oid。
- 在脱離數據庫環境讀取 TOAST 數據時,需要格外謹慎。
默認情況下,toast oid 與 toast filenode 一致, external 結構體中保存的是 toast oid 。
vacuum full/truncate 之後,兩者不再一致,但是 external 結構體中存的依然是 toast oid。
國產 PG 係數據庫適配
在國產 PostgreSQL 係數據庫的適配過程中,不同廠商對數據文件和 WAL 日誌均有一定修改。
在數據文件頁頭方面,一些數據庫增加了額外字節或填充零,使得原生讀取函數無法直接解析。通過分析固定的字節模式,可以推斷新增字段或填充值的位置,從而調整讀取邏輯,使頁頭的 lower、upper 等關鍵字段正確解析。
在 WAL 日誌適配方面,部分數據庫增加了額外類型,改變了原有類型的編號順序。為保證數據恢復功能,需要重新映射類型 ID,以正確識別 XLOG、HEAP、BTREE 等關鍵記錄。
PDU 核心價值與性能優化
PDU 分為社區版和專業版,專業版的許可與服務器綁定。PDU 的核心價值在於數據恢復的速度——對於急需恢復生產環境的客户,解析數據的效率至關重要。
在實際使用中,TOAST 數據的解析是性能瓶頸所在。社區版存在限速,專業版全速解析時也會因 TOAST 數據量大而明顯下降。為提升性能,開發者嘗試了多線程,但發現瓶頸依然在 TOAST 解析上。針對這一問題,進行了結構優化:原本的全表掃描方式改為類似目錄索引的解析方式。以一張 200MB、含 18,045 條數據的高密度 TOAST 表為例,優化前解析耗時約 800 秒,優化後降至 1.6 秒,同時解析結果與原數據完全一致。
這一優化顯著提升了 PDU 的實際應用效率,也體現了其在災難恢復場景下的核心價值。
結語
通過對 PostgreSQL 內核數據結構的深度解析和性能優化,PDU 顯著提升了大規模數據恢復的效率與準確性,為數據庫運維提供了可靠、可落地的災難恢復能力,同時兼顧不同廠商版本的兼容性,實現了高效、安全的數據恢復實踐。