1. 程式人生 > 實用技巧 >PostgreSQL的WAL(3)--Checkpoint

PostgreSQL的WAL(3)--Checkpoint

我們已經熟悉了buffer cache的結構(共享記憶體的主要物件之一),並得出結論,要在所有RAM內容丟失後發生故障後恢復,必須保留預寫日誌(WAL)。

我們上次中斷的地方尚未解決的問題是,我們不知道在恢復期間從哪裡開始播放WAL記錄。從頭開始,這是不可行的:不可能從伺服器啟動時保留所有WAL記錄-這可能既需要巨大的記憶體,又要很長的恢復時間。我們需要找到一個點,並且可以從該位置開始恢復(並相應地安全刪除所有先前的WAL記錄)。這就是我們要講的檢查點。

檢查點

檢查點必須具備哪些特點呢? 我們必須確保所有從檢查點開始的WAL記錄都將應用於重新整理到磁碟的頁面。如果不是這種情況,則在恢復期間,我們可能從磁碟上讀取一個過舊的頁面版本,對其應用WAL記錄,這樣做會不可逆轉地損害資料。

我們如何獲得檢查點? 最簡單的選擇是不時暫停系統工作,並將緩衝區的所有髒頁和其他快取記憶體重新整理到磁碟。(請注意,僅寫入頁面,而不從快取記憶體中逐出頁面)這些點將滿足上述條件,但是時而連續"死亡"一段時間的系統,沒有人會滿意。

實際上這有點複雜:檢查點從一個點變成一個間隔。首先,我們啟動一個檢查點。之後,我們悄悄地將髒緩衝區重新整理到磁碟上,而不會中斷工作或在任何可能的情況下導致峰值負載。

當所有在檢查點開始時變髒的緩衝區都在磁碟上時,該檢查點被視為已完成。現在(但不是更早),我們可以使用開始時間作為開始恢復的時間。而且我們不再需要到現在為止建立的WAL記錄。

一個被稱作檢查點程序的後臺程序執行檢查點。

寫入髒緩衝區的持續時間由checkpoint_completion_target引數定義。它顯示了寫入完成後兩個相鄰檢查點之間的時間比例。預設值為0.5(如上圖所示),即兩次檢查之間的寫入時間佔一半。通常,此值增加到1.0,以實現更高的均勻性。

讓我們更詳細地看看執行檢查點時會發生什麼。

首先,檢查點將XACT緩衝區重新整理到磁碟。由於它們很少(只有128個),因此它們會立即被寫入。

然後,主要任務開始:從緩衝區快取記憶體中重新整理髒頁。正如我們已經提到的,由於緩衝區快取記憶體的大小可能很大,因此無法立即重新整理所有頁面。因此,buffer cache中所有當前髒的頁面都用位於header中的特殊標誌標記。

然後,檢查點程序遍歷所有緩衝區,並將標記的緩衝區重新整理到磁碟。這裡需要提醒你,頁面不會從快取記憶體中逐出,而只會寫入磁碟。因此,你不必關注緩衝區的使用計數或是否被pin。

自然,在執行檢查點時,buffer cache中的頁面仍會繼續被更新。但是不會標記新的髒緩衝區,並且檢查點程序不會將它們寫入磁碟。

在工作結束時,該過程將建立檢查點末尾的WAL記錄。該記錄包含檢查點開始時間的LSN。由於檢查點啟動時不會向WAL寫入任何內容,因此任何日誌記錄都可以位於此LSN上。

此外,最後完成的檢查點的指示在$PGDATA/global/pg_control檔案中更新。在檢查點完成之前,pg_control指向上一個檢查點。

為了觀看檢查點的工作,讓我們建立一個表。它的頁面將進入buffer cache並稱為髒頁:

=> CREATE TABLE chkpt AS SELECT * FROM generate_series(1,10000) AS g(n);
=> CREATE EXTENSION pg_buffercache;
=> SELECT count(*) FROM pg_buffercache WHERE isdirty;
 count
-------
    78
(1 row)

讓我們記住當前的WAL位置:

=> SELECT pg_current_wal_insert_lsn();
 pg_current_wal_insert_lsn
---------------------------
 0/3514A048
(1 row)

現在,讓我們手動執行檢查點,以確保快取記憶體中不留任何髒頁(正如我們已經提到的,可以出現新的髒頁,但是在上述情況下,執行檢查點時沒有更改):

=> CHECKPOINT;
=> SELECT count(*) FROM pg_buffercache WHERE isdirty;
 count
-------
     0
(1 row)

讓我們看一下檢查點在WAL中的體現:

=> SELECT pg_current_wal_insert_lsn();
 pg_current_wal_insert_lsn
---------------------------
 0/3514A0E4
(1 row)

postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/3514A048 -e 0/3514A0E4
rmgr: Standby     len (rec/tot):     50/    50, tx:          0, lsn: 0/3514A048, prev 0/35149CEC, desc: RUNNING_XACTS nextXid 101105 latestCompletedXid 101104 oldestRunningXid 101105
rmgr: XLOG        len (rec/tot):    102/   102, tx:          0, lsn: 0/3514A07C, prev 0/3514A048, desc: CHECKPOINT_ONLINE redo 0/3514A048; tli 1; prev tli 1; fpw true; xid 0:101105; oid 74081; multi 1; offset 0; oldest xid 561 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 101105; online

在這裡看到兩個記錄。最後一個是檢查點完成的記錄(CHECKPOINT_ONLINE)。在單詞“ redo”之後輸出檢查點開始的LSN,此位置對應於在檢查點開始時間的最後一個WAL記錄。

我們將在控制檔案中找到相同的資訊:

postgres$ /usr/lib/postgresql/11/bin/pg_controldata -D /var/lib/postgresql/11/main | egrep 'Latest.*location'
Latest checkpoint location:           0/3514A07C
Latest checkpoint's REDO location:    0/3514A048

恢復

現在,我們準備更準確地陳述上一篇文章中提及的恢復演算法。

如果postgresql server出現故障,則在隨後的啟動中,啟動過程會通過檢視pg_control檔案來查詢與“shutdown”狀態不同的狀態。在這種情況下,將執行自動恢復。

首先,恢復過程將從pg_control檔案讀取檢查點開始位置。(要完成此操作,如果backup_label檔案可用,那麼將從那裡讀取檢查點記錄-從備份中還原這是必需的,但這是另一個系列的主題。)

然後,恢復過程將從找到的位置開始讀取WAL,並將WAL記錄逐一應用於頁面(如果有需要,正如我們上次討論的那樣)。

最後,all unlogged tables are emptied by their initialization forks.。

這是啟動過程完成的工作,檢查點程序立即執行檢查點以保護磁碟上已還原的狀態。

我們可以通過強制在immediate模式下關閉來模擬故障。

student$ sudo pg_ctlcluster 11 main stop -m immediate --skip-systemctl-redirect

(這裡需要--skip-systemctl-redirect選項,因為我們使用安裝在Ubuntu上的PostgreSQL。它由pg_ctlcluster命令控制,該命令實際上呼叫systemctl,而後者又呼叫pg_ctl。但是--skip-systemctl-redirect選項使我們無需systemctl即可執行操作並保留重要資訊。)

讓我們檢查叢集的狀態:

postgres$ /usr/lib/postgresql/11/bin/pg_controldata -D /var/lib/postgresql/11/main | grep state
Database cluster state:               in production

啟動時,PostgreSQL知道發生了故障,需要恢復。

student$ sudo pg_ctlcluster 11 main start

postgres$ tail -n 7 /var/log/postgresql/postgresql-11-main.log
2019-07-17 15:27:49.441 MSK [8865] LOG:  database system was interrupted; last known up at 2019-07-17 15:27:48 MSK
2019-07-17 15:27:49.801 MSK [8865] LOG:  database system was not properly shut down; automatic recovery in progress
2019-07-17 15:27:49.804 MSK [8865] LOG:  redo starts at 0/3514A048
2019-07-17 15:27:49.804 MSK [8865] LOG:  invalid record length at 0/3514A0E4: wanted 24, got 0
2019-07-17 15:27:49.804 MSK [8865] LOG:  redo done at 0/3514A07C
2019-07-17 15:27:49.824 MSK [8864] LOG:  database system is ready to accept connections
2019-07-17 15:27:50.409 MSK [8872] [unknown]@[unknown] LOG:  incomplete startup packet

日誌中報告了需要恢復:資料庫系統未正確關閉; 自動恢復正在進行中 然後在«redo starts at»位置開始播放WAL記錄,並在可能獲取下一個WAL記錄的同時繼續播放。這樣就可以在«redo done at»位置完成恢復,並且DBMS開始與客戶端一起工作(資料庫系統已準備好接受連線)。

在伺服器正常關閉時會發生什麼? 要將髒頁重新整理到磁碟,PostgreSQL斷開所有客戶端的連線,然後執行最終檢查點。

讓我們記住當前的WAL位置:

=> SELECT pg_current_wal_insert_lsn();
 pg_current_wal_insert_lsn
---------------------------
 0/3514A14C
(1 row)

現在,我們以常規方式關閉:

student$ sudo pg_ctlcluster 11 main stop

檢查叢集狀態:

postgres$ /usr/lib/postgresql/11/bin/pg_controldata -D /var/lib/postgresql/11/main | grep state
Database cluster state:               shut down

而且WAL具有最終檢查點(CHECKPOINT_SHUTDOWN)的唯一記錄:

postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/3514A14C
rmgr: XLOG        len (rec/tot):    102/   102, tx:          0, lsn: 0/3514A14C, prev 0/3514A0E4, desc: CHECKPOINT_SHUTDOWN redo 0/3514A14C; tli 1; prev tli 1; fpw true; xid 0:101105; oid 74081; multi 1; offset 0; oldest xid 561 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; shutdown
pg_waldump: FATAL:  error in WAL record at 0/3514A14C: invalid record length at 0/3514A1B4: wanted 24, got 0

重新啟動例項:

student$ sudo pg_ctlcluster 11 main start

後臺寫

如我們所知,檢查點是將髒頁從buffer cache重新整理到磁碟的過程之一。但這不是唯一的。

如果後端程序需要從緩衝區重新整理頁面,但是該頁面含有髒資料,則該程序將不得不自行將頁面寫入磁碟。這種情況並不好,因為它需要等待-在後臺非同步完成寫入會更好。

因此,除了檢查點程序外,還存在後臺寫程序(也稱為bgwriter或僅稱為writer)。該程序使用與驅逐技術相同的演算法來搜尋緩衝區。他們有兩個區別。

1.後臺寫程序使用自己的指標,而不是指向«next victim»的指標。自己的指正可以在«next victim»的指標之前,但是永遠不能在它之後。 2.遍歷緩衝區時,使用計數不會減少。

被寫出的buffer要滿足以下條件:

·包含髒的資料

·沒有被pin住(pin計數為0)

·使用計數為0

因此,後臺寫過程先於eviction,找到很可能很快被逐出的緩衝區。理想的情況是,後臺寫必須能夠檢測到他們選擇的緩衝區可以被使用,而不會浪費寫入時間。

調優

通常根據以下推理來設定檢查點。

首先,我們需要確定在兩個檢查點之間可以負擔多少數量的WAL記錄(以及我們可以接受的恢復時間)。越多越好,但是出於明顯的原因,該值是有限的。

然後,我們可以計算出在正常負載下生成此數量的wal所需的時間。我們已經討論瞭如何執行此操作(我們需要記住WAL中的位置,並從另一個位置中減去一個)。

接著是檢查點之間的通常間隔。設定checkpoint_timeout引數。預設值為5分鐘,顯然太短;通常會增加到半個小時。

但是有可能(甚至可能)有時負載會比平時更高,並且在引數指定的時間內會生成過多的WAL記錄。在這種情況下,希望更頻繁地執行檢查點。為此,我們在max_wal_size引數中指定允許的WAL檔案大小。如果實際量更多,則伺服器將啟動計劃外的檢查點。

伺服器需要保留從最後一個完成的檢查點開始的WAL檔案以及當前檢查點期間累積的檔案。因此,可以將總數量估算為一個檢查點週期中的數量乘以(1 + checkpoint_completion_target)。在版本11之前,我們應該乘以(2 + checkpoint_completion_target),因為PostgreSQL還保留了最後一個檢查點中的檔案。

因此,大多數檢查點都按計劃執行:每個checkpoint_timeout時間單位一次。但是在負載增加時,達到max_wal_size的數量時,檢查點執行的頻率會更高。

重要的是要理解可以超過max_wal_size引數的值:

·max_wal_size引數的值僅是理想值,而不是嚴格的限制。實際可能超過該值。

·server不能擦除尚未通過複製槽傳遞、或尚未歸檔的的wal檔案,

可以通過min_wal_size引數指定最小值。

僅在調整檢查點時才調整後臺寫才有意義。

後臺寫一次最多寫bgwriter_lru_maxpages個頁,在下一次寫之前會根據bgwriter_delay的值sleep一段時間。

預設值為:bgwriter_delay = 200毫秒,bgwriter_lru_maxpages = 100。

如果根本找不到髒緩衝區(也就是說,系統中什麼也沒有發生),則它“進入休眠狀態”。

監控

你需要根據監控結果來調優檢查點程序和後臺寫。

如果wal數量太多,引數checkpoint_warning會輸出警告提醒,預設值是30秒,我們需要將其調整到checkpoint_timeout的值。

引數log_checkpoints可以將檢查點資訊寫入log。預設是不開啟

=> ALTER SYSTEM SET log_checkpoints = on;
=> SELECT pg_reload_conf();

現在,讓我們更改資料中的某些內容並執行檢查點。

=> UPDATE chkpt SET n = n + 1;
=> CHECKPOINT;

在看看log檔案的內容:

postgres$ tail -n 2 /var/log/postgresql/postgresql-11-main.log
2019-07-17 15:27:55.248 MSK [8962] LOG:  checkpoint starting: immediate force wait
2019-07-17 15:27:55.274 MSK [8962] LOG:  checkpoint complete: wrote 79 buffers (0.5%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.001 s, sync=0.013 s, total=0.025 s; sync files=2, longest=0.011 s, average=0.006 s; distance=1645 kB, estimate=1645 kB

我們可以在此處看到寫入了多少緩衝區,在檢查點之後更改了WAL檔案集,執行檢查點花費了多長時間以及相鄰檢查點之間的距離(以位元組為單位)。

但是,最有用的資訊可能是pg_stat_bgwriter檢視中檢查點和後臺寫程序的統計資訊。

=> SELECT * FROM pg_stat_bgwriter \gx
-[ RECORD 1 ]---------+------------------------------
checkpoints_timed     | 0
checkpoints_req       | 1
checkpoint_write_time | 1
checkpoint_sync_time  | 13
buffers_checkpoint    | 79
buffers_clean         | 0
maxwritten_clean      | 0
buffers_backend       | 42
buffers_backend_fsync | 0
buffers_alloc         | 363
stats_reset           | 2019-07-17 15:27:49.826414+03

其中:

·checkpoints_timed--按計劃(到達checkpoint_timeout時)。

·checkpoints_req--按需(包括在達到max_wal_size時執行的檢查)。該值越大,表明檢查點發生的越頻繁。

以下是有關寫入頁數的重要資訊:

·buffers_checkpoint--通過檢查點。

·buffers_backend--通過後端程序。

·buffers_clean--通過後臺寫程序。

在一個經過良好調整的系統中,buffers_backend的值必須小於buffers_checkpoint和buffers_clean的總和。

引數maxwrite_clean的值也將有助於調整後臺寫。它顯示由於超出bgwriter_lru_maxpages的值而使程序停止了多少次。

可以在重置收集的統計資訊:

=> SELECT pg_stat_reset_shared('bgwriter');

  

原文地址:https://habr.com/en/company/postgrespro/blog/494464/