天天看點

PostgreSQL 9.6 IO Hang問題淺析與優化

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過程中都沒有任何排程。

PostgreSQL 9.6 IO Hang問題淺析與優化

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>