PostgreSQL的"double buffers"刷髒機制和引數

T1YSL發表於2022-12-16

PostgreSQL資料庫使用雙快取寫資料,shared_buffer + OS page cache,下圖是PG與OS記憶體互動的過程 ,在PostgreSQL中,shared_buffers所代表的記憶體區域可以看成是一個以8KB的block為單位的陣列,即最小的分配單位是8KB。這正好是一個page的大小,每個page以page內部的後設資料(Page Header)互相區分。這樣,當Postgres想要從disk獲取資料(page)時,他會(根據page的後設資料)先搜尋shared_buffers,確認該page是否在shared_buffers中,如果存在,則直接命中,返回快取的資料以避免I/O。如果不存在,再到OS快取查詢,最後才會透過I/O訪問disk獲取資料。

對於double buffer也可參考我這篇文章

本篇文章,結合“double buffers”機制分析一下影響buffers刷髒頁的相關機制和引數。

1671009408655.png

一、刷髒頁之前記錄WAL日誌檔案(redo)

一個PostgreSQL後端程式產生資料寫入後,一定會先寫入WAL日誌。

1671004656782.png

而WAL日誌也不是憑空產生的,它是將wal buffer裡的資料持久化到磁碟的,大致的流程基本如下

透過一些介面註冊wal資料
          ⬇
將註冊的wal資料重組為一個資料連結串列
          ⬇
將資料連結串列中的資料複製到wal頁buffer中
          ⬇
將wal頁buffer刷寫到磁碟中

1671010337890.png

而對於這套流程裡將記錄儲存到wal日誌檔案的主要函式 ,主要有以下幾個:

XLogBeginInsert(); 
XLogRegisterData(); 
XLogRegisterBuffer(); 
XLogRegisterBufData();
XLogSetRecordFlags();  
XLogInsert();  
XLogRecordAssemble();  
XLogInsertRecord(); 
PageSetLSN

在寫完了wal日誌後,資料庫可以進行刷髒頁。

二、資料庫層刷髒

PostgreSQL支援一種稱為全頁寫(full page write)的功能來處理部分寫(頁裂)問題。如果啟用(預設啟用),PG會在每個檢查點之後、每個頁面第一次發生變更時,將頭資料和整個頁面作為一條XLOG記錄寫入WAL緩衝區,然後再進行刷髒。

1.資料庫刷髒頁方式

1.bgwriter刷髒:後臺刷不影響使用者使用,但從全域性上看可能會有單頁多次重複刷的現象

2.checkpoint刷髒:阻塞性刷髒,嚴重影響QPS,但從全域性上看可以等單頁寫多次,減少刷髒頁的次數 。

checkpoint是以特定的時間間隔重新整理所有髒頁,並建立一個用於資料庫恢復用的檢查點 。而Bgwriter定期把髒資料從記憶體緩衝區刷出到磁碟中,減少查詢時的阻塞 ,因為PostgreSQL在定期作檢查點時需要把所有髒頁寫出到磁碟,透過BgWriter預先寫出一些髒頁,可以減少設定checkpoint時要進行的IO操作,以便始終有足夠多的乾淨頁面可以使用,使系統的IO負載趨向平穩 。兩者的目的和執行頻率都有不同。

Bgwriter使用的快取替換演算法在8.1版本之前是用的LRU,從8.1版開始,PostgreSQL使用了 Clock Sweep(時鐘掃描)。 clock sweep 演算法的思想是根據訪問次數來判斷哪些資料為熱點資料。當快取空間不足,它會優先替換掉訪問次數低的快取 ,即快取使用頻率高的會被儲存,低的被移除。

2.相關引數或機制

1.bgwriter相關

bgwriter_delay :bgwriter每次工作之後休息的間隔,休息避免阻塞使用者執行緒。 在每一輪迴圈中,background writer都會為一定數量的髒緩衝區發出寫操作,然後background writer進行睡眠,睡眠的時間為bgwriter_delay引數值,然後再喚醒,然後重複。設定bgwriter_delay為不是10的倍數的值可能與將其設定為10的下一個更高的倍數具有相同的結果。

bgwriter_flush_after :讓bgwriter時不時的觸發OS寫dirty page 。當 background writer 寫入的資料量超過本引數值之後,強制OS做一次FLUSH 。這麼做會限制 kernel 中 page cache 的 dirty data 的數量,減少在 checkpoint 最後 fsync 時發生卡頓的可能性。 因為OS層可能要累積到一個較大值才會去寫盤 此時可能導致較大的寫盤IO動作, 從而爭搶使用者的IO 。但是bgwriter過於頻繁讓OS寫磁碟也會有問題,例如shared buffer中的page在os層刷髒期間多次被標記為髒塊,或者相鄰的page在一個刷髒視窗內變髒並且被bgwriter多次寫出,bgwrite頻繁觸發fsync ,也會增加磁碟的IO。

OS也有自己的IO排程策略,可以設定dirty_background_bytes為一個較小值, 避免大的IO。

bgwriter_lru_maxpages : 一個bgwriter工作週期內, 最多刷出多少個dirty page。每輪 bgwriter 程式寫入 LRU Page 的最大數量。本引數設定為零會禁用 background writing (bgwriter)行為,但是 background writer 程式依然會存在

bgwriter_lru_multiplier : 一個bgwriter工作週期內, 應該刷出多少個dirty page。 在每輪中寫出的髒緩衝區的數量基於最近幾輪中伺服器程式需要的新緩衝區的數量。最近的平均需求乘以bgwriter_lru_multiplier,以估計下一輪所需的緩衝區數量。髒緩衝區被寫出,直到相應的數量的緩衝區為止。(每輪寫入的緩衝區數量不會超過bgwriter_lru_maxpages。)。1.0的設定表示一個“及時”的策略,即準確地寫入預測需要的緩衝區數量。較大的值可以緩衝需求的峰值,而較小的值有意讓伺服器程式完成寫操作。預設值是2.0。

2.checkpoint相關

Checkpoint的工作內容:

1.Flush Dirty Pages,刷髒
2.Update some points,更新XlogCtl和ControlFile,並持久化至pg_control檔案
3.Remove old wal,計算兩次checkpoint間的WAL數量進行回收重用,並清理不再需要的WAL

如下為一些定義的Checkpoint觸發的條件,是xlog.h標頭檔案裡定義的場景,會直接引起CreateCheckPoint和相關附屬行為 ,有的包含了刷髒的流程。

image.png

需要注意的是,當非停庫、redo完成、強制觸發checkpoint時,如果資料庫沒有寫入操作,則直接return不進行Flush dirtypage等操作 。也就是所謂的Checkpoint skipped機制。

三、系統層刷髒

OS快取使用簡單的LRU(移除最近最久未使用的快取)

對於系統層的刷髒而言。對於這幾個系統引數也有一個大致的分類。

1671002572994.png

1.後臺非同步

vm.dirty_background_bytes = 0  # 觸發回刷的髒資料量。類似postgresql的bgwriter, 由後臺程式而不是使用者程式刷
vm.dirty_background_ratio = 5   觸發回刷的髒資料佔可用記憶體的百分比,5表示5%

2.前臺阻塞刷髒

vm.dirty_bytes = 0 # 觸發同步寫的髒資料量。類似postgresql 的 server process刷髒, 使用者程式參與, 所以會導致使用者程式的RT升高
vm.dirty_ratio = 10  #觸發同步寫的髒資料佔可用記憶體的百分比。是可以用髒資料填充的絕對最大系統記憶體量,當系統到達此點時,必須將所有髒資料提交到磁碟,同時所有新的I/O塊都會被阻塞,直到髒資料被寫入磁碟。這通常是長I/O卡頓的原因,但這也是保證記憶體中不會存在過量髒資料的保護機制。
vm.dirty_writeback_centisecs = 500 # 回刷程式定時喚醒時間。後臺程式的排程間隔。指定多長時間 pdflush/flush/kdmflush 這些程式會喚醒一次,然後檢查是否有快取需要清理。
vm.dirty_expire_centisecs = 100 # 指定髒資料超時回刷時間(單位:1/100s),也就是髒資料能存活的時間。在page cache中存活時間超過這個值的髒頁才會被刷盤。當 pdflush/flush/kdmflush 在執行的時候,他們會檢查是否有資料超過這個時限,如果有則會把它非同步地寫到磁碟中。畢竟資料在記憶體裡待太久也會有丟失風險。
  • XXX_ratio和 XXX_bytes是同一個配置屬性的不同計算方法。如果設定_bytes版本,則_ratio版本將變為0,反之亦然。,優先順序 XXX_bytes > XXX_ratio

四、如何確認快取資料量和快取大小

在訪問資料時,資料會先載入到os快取,然後再載入到shared_buffers,這個載入過程可能是一些查詢,也可以使用pg_prewarm預熱快取,以獲取一個好的查詢效果。

如下兩種方式可以檢視快取的佔用

1.pg_buffercache和pgfincore

可以使用pg_buffercache和pgfincore這兩個外掛去檢視快取的資料量。可參考我這篇文章裡的相關使用

2.hcache

除此之外可以使用hcache,hcache是一款檢視buff/cache的工具,可以檢視程式的快取佔用情況,以及佔用作業系統快取最多的N的檔案 。

安裝 方式如下

wget 
chmod +x hcache
mv hcache /usr/local/sbin/

使用方式

  • 檢視使用快取最多的10個檔案 ( hcache -top 10)
  • 檢視列出的檔案詳細( lsof filename)
  • 檢視程式的快取使用 (hcache -pid xxxx)

image.png

五、如何釋放shared_buffers和OS caches

注意:在生產環境釋放快取可能造成很大的影響。嚴禁隨便執行。可供測試環境自己測試

1.如何釋放shared_buffers

重啟資料庫可以釋放shared_buffers。

2.如何釋放OS cache

echo 1 > /proc/sys/vm/drop_caches

drop_caches的值可以是0-3之間的數字,代表不同的含義:

0:不釋放(系統預設值)
1:釋放頁快取
2:釋放dentries和inodes
3:釋放所有快取

釋放完記憶體後改回去讓系統重新自動分配記憶體。

echo 0 >/proc/sys/vm/drop_caches

free -m #看記憶體是否已經釋放掉了。
如果需要釋放所有快取,就輸入下面的命令:

echo 3 > /proc/sys/vm/drop_caches


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69990629/viewspace-2928446/,如需轉載,請註明出處,否則將追究法律責任。

相關文章