天天看點

PostgreSQL WAL解析與閃回的一些想法PostgreSQL WAL解析與閃回的一些想法

PostgreSQL WAL解析與閃回的一些想法

1. 背景

最近在walminer基礎做了不少修改,以支援我們的使用場景。詳細參考

修改也花了不少精力和時間,這個過程中有些東西想記錄下來,友善以後查閱。

是以,這篇東西有點像流水賬。

2. WAL檔案格式

解析WAL的第一步是要了解WAL的檔案格式,我覺得最詳細易懂最值得看的資料是下面這個。

但是,以上還不夠。細節的東西還是要看源碼。我主要看的是寫WAL記錄的地方。

walminer作者李傳成的部落格裡也有不少WAL解析相關的文章,是後來才發現的,我還沒有看過。

3. walminer的解析流程

walminer解析WAL的入口是pg_minerXlog()函數。其主要過程如下

  1. 加載資料字典
  2. 起點搜尋解析階段
    周遊WAL,根據輸入的起始時間和起始xid找到比對的第一個事務。
    這個階段隻解析事務類型的WAL記錄,其他WAL記錄快速跳過。           
  3. 完全解析階段
    緊接着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的一個空白。

但是,在我們準備把它推向生産時發現了不少問題。

  1. 資源消耗和解析速度
    • 粗測了一下,解析一個16MB的WAL檔案大概需要15秒。不得不說實在太慢了。
    • 解析大量WAL檔案還容易把記憶體撐爆。
  2. 正确性和可靠性
    • 對并發事務産生的WAL記錄,解析的結果不對。
    • 缺少回歸測試集
    • 其他的小問題。
  3. 易用性
    • 不支援基于LSN位置的過濾
    • 解析一次WAL要調用好幾個函數,我覺得沒有必要,一個就夠了。

對這些已知的問題,都進行了改進。主要有下面幾點

  1. 使用單個wal2sql()函數執行WAL解析任務
  2. 支援指定起始和結束LSN位置過濾事務
  3. 支援從WAL記錄的old tuple或old key tuple中解析old元組構造where條件
  4. 增加lsn和commit_end_lsn結果輸出字段
  5. 添加FPI(FULL PAGE IMAGE)解析開關,預設關閉image解析
  6. 優化WAL解析速度,大約提升10倍
  7. 給定LSN起始位置後,支援根據WAL檔案名篩選,避免大量備援的檔案讀取。
  8. 修複多個解析BUG
  9. 增加回歸測試集

    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作為閃回工具,還有進一步改進的空間。

我考慮主要有以下幾點可以改進的

  1. 以fdw的形式提供接口

    和函數相比fdw的好處是明顯的

    • 不需要等所有WAL都解析完了再輸出,是以可以結合limit進行多次快速探測
    • 不需要建立臨時表,解析過程中不需要産生WAL(産生WAL可能會觸發WAL清理)。
    • 可以在備庫執行

      使用fdw後,過濾條件直接通過where條件傳遞,接口更清晰。無法通過where條件傳遞東西,比如WAL存儲目錄,可以通過設定參數解決。

  2. 把解析過程分成事務比對探測和完全解析2個部分

    完全解析時,需要從比對的事務往前回溯一部分,確定該事務的SQL甚至所需FPI都被解析到。

    單純從比對的事務後面開始完全解析,會丢失SQL的。

  3. 增加DDL解析

    其實并不需要解析出完整的DDL和逆向的閃回DDL,這個任務也很難實作。

    隻需要能知道什麼時間,在WAL的哪個位點,哪個表發生了定義變更即可。

  4. 代碼重構

    從性能和可維護性考慮,有必要進行代碼重構。

  5. 工具命名

    既然這個工具的功能是從WAL中解析出原始SQL和undo SQL,walminer這個名稱就顯得不合适了。

    因為從字面上了解,walminer應該是解析WAL本身包含的資訊,包括很多與SQL無關的的資訊,

    但是不應該包含undo SQL這種WAL裡沒有而完全是被構造出來的東西。

    是以,顧名思議,這個東西可以叫wal2sql。

6. 參考