博客 / 詳情

返回

PostgreSQL 中的“髒頁(Dirty Pages)”是什麼?

PostgreSQL 以固定大小的數據塊(Page)存儲數據,默認大小為 8 KB。當客户端執行更新或插入操作時,PostgreSQL 並不會立即將變更寫入磁盤,而是先將相關數據頁加載到共享內存(Shared Buffers)中,在內存中完成修改,並將該頁面標記為“髒頁”。所謂“髒頁”,是指內存中的頁面版本已經新於磁盤上的對應版本。

在向客户端返回操作結果之前,PostgreSQL 會先將變更記錄寫入預寫日誌(Write-Ahead Log,WAL),以保證即使數據庫發生崩潰也能恢復數據一致性。但實際的數據文件並不會立刻更新,只有在檢查點(Checkpoint)觸發或後台寫進程執行刷新時,髒頁才會被寫回磁盤。

在此之前,髒頁會持續累積在內存中,直到通過以下三種機制之一被刷新:

  • 後台寫進程(Background WriterBGWriter):一個常駐後台進程,在可用的乾淨緩衝區數量下降時,持續將髒頁寫入磁盤。
  • 檢查點進程(Checkpointer):在觸發檢查點時(如達到 checkpoint_timeout 或 WAL 超過 max_wal_size),將所有髒頁刷新到磁盤。
  • 後端進程(Backend):在緊急情況下(如共享緩衝區幾乎全部為髒頁),普通後端進程會自行寫髒頁,可能導致用户查詢阻塞。

理解併合理控制髒頁的刷新時機與方式,是優化 PostgreSQL 性能的關鍵。

髒頁為何影響性能

髒頁會從多個方面影響數據庫性能:

1. 檢查點期間的 I/O 峯值

檢查點發生時,所有髒頁都必須被刷新到磁盤。如果髒頁數量較多,會在短時間內產生大量磁盤 I/O,影響其他查詢性能。checkpoint_timeoutcheckpoint_completion_targetmax_wal_size 等參數決定了檢查點觸發的頻率以及刷新髒頁的節奏。

2. 後端寫入帶來的查詢阻塞

當共享緩衝區被大量髒頁佔滿,而 BGWriter 無法及時清理時,後端進程將被迫自行寫盤,直接阻塞正在執行的用户查詢。為避免此類情況,應通過合理的內存與刷新參數配置,讓 BGWriter 承擔絕大多數寫入工作。實踐中通常包括:

  • shared_buffers 分配足夠內存;
  • 調整 bgwriter_delaybgwriter_lru_maxpagesbgwriter_lru_multiplierbgwriter_flush_after 等參數,使髒頁持續、平穩地寫入磁盤;
  • 通過增大 checkpoint_timeout、提高 checkpoint_completion_target 以及增大max_wal_size,減少檢查點引發的突發寫入。

3. 吞吐量與崩潰恢復時間的權衡

較少的刷新頻率(如較大的 checkpoint_timeout)可以降低 I/O 開銷,但會增加數據庫崩潰後需要回放的 WAL 數量;更頻繁的刷新可以加快恢復速度,但可能降低運行時性能。合理的參數配置需要根據實際業務負載在兩者之間取得平衡。

PostgreSQL 管理髒頁的核心機制

後台寫進程(Background Writer,BGWriter)

BGWriter 是一個獨立進程,其目標是在後台持續寫出髒頁,保持一定數量的乾淨緩衝區可用。根據官方文檔描述:

  • 當共享緩衝區中可用的乾淨頁面數量低於閾值時,BGWriter 會寫出部分髒頁並將其標記為乾淨。
  • 若同一頁面在一個檢查點週期內被多次修改,可能會被多次寫盤,從而增加總體 I/O。

BGWriter 的主要配置參數(位於 postgresql.conf)包括:

圖片1.png

調優建議:

如果在 pg_stat_bgwriter 中觀察到後端進程寫盤較多,應適當提高 bgwriter_lru_maxpagesbgwriter_lru_multiplier;若 BGWriter 本身導致 I/O 過高,則可適當降低相關參數。通過調整 bgwriter_delay,在寫入頻率與 CPU 開銷之間取得平衡。

檢查點(Checkpointer)

檢查點觸發時,PostgreSQL 會將所有髒頁寫入磁盤,並在 WAL 中記錄檢查點位置。合理調整檢查點參數有助於平滑 I/O 壓力:

圖片2.png

通過增大 checkpoint_timeoutcheckpoint_completion_target,可以將寫入分散到更長時間窗口內;max_wal_size 決定了自動檢查點的觸發時機,從而間接影響髒頁刷新頻率。

共享緩衝區(Shared Buffers)

shared_buffers 決定了 PostgreSQL 可用於緩存數據頁和存放髒頁的內存大小。該參數直接影響頁面在內存中的停留時間以及寫盤頻率。

在專用數據庫服務器上,通常建議將其設置為物理內存的 25%~ 40%。當 shared_buffers 較大時,往往需要相應提高 max_wal_size,以避免檢查點過於頻繁。

shared_buffers 過小會導致髒頁頻繁被淘汰,引發後端寫盤;設置過大則可能在檢查點時一次性刷新大量頁面,造成 I/O 峯值。合理的 shared_buffers 配合 BGWriter 調優,可以顯著減少後端寫入。

自動清理(Autovacuum) 與 凍結清理(Vacuum Freeze)

自動清理(Autovacuum)在更新可見性信息或凍結元組(Freeze)時,也會產生髒頁。應確保 Autovacuum 運行頻率足以防止表膨脹,但又不過於激進,以免產生不必要的寫入。可根據業務負載調整 autovacuum_vacuum_cost_limitautovacuum_vacuum_scale_factor。在 SSD 環境下,適當提高 Autovacuum 強度通常是有益的。

面向性能的調優實踐

1. 先監控,再調優

通過 pg_stat_bgwriter 重點關注以下指標:

  • buffers_checkpoint:檢查點寫出的髒頁數量。
  • buffers_clean:BGWriter 寫出的頁面數量。
  • buffers_backend:後端進程寫出的頁面數量。

目標是將 buffers_backend 控制在接近 0 的水平。

2. 合理配置共享緩衝區大小

以物理內存的 25% 作為起點,根據負載特徵調整:

  • 若業務場景以讀操作為主,可適當增大共享緩衝區。
  • 若寫操作導致檢查點產生大幅性能波動,則需調小共享緩衝區。

3. 調整 BGWriter 參數

  • 降低 bgwriter_delay 參數值(如設置為 100 毫秒),提高後台寫入器的喚醒頻率。
  • 針對高寫入負載場景,增大 bgwriter_lru_maxpages(建議取值範圍 200–1000)與 bgwriter_lru_multiplier(建議取值範圍 3–4)參數,提升單次週期內的髒頁處理能力。
  • bgwriter_flush_after 參數設為與存儲系統最佳寫入粒度匹配的值;對於固態硬盤,512KB–1MB 為推薦取值區間。

4. 優化檢查點行為

  • 增大 checkpoint_timeout 參數,降低檢查點觸發頻率(建議取值範圍 15–60 分鐘)。
  • checkpoint_completion_target 參數提升至 0.7–0.9,使檢查點的寫入操作均勻分佈。
  • 增大 max_wal_size 參數,避免檢查點被過度頻繁觸發。

5. 避免後端寫入

buffers_backend 持續增長時,應優先增加 shared_buffers 或增強 BGWriter 的寫盤能力。後端寫盤往往是查詢延遲的主要來源。

6. 操作系統層面優化

  • Linux 系統中,應避免 vm.dirty_background_ratiovm.dirty_ratio 設置過高,以免內核長時間累積髒頁,造成突發寫回。
  • 關閉透明大頁(THP),並在內存充足的服務器上啓用靜態大頁(Huge Pages),以提升整體性能。

7. 持續評估與迭代

不同業務負載差異較大,應結合 pg_stat_activitypg_stat_bgwriter 以及 PostgreSQL 17 引入的 pg_stat_io 等視圖,持續評估調優效果,並逐步調整參數。

總結

“髒頁”本質上是 PostgreSQL 內存中等待寫回磁盤的已修改數據頁。通過髒頁機制,PostgreSQL 能夠合併寫入操作,並藉助 WAL 保證崩潰安全性。但如果相關參數配置不當,髒頁處理可能引發 I/O 峯值和查詢延遲。

深入理解共享緩衝區、後台寫進程、檢查點與 WAL 的協同機制,併合理調優 bgwriter_delaybgwriter_lru_maxpagesbgwriter_lru_multipliercheckpoint_timeoutshared_buffers 等關鍵參數,有助於在保障數據可靠性的同時,實現平穩、可預測的數據庫性能。

原文鏈接:

https://stormatics.tech/blogs/what-are-dirty-pages-in-postgresql

作者:Umair Shahid

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.