postgresql , 實體流複制 , io不對稱
開車的同學都喜歡一馬平川,最好是車道很多,車很少,開起來爽。

大家想象一下,同樣的車速,6車道每秒可以通過6輛車,而1車道每秒就隻能通過1輛車。
好了,我們回到io層面,我們在使用fio測試塊裝置的io能力時,可以選擇多少個線程進行壓測,實際可以了解為開多少車道的意思。
隻要沒到通道或者裝置本身的極限,當然開的車道(并發)越多,測出來的io資料越好看。比如單線程可以做到每秒處理1萬次請求,而開8個并發,可能處理能達到8萬次請求。
這個可以了解之後,我們來看看postgresql的實體複制,為了保證資料一緻性,備庫在apply時,目前隻有一個startup程序,對于partial block從redo中讀取出塊的變化,并從資料檔案讀出對應的完整塊,在shared buffer中完成合并,最後bg writer會将shared buffer的dirty page write(異步寫)到資料檔案,對于fpw,則直接寫入shared buffer,後期bg write會負責處理dirty page。
雖然postgresql備庫已經使用shared buffer減少了寫操作(比如單個資料塊的多次變更,隻要對應的dirty page沒有從shared buffer evict出去,就不需要多次讀io;寫io也可以降低(比如os層io合并,或者bgwrite排程機制也可以降低寫io)),但是這些技術在主庫也存在,除非備庫設定的shared buffer更大,那麼備庫的寫io也許能降低。
另一方面,備庫在恢複非fpw塊時,需要從資料檔案讀取資料塊,進行合并,這個動作實際上會産生離散讀,在bgwrite将資料塊寫出shared buffer時産生離散寫。
小結一下,主庫是多程序離散讀寫轉換為單程序順序寫,而備庫單程序順序讀轉換為單程序離散讀寫。
主節點
下面指非分組送出、采用同步送出、開啟fsync時的流程。
1. 資料庫背景程序wal writer,負責将wal buffer的redo資料,批量寫入(fsync) wal檔案。
2. 資料庫背景程序bg writer,負責将shared buffer的dirty page資料(根據page lsn判斷,該頁wal已fsync),寫入(write) datafile。os排程,将page cache持久化寫入資料檔案(datafile)。
3. 使用者程序(s),将需要用到的資料頁,讀入shared buffer,當shared buffer不夠用時,會evict一些page,和bg writer操作類似。
4. 使用者程序(s),非送出事務時,将産生的變更寫入wal buffer,送出事務時,會觸發xlogflush(src/backend/access/transam/xact.c),将wal buffer寫入(fsync)wal檔案。
備節點
1. wal receiver程序,負責将收到的wal寫入wal buffer。
2. wal writer程序,負責将wal buffer寫入(fsync)wal檔案。
3. startup程序,從wal檔案讀取日志,同時從資料檔案讀取對應資料塊,合并(apply redo)後,寫入shared buffer。
4. bgwriter程序,将shared buffer的dirty page資料(根據page lsn判斷,該頁wal已fsync),寫入(write) datafile。os排程,将page cache持久化寫入資料檔案(datafile)。
對比主節點和備節點的操作,可以觀察到一些不對等的地方。
1. 寫(fsync)wal檔案時,主節點有使用者程序、wal writer并發的情況出現。而備節點隻有wal writer單一程序。
2. 寫(write)資料檔案時,主節點有使用者程序、bg writer并發的情況出現。而備節點隻有bg writer單一程序。
3. 寫shared buffer時,主節點有使用者程序并發讀寫。而備節點隻有startup單一程序。
由于以上不對稱的情況(主庫多數操作是多車道,備庫多數操作是單車道),當主庫産生的xlog量非常龐大,或者包含一些非常耗時的操作(例如(大量離散io,大量系統調用()))時,備庫可能會出現延時。
通常來說備庫接收日志不會有延遲,隻要網絡帶寬比主庫産生redo的速度快。
延遲通常發生在apply階段。前面分析了主庫多數操作是多車道,備庫多數操作是單車道,成為備庫apply延遲的主要原因。
1. 恢複時,需要消耗大量cpu時,例如開啟了資料檔案checksum時,會額外消耗startup程序的cpu。
2. 主庫頻繁的離散io操作,seek等。例如大量的索引變更,例如大量的索引vacuum,例如大量的vacuum操作。
3. 頻繁或者大量的系統調用,例如大批量删除對象,如drop schema。
<a href="https://github.com/digoal/blog/blob/master/201610/20161012_01.md">《postgresql daas設計注意 - schema與database的抉擇》</a>
4. 沖突,例如備庫開放使用者查詢,某些查詢操作和replay操作沖突時,可能短暫的影響恢複。
1. checksum,除非你要防實體篡改,否則通常不需要開啟checksum。checksum隻是幫助你了解塊是否損壞,并不能起到修複作用。(redo的checksum是預設強制打開的,但是資料檔案的checksum可選)
2. 删除沒有必要使用的索引。
3. 垃圾回收的排程,根據業務進行調整,預設是20%,越低越頻繁,越頻繁,垃圾越少。但是越頻繁可能導緻産生的vacuum dirty page會增加。可以選擇一個較為折中的值,例如5%。
4. 檢查點拉長,可以減少full page的量。full page是指每次檢查點後,第一次被更改的頁,需要将這個頁寫入wal日志,當資料庫crash後,可以保證資料的完整性。但是由于full page的引入,日志量會增加。
拉長檢查點的間距,可以減少full page。對于cow檔案系統例如(zfs, btrfs),不需要開啟full page write。
5. 加大備庫shared buffer,可以減少write datafile。
6. 關閉io時間的跟蹤,可以提高io操作效率。
7. 備庫使用iops能力更強、io延遲更低的機器(例如nvme的ssd),進而抹平不對稱的情況,注意,不建議使用raid 5機器。
8. 核心層面改造,使用并行apply,讓主備的操作盡量對等,這個涉及工作量較大,因為目前報pg apply延遲的case相對來說還是比較少的。
9. 如果有多個備庫,備庫可以關閉fsync。如同voltdb一樣,如果備庫挂了,重建備庫。
10. 将當機年齡加大,例如加到15億,可以減少當機産生的redo。
11. 修改核心,将xid改成64位的,徹底規避當機的問題,不過這麼做的話,和社群版本就完全不相容了。
12. 增加單個程序可打開的檔案數,可以減少檔案開啟和關閉,特别是資料庫的檔案數很多時,可以有效的減少系統調用的時間耗費。
配置例子
我們可以根據以上分析,模拟一個場景,讓備庫處于apply延遲的狀态,你可以使用perf , pstack , strace等工具分析是否符合我前面從原理或代碼層面的分析。
假設主備已經搭建好了。
在主庫建立幾張表,這幾張表涉及大量的索引。
建立了17張表,涉及1818個索引。
建立測試腳本
壓測
觀察延遲
分析