復習下前面的知識點:
1、緩沖區高速緩存(Buffer Cache)位於服務器的共享內存中,並且所有進程均可訪問。在讀取或更新數據時,進程將頁面讀入緩存。當頁面位於緩存中時,我們在RAM中使用它並保存數據到磁盤。
2、當遇到掉電等故障場景,所有 RAM 內容丟失時,要在故障后恢復數據,pg必須維護一份預寫式日志(WAL),在故障恢復過程中通過重放WAL日志進行數據恢復。
checkpoint
本文需要討論的是,我們不知道在恢復期間從哪里開始重放 WAL 記錄。
方案一:
從頭開始恢復,帶來是問題是我們不一定保留了所有歷史的WAL日志記錄(保留所有歷史WAL會占用大量磁盤空間),即使我們通過歸檔保留了所有的WAL記錄,恢復的時長也會難以接受。
方案二:
我們需要一個逐漸向前推進的時間點,我們可以從該時間點開始恢復(並可以安全地刪除時間點以前的WAL 記錄),為此,pg引入了檢查點(checkpoint)。

當執行checkpoint后,會確保檢查點開始時所有臟的緩沖區都刷到磁盤上,此時checkpoint執行完成。后續我們可以使用checkpoint記錄的開始時間作為開始恢復的點。

checkpoint會執行多久
checkpoint執行后會帶來大量的頁面寫入操作,為了避免大量的頁面寫入對I/O造成沖擊,在檢查點期間寫入臟緩沖區的過程會分散為一段時間。該周期由checkpoint_completion_target控制,它是檢查點間隔的一部分,默認為0.5,也就是說每個checkpoint需要在checkpoints間隔時間的50%內完成。
通常checkpoint_completion_target值會增加到 0.9 以獲得更好的均勻性,PostgreSQL 14版本中會將默認值修改為0.9。
checkpoint執行過程
1、首先會對緩沖區中的臟頁進行標記

2、然后檢查指針遍歷所有緩存並將標記的臟頁刷新到磁盤(頁面不會從緩存中逐出,而只會寫入磁盤)。
3、新的臟緩沖區不會被標記,並且檢查指針不會寫入它們。

4、創建檢查點結束的 WAL 記錄,該記錄包含檢查點開始時間的 LSN。
5、更新控制文件信息($PGDATA/global/pg_control)。

演示
1、創建一個表;它的頁面將進入緩沖區緩存並變臟:
CREATE EXTENSION pg_buffercache; CREATE TABLE chkpt AS SELECT * FROM generate_series(1,10000) AS g(n); SELECT count(*) FROM pg_buffercache WHERE isdirty;
postgres=# SELECT count(*) FROM pg_buffercache WHERE isdirty; count ------- 62 (1 row)
2、記住當前的 WAL 位置
SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 3/8A0B3810 (1 row)
3、手動執行檢查點操作
CHECKPOINT; SELECT count(*) FROM pg_buffercache WHERE isdirty;
count ------- 0 (1 row)
4、看看檢查點是如何反映在 WAL 中的
SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn --------------------------- 3/8A0B38F8 (1 row)
可以看到lsn有更新,接下來用pg_waldump查看wal日志記錄了哪些內容
查看當前lsn對應的wal日志文件
SELECT file_name, upper(to_hex(file_offset)) file_offset FROM pg_walfile_name_offset('3/8A0B38F8');
file_name | file_offset --------------------------+------------- 00000001000000030000008A | B38F8 (1 row)
/usr/local/pgsql/bin/pg_waldump -p /usr/local/pgsql/data/pg_wal/ -s 3/8A0B3810 -e 3/8A0B38F8 00000001000000030000008A
rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 3/8A0B3810, prev 3/8A0B37D8, desc: RUNNING_XACTS nextXid 58215 latestCompletedXid 58214 oldestRunningXid 58215 rmgr: XLOG len (rec/tot): 114/ 114, tx: 0, lsn: 3/8A0B3848, prev 3/8A0B3810, desc: CHECKPOINT_ONLINE redo 3/8A0B3810; tli 1; prev tli 1; fpw true; xid 0:58215; oid 34053; multi 1; offset 0; oldest xid 479 in DB 1; oldest multi 1 in DB 12723; oldest/newest commit timestamp xid: 0/0; oldest running xid 58215; online
可以看到日志記錄了CHECKPOINT_ONLINE信息,checkpoint start的LSN在單詞“redo”之后輸出,這個位置對應checkpoint start時間最后一個WAL記錄。
5、查看控制文件信息,可知控制文件記錄了最近的checkpoint點對應的lsn信息
pg_controldata -D /usr/local/pgsql/data/ |egrep 'Latest.*location'
Latest checkpoint location: 3/8A0B3848 Latest checkpoint's REDO location: 3/8A0B3810
故障后恢復
如果服務器出現故障,則在后續啟動時,會通過查看pg_control控制文件,查找與“關閉”不同的狀態來檢測此情況。在這種情況下進行自動恢復。
1、直接kill掉postgres主進程
2、查看控制文件Database cluster state狀態,沒有發生變化
pg_controldata -D /usr/local/pgsql/data/ |grep state -------- Database cluster state: in production
3、啟動數據庫,查看日志
pg_ctl -D /usr/local/pgsql/data/ start

可知故障后,數據庫從Latest checkpoint's REDO location開始恢復數據
正常關閉后恢復
1、記錄當前的lsn
SELECT pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn
---------------------------
3/8B0000A0
(1 row)
2、正常關閉數據庫
pg_ctl -D /usr/local/pgsql/data/ stop
3、查看控制文件Database cluster state狀態,可知正確記錄了關閉狀態
pg_controldata -D /usr/local/pgsql/data/ |grep state
Database cluster state: shut down
4、查看wal日志, 擁有最終檢查點的唯一記錄(CHECKPOINT_SHUTDOWN)
/usr/local/pgsql/bin/pg_waldump -p /usr/local/pgsql/data/pg_wal/ -s 3/8B0000A0 00000001000000030000008B
rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 3/8B0000A0, prev 3/8B000028, desc: RUNNING_XACTS nextXid 58215 latestCompletedXid 58214 oldestRunningXid 58215 rmgr: XLOG len (rec/tot): 114/ 114, tx: 0, lsn: 3/8B0000D8, prev 3/8B0000A0, desc: CHECKPOINT_SHUTDOWN redo 3/8B0000D8; tli 1; prev tli 1; fpw true; xid 0:58215; oid 34053; multi 1; offset 0; oldest xid 479 in DB 1; oldest multi 1 in DB 12723; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; shutdown pg_waldump: fatal: error in WAL record at 3/8B0000D8: invalid record length at 3/8B000150: wanted 24, got 0
5、再次啟動數據庫,可知數據庫正常啟動,無恢復操作

