暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

【干货分享】深度解析Write-Ahead Logging

云贝教育 2021-10-28
1372

点击上方蓝色文字关注我们哦


内容简介

1.Write-Ahead Logging概览

2.Transaction log

3.FPI

4.WAL文件与记录的内部结构

5.事务日志文件管理

6.实例恢复

7.归档

8.WAL写放大浅析

9.walminer、pg_waldump、wal2json工具浅析‍





Write-Ahead Logging概览作用

  • 故障中,保证数据不丢失

  • Point-in-Time Recovery PITR

  • Streaming Replication SR

  • 记录数据库所有的变更和行为


数据变更发生时:

        先将变更后内容记入预写日志缓冲区

        再将更新后的数据写入数据缓冲区

提交时:

        预写日志缓冲区刷新到Disk

        数据缓冲区写磁盘推迟

检查点发生时:

        将所有数据缓冲区刷新到磁盘


Crash后的实例恢复

PostgreSQL 7.1之前的Crash

插入数据,页面未被刷新回磁盘,缓冲区中页面被弄脏。
继续插入数据,页面仍然未被刷新回磁盘
OS  OR  PG Server Crash 后缓冲区中的数据丢失
没有WAL的DBMS是极度不安全的。


PostgreSQL 7.1之后的写操作


    1.检查点进程周期性运行,启动时会向WAL文件写入一条记录,记录包含redo point位置。(LSN位置)
    2.插入1条数据,页面被加载到共享缓冲区,向该页面中插入一条元组,在LSN_1的位置插入一条WAL记录,在将表A的LSN从LSN_0变更为LSN_1。
    3.当事务Commit时,WAL缓冲区会记录一条关于Commit行为的记录。再将WAL缓冲区中的XLOG记录写入到WAL段文件中。
    4.插入第2条数据,向页面中插入新的元组,在LSN_2的位置插入一条新的WAL记录,在将表A的LSN从LSN_1变更为LSN2。
    5.PG Server Crash时,尽管共享缓冲区中的所有数据将丢失,但是所有页面的修改,已经记录到了WAL段文件中。



    概念:

    Log Sequencee Number  LSN日志序列号,标识了记录在事物日志中的位置,也代表WAL记录的唯一标识。
    WAL写入时机,增删改时WAL记录写入WAL缓冲区,事物提交或终止时,被写入WAL段文件中。
    重做点 Redo Point,最新的检查点开始时XLOG记录的写入位置(LSN)。
    PostgreSQL实例恢复,是从哪一点开始恢复的?重做点 Redo Point。
      src/include/storage/bufpage.h


        pd_lsn - identifies xlog record for last change to this page.
        标识对该页的最后更改的xlog记录。


        PostgreSQL 7.1之后的恢复


        1.从重做点开始,依序读取正确的WAL段文件并重放XLOG记录。
        2.从WAL段文件中读取第一条执行的insert的xlog记录,并将需要恢复的Page页面加载到共享缓冲区中。
        3.重放xlog记录前,会比较xlog记录的LSN与相应页面的LSN。
        a.如果XLOG记录的LSN比页面LSN大,XLOG记录中的数据部分就会被插入页面中,并将页面的LSN更新为XLOG记录的LSN。
        b.如果XLOG记录的LSN比页面的LSN小,那么什么也不用做。
        通过按照时间顺序重放写在WAL段文件中的XLOG记录来自我恢复。WAL可以理解成是一种重做日志,是用来做事物的前滚用的。




        引入全页写后的Crash恢复

        1、块折断

        • full_page_writes作用:

        是否开启全页写入,此参数是为了防止块折断(块损坏)的一种策略。
        • 造成块折断的原因:

        linux操作系统文件系统一个块一般是4k,而数据库则一般是一个块8k,当数据库的脏块刷新到磁盘上时,由于底层是两个块组成的,比如刷第一个操作系统块到磁盘上了,而当刷第二个操作系统块的时候发生了停电等突然停机事故,则就发生了块折断(数据块是否折断是根据块的checksum值来检查的)。
        • pg采用的机制:

        当checkpoint后的一个块第一次变脏后就要整块写入到wal日志中,后续继续修改此块则只把修改的信息写入wal日志中,如果在此过程中发生了停电,则实例启动后会从checkpoint检查点,之后开始进行实例恢复,如果有块折断,则在全页写入的块为基础进行恢复,最后覆盖磁盘上的折断块,所以当每次checkpoint后如果数据有修改都会进行全页写入。
        参数checkpoint_segments对checkpoint影响
        参数checkpoint_segments控制checkpoint的间隔,如果checkpoint_segments设置太小就会造成频繁的checkpoint,进而导致写入了过多的全页,可能会造成wal日志的暴增.如果设置的过大,恢复时间会变长。
        mysql为了防止块折断采用了double write,oracle采用了redo+undo机制,其中undo记录了前镜像,而redo则既记录了修改数据又记录了undo块。


        引入全页写(FPW)后的数据写入


        • 概念

        1.在写入脏页面的过程中,出现了宕机,导致页面损坏。WAL是无法在损坏的页面上重放的。
        2.当一次检查点之后,页面第一次被修改时,页面+页面头部信息会作为XLOG日志写入到WAL段文件中。(整页镜像)
        • 解析

        检查点后,页面被第一次修改~~~
        1.插入1条数据,页面被加载到共享缓冲区,向该页面中插入一条元组,在LSN_1的位置插入一条WAL记录(整页镜像),在将表A的LSN从LSN_0变更为LSN_1。
        2.当事务Commit时,WAL缓冲区会记录一条关于Commit行为的记录。再将WAL缓冲区中的XLOG记录写入到WAL段文件中。
        3.插入第2条数据,向页面中插入新的元组,在LSN_2的位置插入一条新的WAL记录,在将表A的LSN从LSN_1变更为LSN2。


        引入全页写(FPW)后的Crash恢复


        1.读取第1条记录,对应的xlog事务日志,加载对应的页面到共享缓冲区,按照整页写的规则,这条事物日志是一个全页镜像(备份区块)。
        2.当一条XLOG日志是全页镜像(备份区块)时,和非全页镜像是有区别,XLOG记录的数据部分会直接覆盖当前页面。无视页面或XLOG记录中的LSN,然后将页面的LSN号更新为XLOG记录的LSN号。
        3.第2条记录是非全页镜像,比对WAL的LSN和Page中的LSN号,将元组写入Page中。


        检查点

        检查点作用

        1.将事务提交的修改写进disk(写脏数据);保证数据库的完整性和一致性。 

        2.缩短恢复时间,将脏页写入相应的数据文件,确保修改后的文件通过fsync()写入到磁盘。


        检查点触发条件

        1.checkpoint_timeout 设置的间隔时间自上一个检查点已经过去(默认间隔为 300 秒(5 分钟))。
        2.在 9.4 或更早版本中,为checkpoint_segments设置的 WAL 段文件的数量自上一个检查点以来已经被消耗(默认数量为 3)。
        3.在 9.5 或更高版本中,pg_xlog(在 10 或更高版本中为 pg_wal)中的 WAL 段文件的总大小已超过参数max_wal_size的值(默认值为 1GB(64 个文件))。
        4.PostgreSQL 服务器在smart或fast模式下停止。
        5.当超级用户手动发出 CHECKPOINT 命令时,它的进程也会这样做。
        6.写入WAL的数据量已达到参数max_wal_size(默认值:1GB)
        7.执行pg_start_backup函数时
        8.在进行数据库配置时(例如CREATE DATABASE DROP DATABASE语句)


        pg_crontrol文件

        由于pg_control 文件包含检查点的基本信息,因此它对于数据库恢复当然是必不可少的。
        如果损坏或无法读取,则无法启动恢复过程,从而无法获得起点。

          pg_control输出项说明
          pg_control version number: 是控制文件版本号。
          Catalog version number: 是系统表版本号,格式是yyyymmddN。
          Database system identifier: 数据库系统号 这个标识串是一个64bit的整数。
          Database cluster state: 记录实例的状态。源码文件中看到数据库的几种状态:
          starting up:表示数据库正在启动状态。
          shut down:数据库实例(非Standby)正常关闭后控制文件中就是此状态。
          shut down in recovery:Standby实例正常关闭后控制文件中就是此状态。
          shutting down:正常停库时,先做checkpoint,开始做checkpoint时,会把状态设置为此状态,
          做完后把状态设置为shut down。
          in crash recovery:数据库实例非异常停止后,重新启动后,会先进行实例的恢复,在实例恢复时的状态就是此状态。
          in archive recovery:Standby实例正常启动后,就是此状态。
          in production:数据库实例正常启动后就是此状态。Standby数据库正常启动后不是此状态
          pg_control last modified: 记录控制文件最后更新的时间。
          Latest checkpoint location: 数据库异常停止后再重新启动时,需要做实例恢复,实例恢复的过程是从WAL日志中,
          找到最后一次的checkpoint点,最后一次的checkpoint点的信息记录在Latest checkpoint项中。
          Latest checkpoint‘s REDO location: 记录最近一次检查点时,Xlog的LSN号。
          Latest checkpoint's REDO WAL file: 记录WAL日志名,目录下pg_wal可以查到文件。
          Latest checkpoint's TimeLineID: 3 当前时间线
          Latest checkpoint's PrevTimeLineID: 3
          Latest checkpoint ' s full_page_writes: on 数据库参数 全页写
          Latest checkpoint's NextXID: 0:1048576
          Latest checkpoint's NextOID: 22051 下一个OID(OID,object 是pg内部使用,作为系统表的主键)
          Latest checkpoint's NextMultiXactId: 65536 多事务ID
          Latest checkpoint's NextMultiOffset: 0 多事务偏移量
          Latest checkpoint's oldestXID: 480
          Latest checkpoint's oldestXID's DB: 13593
          Latest checkpoint's oldestActiveXID: 1048576
          Latest checkpoint's oldestMultiXid: 1 旧多事务ID
          Latest checkpoint's oldestMulti's DB: 16565
          Latest checkpoint's oldestCommitTsXid: 0 最旧的事务号
          Latest checkpoint's newestCommitTsXid: 0 最新的事务号
          Time of latest checkpoint: Mon 11 Oct 2021 01:46:24 PM CST 最后一次执行检查点时间
          Fake LSN counter for unlogged rels: 0/3E8
          Minimum recovery ending location: 0/0 这个值与Standby库应用WAL日志有关
          Min recovery ending loc's timeline: 0
          Backup start location: 0/0 记录了一个WAL日志的位置,用于主备库同步。
          Backup end location: 0/0 记录了一个WAL日志的位置,用于主备库同步。
          End-of-backup record required: no 记录了备库恢复过程中的一些中间状态。
          wal_level setting: replica
          wal_level (枚举类型) pg10版本中,待选的值为minimal、replica、logical。
          minimal --不能通过基础备份和wal日志恢复数据库
          replica = 9.6版本以前的archive和hot_standby --该级别支持wal归档和复制。
          logical --在replica级别的基础上添加了支持逻辑解码所需的信息。在开启归档的系统中,进行...
          wal_log_hints setting: off
          设置wal_log_hints这个参数使得能够记录特定提示位(hint-bit)的变化。
          这个参数只能在服务器启动的时候被设置。默认值为off。
          一些特定的工具会要求设置这个参数来正常的运行,所以将它设置为ON就好。
          max_connections setting: 600 最大连接数
          max_worker_processes setting: 8设置系统能够支持的后台进程的最大数量。
          max_wal_senders setting: 10指定来自后备服务器或流式基础备份客户端的并发连接的最大数量(即同时运行 WAL 发送进程 的最大数)。
          src/include/access/xlog_internal.h
          max_prepared_xacts setting: 0
          max_locks_per_xact setting: 64
          track_commit_timestamp setting: off 记录事务提交时间。这个参数只能在postgresql.conf文件 或者服务器命令行上设置。缺省值是off。




            Maximum data alignment: 8
            Database block size: 8192 数据块的大小
            Blocks per segment of large relation: 131072
            WAL block size: 16384 WAL日志块的大小
            Bytes per WAL segment: 16777216 WAL日志文件的大小
            Maximum length of identifiers: 64是指一些数据库对象名称的最大长度,如表名、索引名的最大长度,目前是64。
            Maximum columns in an index: 32表示一个索引最多多少列,目前为32个。
            Maximum size of a TOAST chunk: 1996是TOAST chunk的最大长度。TOAST是解决当列的内容太长,在一个数据块中存不下时的一种行外存储的方式。
            Size of a large-object chunk: 2048 大对象的chunk的大小
            Date/time type storage: 64-bit integers Date/time类型是用64bit的长整数表示。
            Float4 argument passing: by value Float4类型的参数是传值还是传引用。
            Float8 argument passing: by value Float8类型的参数是传值还是传引用。
            Data page checksum version: 1 数据块checksum的版本,默认为0,数据块没有使用checksum,1是启用。
            Mock authentication nonce: 8828beb6f158499535ed0f02f5788bdb104181b80f58e4c28ab6f4d87f849f5c



            事务日志文件管理

            • WAL段文件

              从PostgreSQL 11开始  initdb 时,可以通过 --wal-segsize选项配置wal段文件大小。
              00000001 00000000 00000001
              -------- -------- --------
              时间线 LogId LogSeg
              时间线:英文为timeline,是以1开始的递增数字,如1,2,3…
              LogId:32bit长的一个数字,是以0开始递增的数字,如0,1,2,3…
              LogSeg:32bit长的一个数字,是以0开始递增的数字,如0,1,2,3…

              • WAL段切换

              满足一下任一条件:
              1.WAL段文件已经被写满。
              2.函数pg_switch_wal()被调用。
              3.启用了archive_mode,且已经超过archive_timeout配置的超时时间。
              切换过的文件会被重命名 或者 重用,以供未来使用。



              • WAL段管理

              每当检查点开始时,PostgreSQL 都会估计并准备下一个检查点周期所需的 WAL 段文件的数量。
              此类估计是根据先前检查点周期中消耗的文件数进行的。它们是从包含先前 REDO 点的段开始计算的,
              其值介于min_wal_size(默认为 80 MB,即 5 个文件)和max_wal_size(1 GB,即 64 个文件)之间。
              如果检查点启动,必要的文件将被保留或回收,而不必要的文件将被删除。
              假设在检查点开始前有6个文件,WAL_3包含了当前重做点,PostgreSQL估计要用5个文件,在这种情况下,WAL_1被重命名成了WAL_7回收被重用,而WAL_2被移除。


              • WAL 活动激增而需要更多文件

              WAL 文件的数量根据服务器活动自适应地变化。如果 WAL 数据写入量不断增加,
              WAL 段文件的估计数量以及 WAL 文件的总大小也会逐渐增加。
              在相反的情况下(即 WAL 数据写入量减少),这些值也会减少。


              WAL 文件的数量根据服务器活动自适应地变化。如果 WAL 数据写入量不断增加,
              WAL 段文件的估计数量以及 WAL 文件的总大小也会逐渐增加。
              在相反的情况下(即 WAL 数据写入量减少),这些值也会减少。


              • WAL文件与记录的内部结构

              默认情况下,WAL 段是一个 16 MB 的文件,它在内部被分成 8192 字节 (8 KB) 的页面。
              第一个页面具有由结构XLogLongPageHeaderData定义的标题数据,而所有其他页面的标题具有由结构
              XLogPageHeaderData定义的页面信息。在页头之后,XLOG 记录从头开始按降序写入每一页。


              • WAL记录写入

              1.将当前事物的状态IN_PROGRESS写入CLOG。
              2.插入元组,创建一条XLOG记录。
              3.将插入元组的XLOG记录写入WAL缓冲区,更新页面的pd_lsn。
              4.执行提交。
                 将该提交行为的xlog记录写入WAL缓冲区。
                 将WAL缓冲区中所有的XLOG写入WAL段中。
              5.在Clog中将当前事物的状态由,IN_PROGRESS修改为COMMITTED


              • WAL Writer Process

              Walwriter 是一个后台进程,用于定期检查 WAL 缓冲区并将所有未写入的 XLOG 记录写入 WAL 段。这个过程的目的是为了避免XLOG记录的突发写入。如果没有开启这个过程,当一次提交大量数据时,XLOG记录的写入可能会遇到瓶颈。
              Walwriter默认工作,不能被禁用。检查间隔设置为配置参数wal_writer_delay,默认值为 200 毫秒。




              恢复描述


                src/bin/pg_controldata/pg_controldata.c
                static const char *
                dbState(DBState state)
                {
                switch (state)
                {
                case DB_STARTUP:
                return _("starting up");
                case DB_SHUTDOWNED:
                return _("shut down");
                case DB_SHUTDOWNED_IN_RECOVERY:
                return _("shut down in recovery");
                case DB_SHUTDOWNING:
                return _("shutting down");
                case DB_IN_CRASH_RECOVERY:
                return _("in crash recovery");
                case DB_IN_ARCHIVE_RECOVERY:
                return _("in archive recovery");
                case DB_IN_PRODUCTION:
                return _("in production");
                }
                return _("unrecognized status code");
                }



                恢复举例 pd_lsn=Xlog的lsn

                元组插入表A,并将一条XLOG记录写入LSN_1
                background writer 将表A持久化到磁盘中,页面的pd_lsn为lsn_1
                表A插入第二条元组,并在LSN_2处写入一条XLOG记录,修改后的页面尚未写入存储


                immediate模式关闭后的启动恢复

                1.第一个页面的pd_lsn=LSN1,不小于Xlog的LSN_1,所以不恢复。
                2.重放第二条XLOG记录,该XLOG记录的LNS_2大于当前页面的LSN_1所以需要恢复。


                归档:

                持续归档




                归档配置


                  archive_mode = on              
                  archive_command = 'DIR=/opt/arch/`date +%F`; test ! -d $DIR && mkdir -p $DIR; chmod 755 $DIR; test ! -f $DIR/%f && cp %p $DIR/%f; chmod 755 $DIR/%f'
                  archive_command 中配置SCP命令可以把归档文件,拷贝到远端服务器进行备份。
                  非主库清理归档配置
                  archive_cleanup_command = 'pg_archivecleanup -d opt/peer_arch/`date +%F` %r 2>>cleanup.log‘
                  本参数定义了在每个restart point时所执行的shell命令。
                  archive_cleanup_command参数的目的是提供一个清理不再被standby server所需要的老的archived wal file的机制。
                  restart point 是一个 point ,该point用于standby server重启recovery操作。



                  WAL写放大浅析——原因与影响

                  原因1:全页写
                  原因2:更新记录CTID变更,索引也需要记录变更,并记录WAL。
                  写入频繁,会产生大量的WAL日志,WAL日志量可能超过实际更新的数据量。
                  影响1:磁盘、网络IO

                  WAL写放大优化


                  优化WAL的副作用

                  pg_waldump、walminer、wal2json工具浅析

                  pg_waldump

                  pg_waldump显示WAL,将WAL以人可以读的格式输出,pg10+版本自带工具

                    pg_waldump decodes and displays PostgreSQL write-ahead logs for debugging.
                    Options:
                    -b, --bkp-details output detailed information about backup blocks
                    -e, --end=RECPTR stop reading at WAL location RECPTR
                    -f, --follow keep retrying after reaching end of WAL
                    -n, --limit=N number of records to display
                    -p, --path=PATH directory in which to find log segment files or a
                    directory with a ./pg_wal that contains such files
                    (default: current directory, ./pg_wal, $PGDATA/pg_wal)
                    -r, --rmgr=RMGR only show records generated by resource manager RMGR;
                    use --rmgr=list to list valid resource manager names
                    -s, --start=RECPTR start reading at WAL location RECPTR
                    -t, --timeline=TLI timeline from which to read log records
                    (default: 1 or the value used in STARTSEG)
                    -V, --version output version information, then exit
                    -x, --xid=XID only show records with transaction ID XID
                    -z, --stats[=record] show statistics instead of records
                    (optionally, show per-record statistics)
                    select pg_switch_wal();
                    create table test(id int);
                    checkpoint;
                    insert into test values(1);
                    select pg_current_wal_insert_lsn();
                    select pg_walfile_name('0/8011878');
                    select file_name,upper(to_hex(file_offset)) file_offset from pg_walfile_name_offset('0/8011878');
                    pg_waldump -p opt/pg_root/pg_wal -s 0/8011728 -e 0/8011878 000000010000000000000008



                    walminer

                    WalMiner是从PostgreSQL的WAL(write ahead logs)日志的解析工具,旨在挖掘wal日志所有的有用信息,从而提供PG的数据恢复支持。目前主要有如下功能:
                    从waL日志中解析出SQL,包括DML和少量DDL
                    解析出执行的SQL语句的工具,并能生成对应的undo SQL语句。与传统的logical decode插件相比,walminer不要求logical日志级别且解析方式较为灵活。

                    数据页挽回
                    当数据库被执行了TRUNCATE等不被wal记录的数据清除操作,或者发生磁盘页损坏,可以使用此功能从wal日志中搜索数据,以期尽量挽回数据。
                    walminer的构建基础是,checkpoint之后对每一个page的更改会产生全页写(FPW),因此一个checkpoint之后的所有wal日志可以完美解析。注意checkpoint是指checkpoint开始的点,而不是checkpoint的wal记录的点

                      https://gitee.com/movead/XLogMiner
                      PG源码编译 如果你从编译pg数据库开始
                      将walminer目录放置到编译通过的PG工程的"../contrib/"目录下
                      进入walminer目录
                      执行命令
                      make && make install
                      创建walminer的extension
                      create extension walminer;
                      添加要解析的wal日志文件
                      -- 添加wal文件:
                      select walminer_wal_add('/opt/pg_root/pg_wal');
                      -- 注:参数可以为目录或者文件
                      -- 移除wal文件:
                      select walminer_wal_remove('/opt/test/wal');
                      -- 注:参数可以为目录或者文件
                      列出wal文件:
                      select walminer_wal_list();
                      select pg_switch_wal();
                      checkpoint;
                      select walminer_wal_add('/opt/pg_root/pg_wal');
                      select walminer_wal_list();
                      create table test(id int);
                      insert into test(id) values(2);
                      select walminer_all();
                      select * from walminer_contents;


                      wal2json

                      https://github.com/eulerto/wal2json

                        tar -zxf wal2json-wal2json_2_4.tar.gz
                        cd wal2json-wal2json_2_4
                        export PATH=/home/euler/pg13/bin:$PATH
                        make
                        make install
                        postgresql.conf
                        wal_level = logical
                        max_replication_slots = 10
                        max_wal_senders = 10


                        终端A

                          $ pg_recvlogical -d postgres --slot test_slot --create-slot -P wal2json
                          $ pg_recvlogical -d postgres --slot test_slot --start -o pretty-print=1 -o add-msg-prefixes=wal2json -f -


                          终端B

                            cd opt/
                            psql -At -f example1.sql postgres



                            example1.sql

                              CREATE TABLE table1_with_pk (a SERIAL, b VARCHAR(30), c TIMESTAMP NOT NULL, PRIMARY KEY(a, c));
                              CREATE TABLE table1_without_pk (a SERIAL, b NUMERIC(5,2), c TEXT);
                              BEGIN;
                              INSERT INTO table1_with_pk (b, c) VALUES('Backup and Restore', now());
                              INSERT INTO table1_with_pk (b, c) VALUES('Tuning', now());
                              INSERT INTO table1_with_pk (b, c) VALUES('Replication', now());
                              SELECT pg_logical_emit_message(true, 'wal2json', 'this message will be delivered');
                              SELECT pg_logical_emit_message(true, 'pgoutput', 'this message will be filtered');
                              DELETE FROM table1_with_pk WHERE a < 3;
                              SELECT pg_logical_emit_message(false, 'wal2json', 'this non-transactional message will be delivered even if you rollback the transaction');
                              INSERT INTO table1_without_pk (b, c) VALUES(2.34, 'Tapir');
                              -- it is not added to stream because there isn't a pk or a replica identity
                              UPDATE table1_without_pk SET c = 'Anta' WHERE c = 'Tapir';
                              COMMIT;
                              DROP TABLE table1_with_pk;
                              DROP TABLE table1_without_pk;

                              精彩回顾

                              【干货分享】MySQL的安全隐患

                              云贝教育|甲骨文官方授权培训机构

                              全国首推—腾讯云TBase专家级(PostgreSQL版)

                              【干货分享】MySQL用户权限及验证逻辑讲解



                              文章转载自云贝教育,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                              评论