深度解析Write-Ahead Logging

崔鹏老师上周技术分享课   精华讲解

内容简介

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

数据缓冲区写磁盘推迟

检查点发生时:

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

image.png

Crash后的实例恢复

PostgreSQL 7.1之前的Crash

image.png

插入数据,页面未被刷新回磁盘,缓冲区中页面被弄脏。

继续插入数据,页面仍然未被刷新回磁盘

OS  OR  PG Server Crash 后缓冲区中的数据丢失

没有WAL的DBMS是极度不安全的。

PostgreSQL 7.1之后的写操作

image.png

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

image.png

pd_lsn - identifies xlog record for last change to this page.

标识对该页的最后更改的xlog记录。

PostgreSQL 7.1之后的恢复

image.png

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恢复

块折断

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)后的数据写入

image.png

概念

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恢复

image.png

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 文件包含检查点的基本信息,因此它对于数据库恢复当然是必不可少的。

如果损坏或无法读取,则无法启动恢复过程,从而无法获得起点。

image.png

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。

image.png

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段管理

image.png

每当检查点开始时,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 活动激增而需要更多文件

image.png

WAL 文件的数量根据服务器活动自适应地变化。如果 WAL 数据写入量不断增加,

WAL 段文件的估计数量以及 WAL 文件的总大小也会逐渐增加。

在相反的情况下(即 WAL 数据写入量减少),这些值也会减少。

image.png

WAL 文件的数量根据服务器活动自适应地变化。如果 WAL 数据写入量不断增加,

WAL 段文件的估计数量以及 WAL 文件的总大小也会逐渐增加。

在相反的情况下(即 WAL 数据写入量减少),这些值也会减少。

WAL文件与记录的内部结构

image.png

默认情况下,WAL 段是一个 16 MB 的文件,它在内部被分成 8192 字节 (8 KB) 的页面。

第一个页面具有由结构XLogLongPageHeaderData定义的标题数据,而所有其他页面的标题具有由结构

XLogPageHeaderData定义的页面信息。在页头之后,XLOG 记录从头开始按降序写入每一页。

image.png

image.png

WAL记录写入

image.png

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

image.png

Walwriter 是一个后台进程,用于定期检查 WAL 缓冲区并将所有未写入的 XLOG 记录写入 WAL 段。这个过程的目的是为了避免XLOG记录的突发写入。如果没有开启这个过程,当一次提交大量数据时,XLOG记录的写入可能会遇到瓶颈。

Walwriter默认工作,不能被禁用。检查间隔设置为配置参数wal_writer_delay,默认值为 200 毫秒。

恢复描述

image.png

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");
}

image.png

image.png

恢复举例 pd_lsn=Xlog的lsn

image.png

元组插入表A,并将一条XLOG记录写入LSN_1

background writer 将表A持久化到磁盘中,页面的pd_lsn为lsn_1

表A插入第二条元组,并在LSN_2处写入一条XLOG记录,修改后的页面尚未写入存储

immediate模式关闭后的启动恢复

image.png

1.第一个页面的pd_lsn=LSN1,不小于Xlog的LSN_1,所以不恢复。

2.重放第二条XLOG记录,该XLOG记录的LNS_2大于当前页面的LSN_1所以需要恢复。

归档

持续归档

image.png

归档配置

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写放大优化

image.png

优化WAL的副作用

image.png

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;

*禁止转载,可转发(转发文章请注明出处)

深度解析Write-Ahead Logging

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值