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/

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM