postgresql檢查點是将shared buffer中的髒頁打标記,并集中将其刷到磁盤的動作(fsync)。(期間可能有刷盤的排程,降低當髒頁很多時帶來的io影響)
在檢查點之外,平時bgwriter程序則會使用bufferio的方式(write)将髒頁寫到os的dirty page。
如果shared buffer非常大,而且資料庫應用如果是頻繁産生髒頁的應用,那麼檢查點帶來的性能影響會非常的明顯。
例如shared buffer有100g,活躍資料有100g,同時活躍資料在不停的被update(産生髒頁),那麼在發生檢查點時,fsync的過程中,可能導緻性能急劇下降。
接下來重制一下以上問題。
單機開啟100個pg執行個體,每個執行個體限制一定的記憶體,cpu,以及io資源,其中日志盤iops限制4000,資料盤iops限制800。
壓測方法
每個執行個體最大資料量1億,對資料進行随機的upsert操作。
是以全表都是熱點。
每個執行個體連4個連接配接,同時進行壓測。
測試用例參考
<a href="https://github.com/digoal/blog/blob/master/201609/20160927_01.md">20160927_01.md</a>
由于同時開啟測試,每個節點幾乎在同一時間點進入檢查點狀态。
産生大量的writeback記憶體。
通過以下方法可以觀察到
解釋
在産生了大量的writeback記憶體計數後,最後檢查點調用fsync前,因為髒頁沒有完全落盤,導緻執行個體的檢查點在fsync的階段需要耗費自己的iops進行刷盤,非常慢。
甚至執行個體完全不可用。
觀察到的現象
資料庫整機io很低(隻有資料盤的io,并且受到cgroup限制),
tps降到0 (更新塊被堵塞) ( shared buffer中沒有剩餘的塊? )
需要等待執行個體的writeback 全部刷盤後才能恢複。
期間程序狀态如下
狀态解釋
程序stack資訊
checkpointer程序
stats收集程序
bgwriter程序
backend process 程序
logger程序
wal writer程序
檔案系統已使用data=writeback挂載
postgresql 9.6的檢查點改進如下
1. 階段1(調用write + 檢查點排程)
2. 階段2(調用sync_file_range)
實際上通過設定os排程也能緩解,例如。
3. 階段3(fsync)
分析
1. 從檢查點源碼開始
2. 調用buffersync
3. 調用synconebuffer
4. 調用flushbuffer
5. 調用mdwrite
6. 調用filewrite
調用write産生dirty page
7. 調用schedulebuffertagforwriteback
8. 調用issuependingwritebacks
作用見階段2。
9. 調用issuependingwritebacks
10. 調用smgrwriteback
src/backend/storage/smgr/md.c
11. 調用filewriteback
12. 調用pg_flush_data
src/backend/storage/file/fd.c
(前面已經調用了write,現在告訴os 核心,開始将髒頁刷到磁盤)
注意,如果range指定的髒頁很多時,sync_file_range的異步模式也可能被堵塞。
調用sync_file_range
異步模式
1. 以上動作做完後,作業系統不一定把dirty page都刷盤了。
因為調用的是異步的sync_file_range。
2. 同時在此過程中,bgwrite, backend process還有可能将shared buffer中新産生的髒頁寫入os dirty page。
這些髒頁也許涉及到接下來檢查點需要fsync的檔案。
13. 接下來, 檢查點開始調用smgrsync
開始fsync檔案級别,如果檔案又産生了髒頁怎麼辦(見以上不穩定因素分析)。
14. 調用mdsync
15. 調用filesync, 同步整個檔案
16. 調用pg_fsync
17. 調用pg_fsync_no_writethrough
18. 調用 fsync 刷盤
1. 調用fsync前,作業系統不一定把dirty page都刷盤了。
因為這兩個不安定因素的存在,同時加上環境中有多個pg執行個體,并且每個pg執行個體都限制了較小的data盤io,導緻fsync時刷盤非常的慢。
redo的io能力遠大于data盤的io能力時,checkpoint過程中可能又會産生很多熱點髒頁。
導緻檢查點在最後fsync收官時,需要刷dirty page,而同時又被執行個體的cgroup限制住,看起來就好像執行個體hang住一樣。
是在write階段進行排程,在sync_file_range和fsync過程中都沒有任何排程。

1. 解決不安定因素1 - 避免檢查點過程中産生未刷盤的dirty page
在檢查點過程中,bgwriter或backend process從shared buffer産生的髒頁write out時,會調用write即buffer io。
進入檢查點後,bgwriter或backend process從shared buffer産生的髒頁write out時,同時記錄該page的id到list(1或2)。
2. checkpoint在最後階段,即調用fsync前,插入一個階段。
将list(1或2)的page實行sync_file_range,等待其刷盤成功。
使用以下flag
3. 為了防止bgwrite或backend process 與checkpoint 的sync file range沖突。
使用兩個list來交替記錄檢查點開始後的shared buffer evict pages。
4. 新增一個guc變量,配置當checkpoint最後一次sync file range的list page樹少于多少時,進入fsync階段。
允許使用者根據iops的規格,配置這個guc變量,進而減少最後fsync時需要等待的page數。
注意這個值也不能設得太小,否則可能造成漫長的很多輪list1和list2的sync file range過程。
需要修改postgresql核心,動作較大。
5. 解決不安定因素2 - 檢查點最後的階段,調用fsync前,確定fd的所有dirty page都已經write out。
目前checkpoint調用的pg_flush_data是異步的sync_file_range,我們需要将其修改為同步的模式。
建議隻修改checkoint的調用,不要動到原有的邏輯。
6. 從os核心層面解決io hang的問題。
阿裡雲rds for postgresql已從資料庫核心層面完美的解決了這個問題,歡迎使用。
<a href="http://yoshinorimatsunobu.blogspot.com/2014/03/how-syncfilerange-really-works.html">http://yoshinorimatsunobu.blogspot.com/2014/03/how-syncfilerange-really-works.html</a>
<a href="http://info.flagcounter.com/h9v1">count</a>