PG中調整checkpoint相關參數的基本原則【原創/翻譯】

原創翻譯文章,轉載須註明出處。訪問我的Github(地址:https://guobo507.github.io)查看最新文章列表。

原文鏈接:https://www.2ndquadrant.com/en/blog/basics-of-tuning-checkpoints/。譯者水平有限,不當之處請多包涵。

在具有一定規模的寫入(non-trivial number of writes)的系統上,合適的檢查點參數設置對於獲得良好的系統性能至關重要。然而,檢查點是我們經常在社區郵件列表,以及在爲客戶進行性能調整審查時,發現設置混亂和配置有問題的區域之一(另一個區域是不久前Citus公司的Joe Nelson曾經討論過的autovacuum)。因此,我將引導您理解和完成檢查點的設置,包括檢查點的作用以及如何在PostgreSQL中進行合適的調整。

What is the point of checkpoints?

PostgreSQL是依賴預寫日誌(WAL)的數據庫之一,所有的更改都要首先寫入WAL日誌(關於數據庫更改的流式信息),然後才寫入到數據文件中。這樣可以提供數據的持久性,因爲在發生崩潰的情況下,數據庫可以使用WAL日誌執行恢復,即從WAL日誌中讀取數據庫的更改,然後將其重新應用到數據庫中。

雖然這可能使實際的寫入量增加一倍,但是可以提高數據庫整體的性能。用戶只需要等待WAL日誌刷新到磁盤,而數據文件頁僅在內存中被修改,然後稍後在後臺刷新到磁盤。這樣做很值得,因爲對WAL日誌本質上是順序寫入的,而對數據文件的寫入通常是隨機的(因爲緩存在內存中的數據頁是隨機的)。

假設系統崩潰了,數據庫需要執行恢復。最簡單的方法是從頭開始,並從頭開始重放所有的WAL日誌。最後,我們就能獲得完整的(正確的)數據庫。這樣做有一個明顯的缺點,需要保留並在恢復時重放所有的WAL日誌。我們經常處理規模不大的數據庫(例如幾百GB),但是每天會產生幾TB的WAL日誌(變更多)。因此,想象一下當數據庫運行了一年以後,需要多少磁盤空間來保存所有WAL日誌,以及在恢復過程中重放所有WAL日誌需要多少時間。

但是,如果數據庫可以保證給定WAL位置(日誌中的偏移量)的所有更改,以及直到該位置的所有數據文件的更改都已經刷新到磁盤。那麼,數據庫在恢復時就可以確定需要恢復的起始位置,並僅僅重放WAL日誌的其餘部分即可,從而將大大減少恢復的時間。而且數據庫還可以刪除該“已知良好(known good)”位置之前的WAL文件。

這正是檢查點的目的:確保在執行數據庫恢復時不再需要某個時間點之前的WAL日誌,從而減少了磁盤空間需求和恢復的時間。

注意:如果您碰巧是一名遊戲玩家,則您可能更加熟悉檢查點的概念。如您的角色在遊戲中通過了某個點,並且如果您沒能擊敗下一個BOSS或掉進了熔岩中;則您只需要從您已經通過的最後一關重新開始即可,而不是從遊戲的開頭重新開始。讓我們看看在PostgreSQL中是如何實現這一點的。

我們再討論另一個比較極端的例子:非常頻繁地執行檢查點操作(例如,每秒進行一次)。這楊的結果是將只保留非常少量的WAL日誌,恢復速度也非常快(只需重放很少量的WAL日誌)。但這也會把對數據文件的異步寫入轉換爲類似同步寫入(頻率高),從而嚴重影響用戶體驗(例如,增加COMMIT的延遲,降低系統的吞吐量)。

因此,在實踐中,您可能希望檢查點不經常發生而不影響用戶,但是也要足夠頻繁,以合理限制恢復時間和對磁盤空間的需求。

Triggering checkpoints

可以觸發檢查點的原因大約有三四種:

  • 直接執行CHECKPOINT命令;
  • 執行需要的檢查點的命令(例如:pg_start_backupCREATE DATABASE,或pg_ctl stop|restart和其他一些命令);
  • 自上一個檢查點以來已達到配置的時間;
  • 自上一個檢查點以來生成了已配置的最大WAL文件數量(也稱爲WAL用盡(running out of WAL)或填充WAL(filling WAL))。

前兩條在數據庫日常運行中是無關緊要的,很少見的,因爲都是必要時纔會手動觸發的事件。這篇文章是關於如何配置另外兩個數據庫自動執行的檢查點事件的,這些事件會影響定期執行的檢查點。

另外兩條基於時間(time)/大小(size)的限制通過如下兩個配置參數來實現:

  • checkpoint_timeout = 5min
  • max_wal_size = 1GB (before PostgreSQL 9.5 this was checkpoint_segments)

使用上面的(默認)值,PostgreSQL將在每5分鐘,或者在磁盤上的WAL日誌文件總量增長到大約1GB之後觸發CHECKPOINT操作。

注意max_wal_size是總WAL日誌文件的大小,它是一個軟限制(soft limit),有兩個意義。首先,數據庫將嘗試不超過該限制,但超過它是可以的,因此請在分區文件系統上配置足夠的可用空間並對其進行監控。其次,這並不是針對“每個檢查點”的限制,由於檢查點是分散的(稍後說明),WAL日誌的配額會在2-3個檢查點之間分配。因此,數據庫通常在寫入了300–500MB的WAL文件之後自動啓動CHECKPOINT操作,具體還要依賴於您的checkpoint_completion_target設置。

與模版配置文件中的大多數其他參數的默認值一樣,默認值也很低,其大小設置可以在Raspberry Pi等小型系統上良好的工作。

但是,如何來確定您的系統中的一個合適的參數值呢?我們的目標是不要太頻繁也不要很少執行檢查點操作。關於這兩個參數的設置的“最佳實踐”包括下面的兩個步驟:

  • 設置一個合理的checkpoint_timeout值;
  • max_wal_size設置得足夠高以至於很少能達到。

實際上很難說一個“合理”的checkpoint_timeout值是多少,因爲它取決於恢復時間目標(RTO),即可接受的最大恢復持續時間是多少。

這有點棘手,因爲checkpoint_timeout它限制了生成WAL日誌所花費的時間,而不是直接影響到恢復的時間。由於多種原因,我們無法確切地說出恢復需要話費多長時間。WAL日誌通常是由多個進程(運行DML)生成的,而恢復時則是由單個進程執行的(此限制主要是固有的,不太可能很快改變)。這不僅影響本地恢復,還會影響到具有流複製的備用數據庫的故障轉移恢復。當然,本地恢復通常是在重新啓動後立即進行的,此時文件系統緩存很冷(cold)。

通常,默認值(5分鐘)比較低,設置30分鐘到1小時之間的值是比較常見的。PostgreSQL 9.6甚至將最長期限增加到1天(因此,一些黑客認爲這對於某些用例是個好主意)。較低的值也可能由於全頁寫入而導致寫入放大(在此不做討論)。

假設我們決定使用30分鐘:

checkpoint_timeout = 30min

現在我們需要評估在30分鐘內數據庫將產生多少WAL日誌文件,以便我們可以將其用於max_wal_size設置。有幾種方法可以確定生成多少WAL日誌文件:

  • 使用pg_current_xlog_insert_location()方法查看實際的WAL位置變化(基本上是文件中的偏移)來計算每30分鐘測量的位置之間的差異。
  • 啓用log_checkpoints = on,然後從服務器日誌中提取信息(每個完成的檢查點都會有詳細的統計信息,包括WAL的數量)。
  • 使用來自pg_stat_bgwriter視圖的數據,該數據還包括有關檢查點數量的信息(您可以將其與當前·max_wal_size·值的知識相結合)。

例如,我們使用第一種方法。在運行pgbench的測試機上,我確實看到了:

postgres=# SELECT pg_current_xlog_insert_location();
pg_current_xlog_insert_location 
---------------------------------
3D/B4020A58
(1 row)

... After 5 minutes(五分鐘後) ...

postgres=# SELECT pg_current_xlog_insert_location();
pg_current_xlog_insert_location 
---------------------------------
3E/2203E0F8
(1 row)

postgres=# SELECT pg_xlog_location_diff('3E/2203E0F8', '3D/B4020A58');
pg_xlog_location_diff 
-----------------------
            1845614240
(1 row)

這表明在5分鐘內,數據庫生成了約1.8GB的WAL日誌,因此checkpoint_timeout = 30min大約會生成10GB的WAL日誌文件。但是,如前所述,max_wal_size配額是2–3個檢查點的總和,因此max_wal_size = 30GB(3 x 10GB)似乎是比較合理的。

其他方法使用不同的數據源進行考量和計算,但是最終想法是大致相同的。

Spread checkpoints

我建議您調整checkpoint_timeoutmax_wal_size,但是我並沒有說明全部的真相。還有另一個參數,稱爲checkpoint_completion_target,但是要調整它,您需要了解“擴展檢查點(spread checkpoints)”的含義。

在執行CHECKPOINT期間,數據庫需要執行以下三個基本的步驟:

  1. 識別共享緩衝區中的所有髒頁(已修改的頁);
  2. 將所有這些緩衝區寫入磁盤(或更確切地說,寫入文件系統緩存);
  3. 調用fsync()將所有修改後的文件刷新到磁盤(數據文件)。

僅當上述所有步驟完成之後,檢查點才能被視爲完成。您可以“儘可能快地”執行這些步驟,即一次性寫入所有的髒緩衝區,然後調用fsync()來刷新到磁盤文件,實際上,這是PostgreSQL 8.2之前的版本所採取的策略。但這會導致由於填充文件系統緩存、使設備飽和而導致I/O的停頓,影響到用戶會話。

爲了解決這個問題,PostgreSQL 8.3引入了“傳播檢查點”的概念。它不是一次寫入所有的髒數據,它將一次檢查點的寫入分散在很長一段時間內。這使操作系統有時間在後臺刷新髒數據,從而使最終調用fsync()的代價要低得多。

我們將寫本次檢查點的寫操作分散到直到下一個檢查點發生時的過程來限制寫操作(從而降低I/O的大小)。因爲數據庫知道到下一次檢查點操作還有多少時間/WAL,並且它可以計算已經寫出了多少緩衝區。但是,數據庫必須直到當前檢查點的最後才發出寫命令,這意味着最後一批寫出操作仍將保留在文件系統緩存中,從而使最終fsync()調用(在開始下一個檢查點之前發出)再次變得昂貴。

The writes are throttled based on progress towards the next checkpoint – the database knows how much time/WAL do we have left until another checkpoint will be needed, and it can compute how many buffers should be already written out. The database however must not issue writes until the very end – that would mean the last batch of writes would still be in filesystem cache, making the final fsync() calls (issued right before starting the next checkpoint) expensive again.

因此,數據庫需要爲操作系統內核預留足夠的時間,以便髒數據在後臺刷新到磁盤上。頁面緩存(Linux文件系統緩存)的到期通常是由時間驅動的,尤其是由以下內核參數驅動:

vm.dirty_expire_centisecs = 3000

這表示頁面數據將在30秒後過期(默認情況下)。

注意:關於內核參數vm.dirty_background_bytes的調整非常重要。在具有大量內存的系統上,此參數的默認值過高,從而使內核可以在文件系統緩存中累積大量髒數據。內核通常會一次全部清除它們,這反而會降低了傳播檢查點的好處。

現在,返回到checkpoint_completion_target = 0.5。此配置參數說明距離下一個檢查點尚有多遠時,本次檢查點操作應該完成所有寫入的操作。例如,假設檢查點僅由checkpoint_timeout = 5min觸發,則數據庫將限制寫入時I/O大小,以使最後一次寫入在2.5分鐘後完成。然後,操作系統還有2.5分鐘的時間將數據刷新到磁盤上,因此5分鐘後發出的fsync()調用既便宜又快速。

當然,考慮到文件系統緩存頁的超時時間僅僅爲30秒,爲系統預留2.5分鐘似乎有點太長了。您可以考慮增加checkpoint_completion_target,例如設置爲0.85,這將使系統大約需要45秒,比它需要的30秒多一點。不過,我們不建議這樣做,因爲在密集寫入的情況下,max_wal_size可能比checkpoint_timeout設置的5分鐘更早觸發檢查點,從而實際爲操作系統預留的時間少於了30秒。

但是,處理寫密集型工作負載的系統不太可能以更高的checkpoint_timeouts值運行,從而使默認的checkpoint_completion_target值也相對較低。例如,將超時設置爲30分鐘,它將強制數據庫在前15分鐘內(以兩倍的寫入速率)進行所有寫操作,然後在其餘15分鐘內保持空閒狀態。

您可以嘗試大致使用下面的公式來設置checkpoint_completion_target

(checkpoint_timeout - 2min) / checkpoint_timeout

例如,checkpoint_timeout設置爲30分鐘時,checkpoint_completion_target約爲0.93。通常建議該參數不要超過0.9(超過0.9的設置是可以的,並且您不太可能觀察到這兩個值之間的顯着差異)。當您使用非常高的checkpoint_timeout值(在PostgreSQL 9.6上現在最長可達1天)時,這可能會改變。

Summary

現在您應該知道檢查點的用途以及如何調整它們的一些基礎考量了。再來總結一下:

  • 大多數檢查點應基於時間的,即由checkpoint_timeout觸發的:

    • 性能(不頻繁的檢查點)和較小的恢復所需的時間(頻繁的檢查點)之間的折衷;
    • 15至30分鐘之間的值是最常見的,但是長達1小時也不是一件壞事。
  • 確定checkpoint_timeout的設置後,可以通過估算WAL日誌文件的量選擇max_wal_size設置;

  • 設置checkpoint_completion_target,以便操作系統內核有足夠(但不是特別多)的時間將數據真正刷新到磁盤上;

  • 適當調整vm.dirty_background_bytes以防止操作系統內核在頁面緩存(文件系統緩存,同樣位於內存中)中累積大量髒數據頁。

或者,您可以簡單地使用我們的PostgreSQL Performance Tuning服務,該服務不僅覆蓋檢查點的配置優化,還覆蓋PostgreSQL的配置的其他重要部分。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章