如果没有时间线,会有什么问题?先举个将数据库恢复到以前时间点的例子。
假设在一个数据库的运行过程中,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造成影响。有了时间线,我们就可以恢复到之前的任何时间点。
在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
新的时间线会在什么情况下出现呢?
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,不能恢复到早于基本备份分支的时间点。
$ pg_ctl promote –D $PGDATA
这时候备库将会升为主备,同时产生一个新的timeline,同样生成一个新的history文件。
每次创建一个新的时间线,PostgreSQL都会创建一个“时间线历史”文件,文件名类似.history,它里面的内容是由原时间线history文件的内容再追加一条当前时间线切换记录。假设数据库恢复启动后,切换到新的时间线ID=5,那么文件名就是00000005.history ,该文件记录了自己从什么时间哪个时间线什么原因分出来的,该文件可能含有多行记录,每个记录的内容格式如下:
*
*
* 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