一、原理
当执行promote
时,我们经常看到的结果是:生成一个新
XLOG
文件,名称为:时间线加
1
,段文件名为之前的段文件号。那么做这个动作的时机是什么时候呢?是否只有这一种现象,会不会有其他现象?先透露下,当执行
promote
动作前,最后一个
XLOG
日志是
SWITCH
时,段文件号会加
1
。下面我们对其流程做详细分析,并通过
gdb
理解其原理。
做这个动作的函数是
exitArchiveRecovery
,
调用时机为startup
进程退出的时刻,见堆栈:
Breakpoint 1, exitArchiveRecovery (endTLI=1, endOfLog=23053968) at xlog.c:5475 5475 InArchiveRecovery = false; (gdb) bt #0 exitArchiveRecovery (endTLI=1, endOfLog=23053968) at xlog.c:5475 #1 0x0815fc4b in StartupXLOG () at xlog.c:7460 #2 0x083dbc56 in StartupProcessMain () at startup.c:207 #3 0x08173cbf in AuxiliaryProcessMain (argc=2, argv=0xbfa8a2f4) at bootstrap.c:451 #4 0x083dab93 in StartChildProcess (type=StartupProcess) at postmaster.c:5386 #5 0x083d5d86 in PostmasterMain (argc=1, argv=0xa22e7e8) at postmaster.c:1369 #6 0x0831c76c in main (argc=1, argv=0xa22e7e8) at main.c:228
具体代码行为恢复完成之后:
StartupXLOG->
读取
checkpoint->
恢复
->exitArchiveRecovery
:
EndOfLog
为当前回放日志最后的位置,
EndOfLogTLI
为当前退出时回放日志的时间线。当
data
目录下有
standby.signal
文件即该机器是备时
ArchiveRecoveryRequested
为
TRUE
。
exitArchiveRecovery
函数调用流程如下:首先通过
endOfLog
即回放最后的位置计算出段文件日志号:
endLogSegNo= (endOfLog – 1) / (wal_segment_size)
;
startLogSegNo= endOfLog / (wal_segment_size)
。如果
endLogSegNo
等于
startLogSegNo
,表示回放位置为文件中间位置,在调用
XLogFileCopy
生成一个新文件,并将上个
XLOG
文件内容拷贝到新文件中;段文件号相同,时间线加
1
。如果
endLogSegNo
不等于
startLogSegNo
,即回放位置正好是文件大小的末尾处,或者正好是
SWITCH
这个日志,那么调用
XLogFileInit
函数进行初始化文件:
XlogFileCopy
函数调用:调用
XLogFilePath
函数获取源
XLOG
文件名,
OpenTransientFile
打开该文件,创建并打开一个临时
XLOG
文件
pg_wal/xlogtemp.pid
,
pid
为
startup
进程的
ID
号。
sizeof(buffer)
为一页大小
8192
字节,从源文件每次
read
一页数据并将之
write
到
xlogtemp
文件,最后一页数据如果不够
8192
字节,则有多少读取多少并写入文件。当文件拷贝完成后,执行一次
sync
。最后调用
InstallXLogFileSegment
将文件重命名。
InstallXLogFileSegment
函数:
XlogFileCopy
调用时,
find_free
为
false
,直接将文件重命名为时间线加
1
的文件名;
XLogFileInit
调用时为
TRUE
,将段文件号加
1
后(注意这里不是加
1
,是因为正好是文件末尾,求得的是下一个段文件号,只是现象上看是加
1
),重命名为时间线加
1
的文件,会先
stat
下这个文件,该流程返回值是
2
即该文件不存在,所以不会再将
segno
加
1
,直接跳过虚框内的步骤,进入重命名流程
durable_like_or_rename
。
XLogFileInit
函数的调用:首先获取新文件的文件名,即时间线加
1
,段文件名为原文件名,本次
exitArchiveRecovery
函数的调用流程中,
use_existent
为
TRUE
所以会视图打开该文件。当然因为该文件不存在所以打开失败。然后创建并打开一个临时文件
xlogtemp.pid
,将该文件全部清
0
,最后
sync
。之后调用
InstallXLogFileSegment
函数重命名。最后打开新文件以供之后使用。
二、GDB跟踪-lsn位置在xlog文件中间
1
、进入第一个断点,即函数入口
Breakpoint 1, exitArchiveRecovery (endTLI=1, endOfLog=23053968) at xlog.c:5475 5475 InArchiveRecovery = false; (gdb) bt #0 exitArchiveRecovery (endTLI=1, endOfLog=23053968) at xlog.c:5475 #1 0x0815fc4b in StartupXLOG () at xlog.c:7460 #2 0x083dbc56 in StartupProcessMain () at startup.c:207 #3 0x08173cbf in AuxiliaryProcessMain (argc=2, argv=0xbfa8a2f4) at bootstrap.c:451 #4 0x083dab93 in StartChildProcess (type=StartupProcess) at postmaster.c:5386 #5 0x083d5d86 in PostmasterMain (argc=1, argv=0xa22e7e8) at postmaster.c:1369 #6 0x0831c76c in main (argc=1, argv=0xa22e7e8) at main.c:228
2
、接着计算出段文件号,这两个值相等,即执行
promote
时,
lsn
最后位置在文件中间。
5507 if (endLogSegNo == startLogSegNo) (gdb) 5517 XLogSegmentOffset(endOfLog, wal_segment_size)); (gdb) p endLogSegNo $1 = 1 (gdb) p startLogSegNo $2 = 1
3
、
lsn
在文件中间,调用
XlogFileCopy
,
upto
为
lsn
在最后文件的偏移
XLogFileCopy (destsegno=1, srcTLI=1, srcsegno=1, upto=6276752) at xlog.c:3405 3405 XLogFilePath(path, srcTLI, srcsegno, wal_segment_size); (gdb) p 23053968%(16*1024*1024) $3 = 6276752
4
、打开原文件
00000001
0000000000000001
,以及临时文件
xlogtemp.29683
3406 srcfd = OpenTransientFile(path, O_RDONLY | PG_BINARY); (gdb) p path $4 = "pg_wal/00000001", '0' <repeats 15 times>, "1", '\000' <repeats 869 times>, "\b\221\250\277gmE\b\004\005ĥ\000\000\000\001\030\221\250\277\bnE\b\004\005ĥ\000\000\000\001X\221\250\277$\207E\b\004\005ĥ\000\000\000\001", '\000' <repeats 13 times>"\270, \004\246X\221\250\277\000\000\000\000\000\000\000 \000\270\004\000\000\000\000\000\000\000\000\000\n\000\000\000\000\270\004\246\270\221\250\277dd\025\b\000\005ĥ\352\320a\b\341\n\000\000~\016b\b\000\000\000" (gdb) n (gdb) p tmppath $5 = "pg_wal/xlogtemp.29683", '\000' <repeats 1002 times> (gdb) n 3420 fd = OpenTransientFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
5
、循环进行拷贝,一次拷贝一页
8192
字节
3429 for (nbytes = 0; nbytes < wal_segment_size; nbytes += sizeof(buffer)) (gdb) p sizeof(buffer) $6 = 8192 (gdb) n 3433 nread = upto - nbytes; (gdb) 3439 if (nread < sizeof(buffer)) (gdb) p nread $7 = 6276752 (gdb) p 6276752/8192 $8 = 766
6
、
InstallXLogFileSegment
函数重命名,
path
为
000000020000000000000001
Breakpoint 2, InstallXLogFileSegment (segno=0xbfa86968, tmppath=0xbfa88974 "pg_wal/xlogtemp.29683", find_free=false, max_segno=0, use_lock=false) at xlog.c:3545 3545 XLogFilePath(path, ThisTimeLineID, *segno, wal_segment_size); (gdb) p path $10 = "pg_wal/00000002", '0' <repeats 15 times>, "1\000 \212\022\251\322\"[\000\000\000\000\000\277 `\b\204\211\250\277\347e\250\277\005\000\000\000\003\000\000\000 NҬ\003\000\000\000\002\000\000\000\005\000\000\000;\000\000\000\000\000\000\000\030f\250\277\206\032`\b\000\000\000\000\350h\250\277\350h\250\277\350h\250\277\240.{\262d\000\000\000\363s\000\000\000\000\000\000\240\020[\264\003\000\000\000\000\000\000\000\003\000\000\000\240\362:\266\003\000\000\000\220\251t\267\003\000\000\000\240\324\032\270\003\000\000\000\220\213T\271\003\000\000\000 \361\003\272\003\000\000\000\020\250=\273\003\000\000\000 \323\343\273\322\"[29683\000\000\000\000\n", '\000' <repeats 15 times>, "\001\000\000\000\373\333y\b\005\000\000\000\000\000\000\000\001\000\000\000\270\240\250\277\310h\250\277\225\017`\b\363s\000\000\000\000\000\000d", '\000' <repeats 27 times>"\350"... (gdb) n
7
、将临时文件重命名为
000000020000000000000001
3579 if (durable_link_or_rename(tmppath, path, LOG) != 0) (gdb) p path $11 = "pg_wal/00000002", '0' <repeats 15 times>, "1\000 \212\022\251\322\"[\000\000\000\000\000\277 `\b\204\211\250\277\347e\250\277\005\000\000\000\003\000\000\000 NҬ\003\000\000\000\002\000\000\000\005\000\000\000;\000\000\000\000\000\000\000\030f\250\277\206\032`\b\000\000\000\000\350h\250\277\350h\250\277\350h\250\277\240.{\262d\000\000\000\363s\000\000\000\000\000\000\240\020[\264\003\000\000\000\000\000\000\000\003\000\000\000\240\362:\266\003\000\000\000\220\251t\267\003\000\000\000\240\324\032\270\003\000\000\000\220\213T\271\003\000\000\000 \361\003\272\003\000\000\000\020\250=\273\003\000\000\000 \323\343\273\322\"[29683\000\000\000\000\n", '\000' <repeats 15 times>, "\001\000\000\000\373\333y\b\005\000\000\000\000\000\000\000\001\000\000\000\270\240\250\277\310h\250\277\225\017`\b\363s\000\000\000\000\000\000d", '\000' <repeats 27 times>"\350"... (gdb) n
三、GDB跟踪-lsn位置在xlog文件尾或最后一个为SWITCH
1
、
lsn
位于文件尾,调用函数
XLogFileInit
Breakpoint 3, exitArchiveRecovery (endTLI=3, endOfLog=50331648) at xlog.c:5475 5475 InArchiveRecovery = false; (gdb) n 5480 UpdateMinRecoveryPoint(InvalidXLogRecPtr, true); (gdb) 5486 if (readFile >= 0) (gdb) 5488 close(readFile); (gdb) 5489 readFile = -1; (gdb) 5498 XLByteToPrevSeg(endOfLog, endLogSegNo, wal_segment_size); (gdb) 5499 XLByteToSeg(endOfLog, startLogSegNo, wal_segment_size); (gdb) 5507 if (endLogSegNo == startLogSegNo) (gdb) 5525 bool use_existent = true; (gdb) 5528 fd = XLogFileInit(startLogSegNo, &use_existent, true); (gdb)
2
、
path
为
000000040000000000000003,
该文件不存在
Breakpoint 2, XLogFileInit (logsegno=3, use_existent=0xbfbd366f, use_lock=true) at xlog.c:3216 3216 XLogFilePath(path, ThisTimeLineID, logsegno, wal_segment_size); (gdb) n 3221 if (*use_existent) (gdb) p path $1 = "pg_wal/00000004", '0' <repeats 15 times>, "3\000\000\000\000\000\346!`\bX6\275\277\060\000\000\000\a\000\000\000\030\333\177\t\364\257`\000\270\331\177\t\000\000\000\000\a\000\000\000\000\224M\000\000\000\000\000h3\275\277: `\b4\000\000\000\330\065\275\277\330\065\275\277\000\000\000\000h3\275\277L\274M\000\004\000\000\000\000\000\000\000?\000\000\000\a\000\000\000\b3\275\277\206\032`\b\000\000\000\000\330\065\275\277\330\065\275\277\330\065\275\277\373\210}\tX\025w\b\004\000\000\000\000\000\000\000\177\323a\bX\000\000\000\000\000\000\000\b", '\000' <repeats 19 times>, "\b\000\000\000\000\000\000\000\060", '\000' <repeats 11 times>, "recovering 00000\322\"[\000\000\270\004\246\277 `\b`6\275\277\360\253a\b\b", '\000' <repeats 11 times>... (gdb) n 3223 fd = BasicOpenFile(path, O_RDWR | PG_BINARY | get_sync_bit(sync_method)); (gdb) 3224 if (fd < 0) (gdb) 3226 if (errno != ENOENT) (gdb) p fd $2 = -1 (gdb) p errno $3 = 2
3
、创建并打开临时文件,将
zbuffer.data
清
0
,然后一页一页的将文件清
0
3243 snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid()); (gdb) 3245 unlink(tmppath); (gdb) 3248 fd = BasicOpenFile(tmppath, O_RDWR | O_CREAT | O_EXCL | PG_BINARY); (gdb) 3249 if (fd < 0) (gdb) 3254 memset(zbuffer.data, 0, XLOG_BLCKSZ); (gdb) p fd $4 = 3 3269 for (nbytes = 0; nbytes < wal_segment_size; nbytes += XLOG_BLCKSZ) (gdb) 3271 errno = 0; (gdb) 3272 if (write(fd, zbuffer.data, XLOG_BLCKSZ) != XLOG_BLCKSZ)
4
、进入
InstallXLogFileSegment
函数,
000000040000000000000003
文件
stat
失败,调用
durable_link_or_rename
重命名。
InstallXLogFileSegment (segno=0xbfbd0de0, tmppath=0xbfbd2de8 "pg_wal/xlogtemp.31765", find_free=true, max_segno=45, use_lock=true) at xlog.c:3550 3550 if (use_lock) (gdb) n 3551 LWLockAcquire(ControlFileLock, LW_EXCLUSIVE); (gdb) 3553 if (!find_free) (gdb) 3561 while (stat(path, &stat_buf) == 0) (gdb) p path $5 = "pg_wal/00000004", '0' <repeats 15 times>, "3\000 \212\022\251\322\"[\000\000\000\000\000\277 `\b\370-\275\277W\n\275\277\005\000\000\000\003\000\000\000 NҬ\003\000\000\000\003\000\000\000\005\000\000\000;\000\000\000\000\000\000\000\210\n\275\277\206\032`\b\000\000\000\000X\r\275\277X\r\275\277X\r\275\277\240.{\262d\000\000\000\025|\000\000\000\000\000\000\240\020[\264\003\000\000\000\000\000\000\000\003\000\000\000\240\362:\266\003\000\000\000\220\251t\267\003\000\000\000\240\324\032\270\003\000\000\000\220\213T\271\003\000\000\000 \361\003\272\003\000\000\000\020\250=\273\003\000\000\000 \323\343\273\322\"[31765\000\000\000\000\n", '\000' <repeats 15 times>, "\001\000\000\000\373\333y\b\005\000\000\000\000\000\000\000\003\000\000\000(E\275\277\070\r\275\277\225\017`\b\025|\000\000\000\000\000\000"... (gdb) n 3579 if (durable_link_or_rename(tmppath, path, LOG) != 0) (gdb) p errno $6 = 2