天天看點

PostgreSQL時間線

PostgreSQL時間線

      • 時間線的引入
      • wal日志名解析
      • 新時間線的出現場景
      • history檔案

時間線的引入

如果沒有時間線,會有什麼問題?先舉個将資料庫恢複到以前時間點的例子。

假設在一個資料庫的運作過程中,DBA在周三12:00AM删掉了一個關鍵的表,但是直到周五中午才發現這個問題。這個時候DBA拿出最初的資料庫備份,加上存在歸檔目錄的日志檔案,将資料庫恢複到周三11:00AM的時間點,這樣就能正常啟動和運作。

但是,DBA後來意識到這樣恢複是不對的,想恢複到周四8:00AM的資料。這時會發現無法做到:因為在資料庫不斷運作中,會産生與舊的WAL檔案重名的檔案,這些檔案進入歸檔目錄時,會覆寫原來的舊日志,導緻恢複資料庫需要的WAL檔案丢失。

為了避免這種情況,需要區分原始資料庫曆史生成的WAL檔案和完成恢複之後繼續運作産生的(重名的)新WAL檔案,如下圖。

PostgreSQL時間線

為了解決這個問題,PostgreSQL引入了時間線的概念。每當歸檔檔案恢複完成後,建立一個新的時間線用來差別新生成的WAL記錄。

00000002.history
00000003.history
00000003000000000000001A
00000003000000000000001B
           

時間線ID号是WAL檔案名組成之一,是以一個新的時間線不會覆寫由以前的時間線生成的WAL。 如圖2所示,每個時間線類似一個分支,在目前時間線的操作不會對其他時間線WAL造成影響。有了時間線,我們就可以恢複到之前的任何時間點。

PostgreSQL時間線

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
           

新時間線的出現場景

新的時間線會在什麼情況下出現呢?

  1. 即時恢複(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,不能恢複到早于基本備份分支的時間點。

  1. 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

繼續閱讀