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

Postgresql WAL文件专题

概述

WAL文件是“Write Ahead Log”的简称, 就是数据库重做日志, 与Oracle的Redo Log的功能是一样的。
WAL文件在PostgreSQL9.X及以下版本是在pg_xlog目录下的, 而在PostgreSQL10.X及以上版本是在pg_wal目录下的。

查看WAL文件所在的目录, 会看到如下文件列表:

[postgres@pg01 pg_wal]$ ls -l
total 81924
-r w------- 1 postgres postgres 312 Nov 26 14:10
000000010000000000000020.00000028.backup
-rw------- 1 postgres postgres 16777216 Dec 2 15:13 000000010000000000000026
-rw------- 1 postgres postgres 16777216 Dec 2 15:16 000000010000000000000027
-rw------- 1 postgres postgres 16777216 Dec 2 14:20 000000010000000000000028
-rw------- 1 postgres postgres 16777216 Dec 2 14:44 000000010000000000000029
-rw------- 1 postgres postgres 16777216 Dec 2 14:46 00000001000000000000002A
drwx------ 2 postgres postgres 96 Dec 2 15:13 archive_status

上面的文件中文件名为24个字母长度的都是WAL文件, 如“000000010000000000000026”“000000010000000000000027”“000000010000000000000028”等。

WAL文件名

为了解释清楚24个字母长度的WAL文件名, 我们先解释一下另一个概念LSN, 即“Log Sequence Number(日志序列号) ”, 是一个不断增长的8字节(64bit) 长数字, 用于记录WAL日志的绝对位置, 随着数据库WAL日志的不断增加, LSN也会不断地增长。

而WAL文件名的24个字符由三部分组成,如图:

其中:

  • 时间线: 英文为timeline, 是以1开始的递增数字, 如1, 2, 3, …。
  • LogId: 32bit长的一个数字, 是以0开始递增的, 如0, 1, 2, 3, …。 实际为LSN的高32bit。
  • LogSeg: 32bit长的一个数字, 是以0开始递增的, 如0, 1, 2, 3, …。 LogSeg是LSN的低32bit的值再除以WAL文件大小(通常为16MB) 的结果。 注意: 当LogId为0时, LogSeg是从1开始的。

WAL日志文件默认大小为16MB, 如果想改变其大小, 在PostgreSQL10.X及之前的版本中需要重新编译程序, 在PostgreSQL11.X版本之后, 可以在Initdb初始化数据库实例时指定WAL文件的大小。

如果WAL文件是默认大小, 即16MB时, LogSeg最大为FF, 即000000~0000FF, 即在文件名中, 最后8字节中前6字节总是0。
这时因为LSN的低32bit的值再除以WAL文件大小[2**32/(1610241024)=256] 最大只能是256, 换算成十六进制, 即FF。

WAL文件循环复用原理

当我们查看WAL日志的目录时, 从表面上来看好像是旧文件不断地被删除, 新的WAL文件会不断地产生。
熟悉Oracle数据库的人会发现这点与Oracle数据库中的Redo Log不一样。
Oracle数据库中Redo Log的个数固定, 是会被循环覆盖的。
这种以循环覆盖的方式写Redo Log的机制在文件系统上相比于append方式有更高的性能, 因为对于文件系统来说,如果用append方式, 文件不断地增加时, 除了添加的内容数据, 文件的尺寸大小数据也需要持久化下去, 这样相当于每次写产生了两次I/O, 一次是内容数据的I/O, 一次是记录文件大小的I/O。
而对于Oracle数据库是先初始化Redo Log, 真正在写Redo Log时是覆盖写,这样每次写, 只有一次I/O。
所以从表面上来看, PostgreSQL不是循环覆盖写, 这样看起来, PostgreSQL在WAL日志这一块要比Oracle性能低, 但实际上, PostgreSQL也是循环覆盖写, WAL写的性能并不比Oracle中低, 下面我们详细讲解一下, PostgreSQL循环覆盖写的原理。

PostgreSQL的循环覆盖写是通过把旧的WAL日志“重命名”来实现的。
发生一次Checkpoint之后, 此Checkpoint点之前的WAL日志文件都可以删除, 而PostgreSQL中一般并不会将其删除, 而是“重命名”旧的WAL文件使之成为一个新的WAL文件。
所以WAL文件目录下文件序号最大的那个WAL文件并不是当前正在写的WAL文件, 因为这个WAL文件有可
能是前一次Checkpoint时重命名旧文件产生的。
我们用一个示例来说明这种情况。

在WAL目录,可以看到如下文件:

[postgres@pg01 pg_wal]$ ls -l
total 81924
-r w------- 1 postgres postgres 312 Nov 26 14:10
000000010000000000000020.00000028.backup
-rw------- 1 postgres postgres 16777216 Dec 2 16:16 000000010000000000000027
-rw------- 1 postgres postgres 16777216 Dec 2 14:20 000000010000000000000028
-rw------- 1 postgres postgres 16777216 Dec 2 14:44 000000010000000000000029
-rw------- 1 postgres postgres 16777216 Dec 2 14:46 00000001000000000000002A
-rw------- 1 postgres postgres 16777216 Dec 2 15:13 00000001000000000000002B
drwx------ 2 postgres postgres 59 Dec 2 16:16 archive_status

序号最大的文件名为“00000001000000000000002B”, 接下来我们进到数据库中, 查看当前正在写的文件是哪一个:

postgres=# select pg_walfile_name(pg_current_wal_lsn());
pg_walfile_name
--------------------------
000000010000000000000027
(1 row)

上面的SQL语句中先用函数“pg_current_wal_lsn”获得当前正在写的LSN号, 然后用函数“pg_walfile_name”找出当前LSN号对应的WAL文件, 发现是“000000010000000000000027”说明这时“000000010000000000000028”“000000010000000000000029”“00000001000000000000002A”“00000001000000000000002B”都是以前的WAL文件。

我们用pg_waldump命令(注: 在PostgreSQL 9.X及之前版本中是pg_xlogdump) 查看这个WAL文件会报错, 示例如下:

[postgres@pg01 pg_wal]$ pg_waldump 00000001000000000000002B
pg_waldump: FATAL: could not find a valid record after 0/2B000000

报错也说明了这个文件是由一个旧文件重命名产生的。
那该文件是由哪个旧文件重命名的呢?
我们可以查看其二进制内容:

0000000: 97d0 0700 0100 0000 0000 0026 0000 0000 ...........&....
0000010: d30b 0000 0000 0000 4a14 0467 fd2d f95b ........J..g.-.[
0000020: 0000 0001 0020 0000 2020 2020 2020 2020 ..... ..
0000030: 2020 2020 2020 2020 2020 2020 2020 2020
0000040: 2020 2020 2020 2020 2020 2020 2020 2020
0000050: 2020 2020 2020 2020 2020 2020 2020 2020

在二进制文件的第8个字节开始的8个字节“0000 0026 0000 0000”中, 由于是在X86平台上, 是小端系统, 所以该值代表的二进制数字为“0x0000000026000000”, 换算成LSN号为“0/26000000”, 所以该文件的原文件名如下:

postgres=# select pg_walfile_name('0/26000008');
pg_walfile_name
--------------------------
000000010000000000000026
(1 row)

所以, “00000001000000000000002B”文件实际上是原来的文件“000000010000000000000026”。
此时将文件“00000001000000000000002B”复制成“000000010000000000000026”,再执行pg_waldump命令就不会报错了:

[postgres@pg01 pg_wal]$ cp 00000001000000000000002B /tmp/000000010000000000000026 [postgres@pg01 pg_wal]$ pg_waldump /tmp/000000010000000000000026 |more rmgr: Heap2 len (rec/tot): 6515/ 6515, tx: 750, lsn: 0/26000C00, prev 0/25FFF260, desc: MULTI_INSE RT+INIT 61 tuples, blkref #0: rel 1663/13806/16588 blk 1108 rmgr: Heap2 len (rec/tot): 6515/ 6515, tx: 750, lsn: 0/26002590, prev 0/26000C00, desc: MULTI_INSE RT+INIT 61 tuples, blkref #0: rel 1663/13806/16588 blk 1109 rmgr: Heap2 len (rec/tot): 4289/ 4289, tx: 750, lsn: 0/26003F08, prev 0/26002590, desc: MULTI_INSE RT+INIT 40 tuples, blkref #0: rel 1663/13806/16588 blk 1110 rmgr: Heap2 len (rec/tot): 2317/ 2317, tx: 750, lsn: 0/26004FE8, prev 0/26003F08, desc: MULTI_INSE RT 21 tuples, blkref #0: rel 1663/13806/16588 blk 1110 ... ... ...

PostgreSQL在源代码中的“改名”方法实际上并不是用改名的方法实现的, 而是用先创建一个新硬链接文件指向旧文件, 然后再删除旧文件的方法, 这种“改名”方法在UNIX平台下相对改名操作更通用一些。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论