XLOG段文件跳号现象分析

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: XLOG段文件跳号现象分析

一、原理


当执行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->恢复->exitArchiveRecoveryEndOfLog为当前回放日志最后的位置,EndOfLogTLI为当前退出时回放日志的时间线。当data目录下有standby.signal文件即该机器是备时ArchiveRecoveryRequestedTRUE


    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.pidpidstartup进程的ID号。sizeof(buffer)为一页大小8192字节,从源文件每次read一页数据并将之writexlogtemp文件,最后一页数据如果不够8192字节,则有多少读取多少并写入文件。当文件拷贝完成后,执行一次sync。最后调用InstallXLogFileSegment将文件重命名。

    InstallXLogFileSegment函数:XlogFileCopy调用时,find_freefalse,直接将文件重命名为时间线加1的文件名;XLogFileInit调用时为TRUE,将段文件号加1后(注意这里不是加1,是因为正好是文件末尾,求得的是下一个段文件号,只是现象上看是加1),重命名为时间线加1的文件,会先stat下这个文件,该流程返回值是2即该文件不存在,所以不会再将segno1,直接跳过虚框内的步骤,进入重命名流程durable_like_or_rename

    XLogFileInit函数的调用:首先获取新文件的文件名,即时间线加1,段文件名为原文件名,本次exitArchiveRecovery函数的调用流程中,use_existentTRUE所以会视图打开该文件。当然因为该文件不存在所以打开失败。然后创建并打开一个临时文件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

      3lsn在文件中间,调用XlogFileCopyuptolsn在最后文件的偏移


      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、打开原文件000000010000000000000001,以及临时文件xlogtemp.29683


      340  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

        6InstallXLogFileSegment函数重命名,path000000020000000000000001

          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


          1lsn位于文件尾,调用函数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) 

            2path000000040000000000000003,该文件不存在


            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\000h2\275\277: `\b4\000\000\000\330\065\275\277\330\065\275\277\000\000\000\000h2\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.data0,然后一页一页的将文件清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
              相关实践学习
              阿里云图数据库GDB入门与应用
              图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
              目录
              相关文章
              |
              2月前
              crash —— 获取物理内存布局信息
              crash —— 获取物理内存布局信息
              |
              6月前
              |
              存储 Oracle 关系型数据库
              Oracle的段:深入数据段与日志段的奥秘
              【4月更文挑战第19天】Oracle数据库中的数据段和日志段是存储管理的核心。数据段存储表和索引的实际数据,随数据增长动态调整;日志段记录变更历史,保障数据完整性和恢复。两者协同工作,确保数据库稳定性和并发控制。了解和优化它们的配置能提升数据库性能和可靠性,为业务发展提供支持。
              |
              6月前
              |
              Go 文件存储 iOS开发
              LabVIEW崩溃后所产生的错误日志文件的位置
              LabVIEW崩溃后所产生的错误日志文件的位置
              158 0
              |
              消息中间件 关系型数据库 Shell
              记录贴:sentry磁盘占用过大如何清理?
              记录贴:sentry磁盘占用过大如何清理?
              1173 0
              |
              SQL 安全 关系型数据库
              需要binlog的场景下,“暴力”干掉历史binlog文件,尽情释放磁盘空间
              需要binlog的场景下,“暴力”干掉历史binlog文件,尽情释放磁盘空间
              104 0
              |
              存储 API C++
              dump系列(2)C++程序异常或内存错误,导致闪退的解决办法:分析dump文件
              dump系列(2)C++程序异常或内存错误,导致闪退的解决办法:分析dump文件
              1783 0
              段错误(核心已转储)问题的分析方法(未成功)
              段错误(核心已转储)问题的分析方法(未成功)
              256 0
              |
              SQL 数据库 虚拟化
              备份链中断导致差异备份报错案例
              原文:备份链中断导致差异备份报错案例   最近一台SQL Server服务器部署SQL Server Backup后,发现每晚的差异备份老是失败,报如下错误:   Msg 3035, Level 16, State 1, Line 1 无法执行数据库"xxxx" 的差异备份,因为不存在当前数据库备份。
              1411 0
              |
              SQL 关系型数据库