PostgreSQL中的full_page_writes參數用來防止部分頁面寫入導緻崩潰後無法恢複的問題。手冊中的相關描述如下:
http://postgres.cn/docs/9.3/runtime-config-wal.html#GUC-FULL-PAGE-WRITES
full_page_writes (boolean)
打開這個選項的時候,PostgreSQL伺服器在檢查點之後對頁面的第一次寫入時将整個頁面寫到 WAL 裡面。 這麼做是因為在作業系統崩潰過程中可能隻有部分頁面寫入磁盤, 進而導緻在同一個頁面中包含新舊資料的混合。在崩潰後的恢複期間, 由于在WAL裡面存儲的行變化資訊不夠完整,是以無法完全恢複該頁。 把完整的頁面影像儲存下來就可以保證正确存儲頁面, 代價是增加了寫入WAL的資料量。因為WAL重放總是從一個檢查點開始的, 是以在檢查點後每個頁面第一次改變的時候做WAL備份就足夠了。 是以,一個減小全頁面寫開銷的方法是增加檢查點的間隔參數值。
為了了解這個問題,先看看在不考慮部分寫入時PostgreSQL的處理邏輯。可以簡單概括如下:
對資料頁面的修改操作會引起頁面中資料的變化。
修改操作以XLOG記錄的形式被記錄到WAL中。
頁面中儲存最後一次修改該頁面的XLOG記錄插入到WAL後的下一個位元組位置(PageHeaderData.pd_lsn)。
必須在最後一次修改該頁面的XLOG記錄已經刷入磁盤後,資料頁面才能刷盤。
恢複時,跳過資料頁面中記錄的pd_lsn位置之前的XLOG
如果将修改操作記為Op1,Op2 ...,将資料頁面的狀态分别記為S1,S2和S3 ...,則如下所示:
當某個資料頁面處于S1狀态時,這個頁面從Op1開始REDO;當資料頁面處于S2狀态時,從Op2開始REDO;當資料頁面處于S3狀态時,不需要恢複。
然而,在部分寫入時,頁面将不再是上面的任何一個狀态,而是新舊混合的不一緻的狀态。如果pd_lsn存的是新值,那麼根本就不進行恢複;如果是舊值,由于恢複操作本來是要基于修改前的狀态的,在中間狀态上執行未必能成功,即使恢複涉及的資料部分恢複了也不能糾正頁面其它地方的不一緻。為了解決這個問題,PostgreSQL引入了fullpagewrites,checkpoint後的第一次頁面修改将完全的頁内容記錄到WAL,之後從上次的checkpoint點開始恢複時,先取得這個完成的頁面内容然後再在其上重放後續的修改操作。
fullpagewrites會帶來很大的IO開銷,是以條件許可的話可以使用支援原子塊寫入的儲存設備或檔案系統(比如ZFS)避免部分寫入。
MySQL中有類似的防止部分寫入的機制,叫innodbdoublewrite。原理類似,但實作稍有不同,innodbdoublewrite生效時,在寫真正的資料頁前,把資料頁寫到doublewrite buffer中,doublewrite buffer寫完并重新整理後才往真正的資料頁寫入資料。
可參考:
http://dev.mysql.com/doc/refman/5.6/en/glossary.html#glosdoublewritebuffer
可以參考某個XLOG的恢複代碼,比如heapxloginsert()。
src/backend/access/heap/heapam.c