PostgreSQL時間線
-
-
- 時間線的引入
- wal日志名解析
- 新時間線的出現場景
- history檔案
-
時間線的引入
如果沒有時間線,會有什麼問題?先舉個将資料庫恢複到以前時間點的例子。
假設在一個資料庫的運作過程中,DBA在周三12:00AM删掉了一個關鍵的表,但是直到周五中午才發現這個問題。這個時候DBA拿出最初的資料庫備份,加上存在歸檔目錄的日志檔案,将資料庫恢複到周三11:00AM的時間點,這樣就能正常啟動和運作。
但是,DBA後來意識到這樣恢複是不對的,想恢複到周四8:00AM的資料。這時會發現無法做到:因為在資料庫不斷運作中,會産生與舊的WAL檔案重名的檔案,這些檔案進入歸檔目錄時,會覆寫原來的舊日志,導緻恢複資料庫需要的WAL檔案丢失。
為了避免這種情況,需要區分原始資料庫曆史生成的WAL檔案和完成恢複之後繼續運作産生的(重名的)新WAL檔案,如下圖。
為了解決這個問題,PostgreSQL引入了時間線的概念。每當歸檔檔案恢複完成後,建立一個新的時間線用來差別新生成的WAL記錄。
00000002.history
00000003.history
00000003000000000000001A
00000003000000000000001B
時間線ID号是WAL檔案名組成之一,是以一個新的時間線不會覆寫由以前的時間線生成的WAL。 如圖2所示,每個時間線類似一個分支,在目前時間線的操作不會對其他時間線WAL造成影響。有了時間線,我們就可以恢複到之前的任何時間點。
wal日志名解析
在PG中日志名是由16進制命名總共24個字元由三部分組成:
0000000100000001000000C4
00000001 //時間線ID
00000001 //LogId
000000C4 //logSeg
我們知道由三部分組成,那麼又是如何計算呢?公式如下:
WAL segment file name = timelineId +(uint32)LSN−1 / (16M ∗ 256) + (uint32)(LSN − 1 / 16M) % 256
我們通過源碼分析下:
// 這裡是計算一個logSegNo
#define XLByteToPrevSeg(xlrp, logSegNo) \
logSegNo = ((xlrp) - 1) / XLogSegSize
//XLOG_SEG_SIZE 定義
#define XLOG_SEG_SIZE (16 * 1024 * 1024)
//這塊是拼檔案名地方
#define XLogFilePath(path, tli, logSegNo) \
snprintf(path, MAXPGPATH, XLOGDIR "/%08X%08X%08X", tli,¦(uint32) ((logSegNo) / XLogSegmentsPerXLogId),¦(uint32) ((logSegNo) % XLogSegmentsPerXLogId))
LogId與logSeg 分别是LogSegNo除XLogSegmentsPerXLogId或者是對XLogSegmentsPerXLogId取模,那麼XLogSegmentsPerXLogId又是什麼呢?
//看到XLogSegmentsPerXLogId的定義我們可以自己計算下
#define XLogSegmentsPerXLogId (UINT64CONST(0x100000000) / XLOG_SEG_SIZE)
postgres=# select x'100000000'::bigint / (16 * 1024 * 1024);
?column?
----------
256
(1 row)
這個值就是256
新時間線的出現場景
新的時間線會在什麼情況下出現呢?
-
即時恢複(PITR)
配置recovery.conf檔案:
restore_command = 'cp /mnt/server/archivedir/%f %p' //從歸檔目錄恢複日志
recovery_target_time = '2015-7-16 12:00:00 ' //指定歸檔時間點,如沒指定恢複到故障前的最後一完成的事務
recovery_target_timeline = 'latest' //指定歸檔時間線,’latest’代表最新的時間線分支,如沒指定恢複到故障前的pg_control裡面的時間線
standby_mode = ‘off’ //打開後将會以備庫身份啟動,而不是即時恢複
設定好recovery.conf檔案後,啟動資料庫,将會産生新的timeline,而且會生成一個新的history檔案。恢複的預設行為是沿着與目前基本備份相同的時間線恢複。如果你想恢複到某些時間線,你需要指定的recovery.conf目标時間線recovery_target_timeline,不能恢複到早于基本備份分支的時間點。
-
standby promote
搭建一個PG主備,然後停止主庫,在備庫機器執行:
$ pg_ctl promote –D $PGDATA
這時候備庫将會升為主備,同時産生一個新的timeline,同樣生成一個新的history檔案。
history檔案
每次建立一個新的時間線,PostgreSQL都會建立一個“時間線曆史”檔案,檔案名類似.history,它裡面的内容是由原時間線history檔案的内容再追加一條目前時間線切換記錄。假設資料庫恢複啟動後,切換到新的時間線ID=5,那麼檔案名就是00000005.history ,該檔案記錄了自己從什麼時間哪個時間線什麼原因分出來的,該檔案可能含有多行記錄,每個記錄的内容格式如下:
* <parentTLI> <switchpoint> <reason>
*
* parentTLI ID of the parent timeline
* switchpoint XLogRecPtr of the WAL position where the switch happened
* reason human-readable explanation of why the timeline was changed
例如:
$ cat 00000004.history
1 0/140000C8 no recovery target specified
2 0/19000060 no recovery target specified
3 0/1F000090 no recovery target specified
當資料庫在從包含多個時間線的歸檔中恢複時,這些history檔案允許系統選取正确的WAL檔案。當然,它也能像WAL檔案一樣被歸檔到WAL歸檔目錄裡。曆史檔案隻是很小的文本檔案,是以儲存它們的代價很小。
當我們在recovery.conf指定目标時間線tli進行恢複時,程式首先尋找.history檔案,根據.history檔案裡面記錄的時間線分支關系,找到從pg_control裡面的startTLI到tli之間的所有時間線對應的日志檔案,再進行恢複。
參考:
[1]: https://yq.aliyun.com/articles/234
[2]: http://www.pgsql.tech/article_103_10000015