PgSQL · 特性分析 · checkpoint機製淺析
背景
上期月報PgSQL · 特性分析 · Write-Ahead Logging機製淺析中簡單介紹了PostgreSQL中WAL機製,其中講到如果是創建checkpoint會觸發刷新xlog日誌頁到磁盤,本文主要分析下PostgreSQL中checkpoint機製。
checkpoint又名檢查點,一般checkpoint會將某個時間點之前的髒數據全部刷新到磁盤,以實現數據的一致性與完整性。目前各個流行的關係型數據庫都具備checkpoint功能,其主要目的是為了縮短崩潰恢複時間,以Oracle為例,在進行數據恢複時,會以最近的checkpoint為參考點執行事務前滾。而在WAL機製的淺析中,也提過PostgreSQL在崩潰恢複時會以最近的checkpoint為基礎,不斷應用這之後的WAL日誌。
檢查點發生時機
在xlog.h文件中,有如下代碼對checkpoint進行了相應的分類:
/*
* OR-able request flag bits for checkpoints. The "cause" bits are used only
* for logging purposes. Note: the flags must be defined so that it's
* sensible to OR together request flags arising from different requestors.
*/
/* These directly affect the behavior of CreateCheckPoint and subsidiaries */
#define CHECKPOINT_IS_SHUTDOWN 0x0001 /* Checkpoint is for shutdown */
#define CHECKPOINT_END_OF_RECOVERY 0x0002 /* Like shutdown checkpoint,
* but issued at end of WAL
* recovery */
#define CHECKPOINT_IMMEDIATE 0x0004 /* Do it without delays */
#define CHECKPOINT_FORCE 0x0008 /* Force even if no activity */
/* These are important to RequestCheckpoint */
#define CHECKPOINT_WAIT 0x0010 /* Wait for completion */
/* These indicate the cause of a checkpoint request */
#define CHECKPOINT_CAUSE_XLOG 0x0020 /* XLOG consumption */
#define CHECKPOINT_CAUSE_TIME 0x0040 /* Elapsed time */
#define CHECKPOINT_FLUSH_ALL 0x0080 /* Flush all pages, including those
* belonging to unlogged tables */
也就是說,以下幾種情況會觸發數據庫操作係統做檢查點操作:
- 超級用戶(其他用戶不可)執行CHECKPOINT命令
- 數據庫shutdown
- 數據庫recovery完成
- XLOG日誌量達到了觸發checkpoint閾值
- 周期性地進行checkpoint
- 需要刷新所有髒頁
為了能夠周期性的創建檢查點,減少崩潰恢複時間,同時合並I/O,PostgreSQL提供了輔助進程checkpointer。它會對不斷檢測周期時間以及上麵的XLOG日誌量閾值是否達到,而周期時間以及XLOG日誌量閾值可以通過參數來設置大小,接下來介紹下與checkpoints相關的參數。
與檢查點相關參數
- checkpoint_segments
- WAL log的最大數量,係統默認值是3。超過該數量的WAL日誌,會自動觸發checkpoint。
- checkpoint_timeout
- 係統自動執行checkpoint之間的最大時間間隔。係統默認值是5分鍾。
- checkpoint_completion_target
- 該參數表示checkpoint的完成時間占兩次checkpoint時間間隔的比例,係統默認值是0.5,也就是說每個checkpoint需要在checkpoints間隔時間的50%內完成。
- checkpoint_warning
- 係統默認值是30秒,如果checkpoints的實際發生間隔小於該參數,將會在server log中寫入寫入一條相關信息。可以通過設置為0禁用。
創建檢查點具體過程
CreateCheckPoint具體過程
當PostgreSQL觸發checkpoint發生的條件後,會調用CreateCheckPoint函數創建具體的檢查點,具體過程如下:
- 遍曆所有的數據buffer,將髒頁塊狀態從BM_DIRTY改為BM_CHECKPOINT_NEEDED,表示這些髒頁將要被checkpoint刷新到磁盤
- 調用CheckPointGuts函數將共享內存中的髒頁刷出到磁盤
- 生成新的Checkpoint 記錄寫入到XLOG中
- 更新控製文件、共享內存裏XlogCtl的檢查點相關成員、檢查點的統計信息結構
PostgreSQL 控製文件pg_control裏存儲的數據是一個ControlFileData結構,具體如下:
typedefstruct ControlFileData
{
uint64 system_identifier;
uint32 pg_control_version; /*PG_CONTROL_VERSION */
uint32 catalog_version_no; /* seecatversion.h */
DBState state; /* see enum above */
pg_time_t time; /* time stamp of last pg_control update */
XLogRecPtr checkPoint; /* 最近一次創建checkpoint的LSN*/
XLogRecPtr prevCheckPoint; /* 最近一次之前創建checkpoint的LSN */
/*由於一個檢查點的時間比較長,所以有可能係統在所有頁麵寫完之前崩潰,這樣磁盤上的檢查點可能是不完全的,因此將最後一個完全檢查點位置寫在prevCheckPoint上*/
CheckPoint checkPointCopy; /* 最近一次checkpoint對應的CheckPoint對象 */
XLogRecPtr minRecoveryPoint;
TimeLineID minRecoveryPointTLI;
XLogRecPtr backupStartPoint;
XLogRecPtr backupEndPoint;
bool backupEndRequired;
......
其中,minRecoveryPoint和minRecoveryPointTLI確定數據庫啟動前,如果做歸檔恢複,我們必須恢複到的最小檢查點。其中minRecoveryPoint指向該檢查點對應的LSN位置,minRecoveryPointTLI指向該檢查點對應的時間線。其具體的用法,我們將在之後的PostgreSQL崩潰恢複中分析,這裏我們主要分析下PostgreSQL中的時間線概念。
PostgreSQL中WAL日誌段名稱,由時間線ID、日誌ID、段ID的八位16進製數依次構成。例如:
00000001 | 00000001 | 0000008F |
---|---|---|
時間線TimeLineID | 邏輯日誌ID | 段ID |
其中時間線是作為日誌段名稱的一部分,用來標識數據庫歸檔恢複後產生的一係列新的WAL記錄。在每次歸檔恢複完成後,都會產生一個新的時間線和新的WAL日誌段。時間線可以理解為平行時空中的各個平行宇宙,我們完全可以恢複到某個時間點,重開一條時間線,繼續進行數據操作,這樣就可以實現完全的PTIR。
在PostgreSQL中,一個新的時間線產生,係統伴隨它會建立一個以“新TimeLineID+.history”命名的“時間線曆史”文件(timeline history),它是一個類似於txt的文件,其中包含所有在當前時間線以前的時間線,同時記錄了每個時間線開始時的第一個WAL段,這樣數據庫恢複時,通過讀取時間線曆史文件文件,根據目標時間點可以快速找到正確的日誌段文件。如果上一次恢複是恢複到具體某時刻,在時間線曆史文件中還會記錄該時間線對應的具體時刻。
在PITR恢複時,無需掃描所有WAL日誌文件,而是通過時間線直接定位某個WAL段,再從該WAL段中找到符合該時間點的日誌記錄,這樣就大大提高了效率。同時數據庫恢複時,默認是沿著基備份開始時的時間點進行,即利用從基備份完成後產生的第一個日誌段文件做恢複,如果想恢複到指定時間點(時間線),需要在recovery.conf配置文件中設置目標時間線(target timeline ID),但是target timeline ID不能指定為基備份以前的時間線。
CheckPointGuts函數
CheckPointGuts函數將共享內存裏的數據刷出並文件同步到磁盤,具體定義如下:
staticvoid
CheckPointGuts(XLogRecPtrcheckPointRedo,int flags)
{
CheckPointCLOG();
CheckPointSUBTRANS();
CheckPointMultiXact();
CheckPointPredicate();
CheckPointRelationMap();
CheckPointBuffers(flags); /* performs all required fsyncs */
/* We deliberately delay 2PC checkpointingas long as possible */
CheckPointTwoPhase(checkPointRedo);
}
可以看出,CheckPointGuts根據不同的緩存類型,把clog、subtrans、multixact、predicate、relationmap、buffer(數據文件)和twophase相應緩存分別調用不同的方法,將緩存刷到磁盤中:
- 提交事務日誌管理器的方法CheckPointClog
- 子事務日誌管理器的方法CheckPointSUBTRANS
- 多事務日誌管理器的方法CheckPointMultiXact
- 支持序列化事務隔離級別的謂詞鎖模塊的方法CheckPointPredicate
- 目錄/係統表到文件節點映射模塊的方法CheckPointRelationMap
- 緩存管理器的方法CheckPointBuffers
- 兩階段提交模塊的方法CheckPointTwoPhase
其中,前四個函數最後都調用了SLRU模塊的SimpleLruFlush(簡單最近最少使用)方法,把相應的共享內存數據寫到磁盤,並通過調用pg_fsync方法把相應文件刷到磁盤上對應文件。
後二個函數沒有使用SLRU算法,直接調用pg_fsync方法把相應文件刷到磁盤上對應文件。
而目錄/係統表到文件節點映射模塊的方法CheckPointRelationMap,會將共享內存裏係統表和對應物理文件映射的map文件刷到磁盤。
總結
至此,我們大體了解了checkpoint的用法和整個實現過程,但是還需要對一些特別的地方做出說明。
- 每個檢查點後,第一次數據頁的變化會導致整個頁麵會被記錄在XLOG日誌中
- 檢查點的開銷比較高,可以用checkpoint_warning自檢,相應調大checkpoint_segments
- 檢查點的位置保存在文件 pg_control,pg_control文件被損壞可能會導致數據庫不可用
其中,如果pg_control文件損壞,在數據庫崩潰恢複時可能出現一些問題,這些問題我們將在分析PostgreSQL數據庫崩潰恢複時具體分析。
最後更新:2017-04-21 09:01:15