PostgreSQL WAL解析與閃回的一些想法
1. 背景
最近在walminer基礎做了不少修改,以支援我們的使用場景。詳細參考
修改也花了不少精力和時間,這個過程中有些東西想記錄下來,友善以後查閱。
是以,這篇東西有點像流水賬。
2. WAL檔案格式
解析WAL的第一步是要了解WAL的檔案格式,我覺得最詳細易懂最值得看的資料是下面這個。
但是,以上還不夠。細節的東西還是要看源碼。我主要看的是寫WAL記錄的地方。
walminer作者李傳成的部落格裡也有不少WAL解析相關的文章,是後來才發現的,我還沒有看過。
3. walminer的解析流程
walminer解析WAL的入口是pg_minerXlog()函數。其主要過程如下
- 加載資料字典
- 起點搜尋解析階段
周遊WAL,根據輸入的起始時間和起始xid找到比對的第一個事務。 這個階段隻解析事務類型的WAL記錄,其他WAL記錄快速跳過。
- 完全解析階段
緊接着2的位置,繼續往下進行完整的解析。 這個階段,會收集所有FPI(FULL PAGE IMAGE)并反映它們的變更, 還會收集所有DML(insert/update/delete)類型的WAL記錄, 并且在遇到事務送出WAL時輸出該事務對應的DML,事務復原時清空該事務對應的DML。
walminer把WAL記錄中tuple變成SQL的過程比較有意思,中間用了一個VALUES的臨時格式。
以下面這個UPDATE語句為例
update tb1 set c1='3xy' where id=3;
其解析過程中涉及到的一些調用點如下:
pg_minerXlog()
->sqlParser()
->XLogMinerRecord()
->XLogMinerRecord_heap()
->minerHeapUpdate(XLogReaderState *record, XLogMinerSQL *sql_simple, uint8 info)
1. 擷取更新前後的tuple值(字元串格式)
->getTupleInfoByRecord()
->getTupleData_Update()
->mentalTup()
->mentalTup_nulldata()
->mentalTup_valuedata()
tupleInfo:VALUES(-3, '3x')"
tupleInfo_old:VALUES(3, NULL)
2. 生成中間redo SQL
->getUpdateSQL(sql_simple, tupleInfo, tupleInfo_old,...)
sql_simple:UPDATE \"public\".\"tb1\" SET VALUES(3, '3xy') WHERE VALUES(3, '3x')
3. 生成中間undo SQL
->getUpdateSQL(&srctl.sql_undo, tupleInfo_old, tupleInfo,...)
srctl.sql_undo:UPDATE \"public\".\"tb1\" SET VALUES(3, '3x') WHERE VALUES(-3, '3xy')"
4. 生成最終undo SQL
将中間中" VALUES"之後部分抹去,從rrctl.values,rrctl.nulls,rrctl.values_old,rrctl.nulls_old重新生成SQL後半部分。
->reAssembleUpdateSql(&srctl.sql_undo,true);
srctl.sql_undo:UPDATE "public"."tb1" SET "c1" = '3x' WHERE "id"=3 AND "c1"='3xy' AND ctid = '(0,10)';
pg_minerXlog()
->sqlParser()
->parserUpdateSql()
4. 生成最終redo SQL
->reAssembleUpdateSql(sql_ori, false)
sql_ori:UPDATE "public"."tb1" SET "c1" = '3xy' WHERE "id"=3 AND "c1"='3x';
4. walminer存在的問題
walminer是個非常棒的工具,填補了PG的一個空白。
但是,在我們準備把它推向生産時發現了不少問題。
- 資源消耗和解析速度
- 粗測了一下,解析一個16MB的WAL檔案大概需要15秒。不得不說實在太慢了。
- 解析大量WAL檔案還容易把記憶體撐爆。
- 正确性和可靠性
- 對并發事務産生的WAL記錄,解析的結果不對。
- 缺少回歸測試集
- 其他的小問題。
- 易用性
- 不支援基于LSN位置的過濾
- 解析一次WAL要調用好幾個函數,我覺得沒有必要,一個就夠了。
對這些已知的問題,都進行了改進。主要有下面幾點
- 使用單個wal2sql()函數執行WAL解析任務
- 支援指定起始和結束LSN位置過濾事務
- 支援從WAL記錄的old tuple或old key tuple中解析old元組構造where條件
- 增加lsn和commit_end_lsn結果輸出字段
- 添加FPI(FULL PAGE IMAGE)解析開關,預設關閉image解析
- 優化WAL解析速度,大約提升10倍
- 給定LSN起始位置後,支援根據WAL檔案名篩選,避免大量備援的檔案讀取。
- 修複多個解析BUG
-
增加回歸測試集
10.合并PG10/11/12支援到一個分支
修改後的walminer參考
https://gitee.com/skykiker/XLogMiner後續希望這些修改能合到源庫裡。
5. 後續改進思路
walminer在功能和使用場景上和MySQL的binlog2sql是非常接近的。
binlog2sql對自己的場景描述如下:
https://github.com/danfengcao/binlog2sql- 資料快速復原(閃回)
- 主從切換後新master丢資料的修複
- 從binlog生成标準SQL,帶來的衍生功能
binlog2sql已經有很多生産部署的案例,但是walminer好像還沒有。
其中原因,我想除了修改版已經解決的那些問題,walminer作為閃回工具,還有進一步改進的空間。
我考慮主要有以下幾點可以改進的
-
以fdw的形式提供接口
和函數相比fdw的好處是明顯的
- 不需要等所有WAL都解析完了再輸出,是以可以結合limit進行多次快速探測
- 不需要建立臨時表,解析過程中不需要産生WAL(産生WAL可能會觸發WAL清理)。
-
可以在備庫執行
使用fdw後,過濾條件直接通過where條件傳遞,接口更清晰。無法通過where條件傳遞東西,比如WAL存儲目錄,可以通過設定參數解決。
-
把解析過程分成事務比對探測和完全解析2個部分
完全解析時,需要從比對的事務往前回溯一部分,確定該事務的SQL甚至所需FPI都被解析到。
單純從比對的事務後面開始完全解析,會丢失SQL的。
-
增加DDL解析
其實并不需要解析出完整的DDL和逆向的閃回DDL,這個任務也很難實作。
隻需要能知道什麼時間,在WAL的哪個位點,哪個表發生了定義變更即可。
-
代碼重構
從性能和可維護性考慮,有必要進行代碼重構。
-
工具命名
既然這個工具的功能是從WAL中解析出原始SQL和undo SQL,walminer這個名稱就顯得不合适了。
因為從字面上了解,walminer應該是解析WAL本身包含的資訊,包括很多與SQL無關的的資訊,
但是不應該包含undo SQL這種WAL裡沒有而完全是被構造出來的東西。
是以,顧名思議,這個東西可以叫wal2sql。