我們上次中斷的地方尚未解決的問題是,我們不知道在恢復期間從哪里開始播放WAL記錄。從頭開始,這是不可行的:不可能從服務器啟動時保留所有WAL記錄-這可能既需要巨大的內存,又要很長的恢復時間。我們需要找到一個點,並且可以從該位置開始恢復(並相應地安全刪除所有先前的WAL記錄)。這就是我們要講的檢查點。
檢查點
檢查點必須具備哪些特點呢? 我們必須確保所有從檢查點開始的WAL記錄都將應用於刷新到磁盤的頁面。如果不是這種情況,則在恢復期間,我們可能從磁盤上讀取一個過舊的頁面版本,對其應用WAL記錄,這樣做會不可逆轉地損害數據。
我們如何獲得檢查點? 最簡單的選擇是不時暫停系統工作,並將緩沖區的所有臟頁和其他高速緩存刷新到磁盤。(請注意,僅寫入頁面,而不從高速緩存中逐出頁面)這些點將滿足上述條件,但是時而連續"死亡"一段時間的系統,沒有人會滿意。
當所有在檢查點開始時變臟的緩沖區都在磁盤上時,該檢查點被視為已完成。現在(但不是更早),我們可以使用開始時間作為開始恢復的時間。而且我們不再需要到現在為止創建的WAL記錄。
一個被稱作檢查點進程的后台進程執行檢查點。
寫入臟緩沖區的持續時間由checkpoint_completion_target參數定義。它顯示了寫入完成后兩個相鄰檢查點之間的時間比例。默認值為0.5(如上圖所示),即兩次檢查之間的寫入時間占一半。通常,此值增加到1.0,以實現更高的均勻性。
讓我們更詳細地看看執行檢查點時會發生什么。
首先,檢查點將XACT緩沖區刷新到磁盤。由於它們很少(只有128個),因此它們會立即被寫入。
然后,檢查點進程遍歷所有緩沖區,並將標記的緩沖區刷新到磁盤。這里需要提醒你,頁面不會從高速緩存中逐出,而只會寫入磁盤。因此,你不必關注緩沖區的使用計數或是否被pin。
為了觀看檢查點的工作,讓我們創建一個表。它的頁面將進入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
我們將在控制文件中找到相同的信息:
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”狀態不同的狀態。在這種情況下,將執行自動恢復。
然后,恢復過程將從找到的位置開始讀取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
在服務器正常關閉時會發生什么? 要將臟頁刷新到磁盤,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文件集,執行檢查點花費了多長時間以及相鄰檢查點之間的距離(以字節為單位)。
=> 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/