天天看點

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

原創翻譯文章,轉載須注明出處。通路我的Github(位址:https://guobo507.github.io)檢視最新文章清單。

目錄

    • What is the point of checkpoints?
    • Triggering checkpoints
    • Spread checkpoints
    • Summary
原文連結: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_backup

    CREATE 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_timeout

max_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的配置的其他重要部分。

繼續閱讀