PostgreSQL 基础知识:WAL 文件和序列号

译者:阎书利

Postgres预写日志(Write Ahead Log, WAL)是数据库的一个功能组件。WAL使许多关键功能成为可能,比如时间点恢复备份、从事件中恢复、流复制等等。有时候,深入数据库的人员需要直接使用WAL文件进行诊断或恢复。

最近在与Crunchy Data的一位客户合作时,我遇到了一种情况,在这种情况下,理解名称和序列号非常重要。在与几个致力于Postgres项目的同事一起工作时,我收集了一些关于WAL内部细节的笔记。今天的目标是研究WAL的LSN和命名约定,以帮助用户更好地理解WAL文件。

日志序列号

PostgreSQL中的事务创建WAL记录,这些记录最终被附加到WAL日志(文件)中。插入发生的位置称为日志序列号(LSN)。可以比较LSN(类型为 pg_lsn)的值,以确定在两个不同偏移量(以字节为单位)之间生成的WAL的数量。当以这种方式使用时,重要的是要知道,如果使用多个WAL日志,则计算假设使用了完整的WAL段(16MB)。和这里使用的计算类似的计算通常被用于确定replica的延迟。

LSN是一个64位整数,表示在预写日志流中的位置。这个64位整数被分成两个段(高32位和低32位)。格式为两个十六进制数,中间用斜杠(XXXXXXXX/YYZZZZZZ)隔开。“X”代表LSN的高32位,“Y”是低32位部分的高8位。“Z”表示文件中的偏移位置。每个元素是一个十六进制数。'X’和’Y’值在默认PostgreSQL部署的WAL文件的第二部分中使用。

WAL文件

WAL的文件名格式为 TTTTTTTTXXXXXXXXYYYYYYYY。这里’T’是时间线,'X’是LSN的高32位,'Y’是LSN的低32位。

首先查看当前的WAL LSN并插入LSN。pg_current_wal_lsn是最后一次写操作的位置。pg_current_wal_insert_lsn是逻辑位置,反映缓冲区中尚未写入磁盘的数据。还有一个刷新值,显示已写入持久存储的内容。

[postgres] # select pg_current_wal_lsn(), pg_current_wal_insert_lsn();
pg_current_wal_lsn | pg_current_wal_insert_lsn
--------------------+---------------------------
76/7D000000 | 76/7D000028
(1 row)

尽管您可以根据上面的输出猜出WAL文件的名称,但最好还是使用这个函数:pg_walfile_name

[postgres] # select pg_walfile_name('76/7D000028');
pg_walfile_name
--------------------------
00000001000000760000007D
(1 row)

查看文件系统,我们可以看到段00000001000000760000007D确实是最近修改的文件。注意,如果数据库是空闲的,那么00000001000000760000007D可能是最老的文件(基于O/S最后修改日期)。这可能是因为PostgreSQL重用了旧的WAL段。在pg_wal_switch期间,旧的文件被重命名。修改的O/S日期/时间只会在文件写入时更改。在ls命令中使用-i将显示文件的inode(第一列)。当文件被重用时,这个数字不会改变,因为文件只是被重命名。

$ ls -larti 00*
169034323 -rw------- 1 bpace staff 376 Jan 30 09:13 0000000100000075000000A0.00000060.backup
169564733 -rw------- 1 bpace staff 16777216 Feb 13 15:49 00000001000000760000007E
167120667 -rw------- 1 bpace staff 16777216 Feb 13 15:50 00000001000000760000007F
167120673 -rw------- 1 bpace staff 16777216 Feb 13 16:00 00000001000000760000007B
167120686 -rw------- 1 bpace staff 16777216 Feb 13 16:18 00000001000000760000007C
169564722 -rw------- 1 bpace staff 16777216 Feb 13 16:18 00000001000000760000007D

让我们创建一个小表并执行一个WAL切换。

[postgres] # create table test (a char(1));
CREATE TABLE
Time: 23.770 ms

[postgres] # select pg_switch_wal();

pg_switch_wal
--------------
76/7D018FD8
(1 row)

再次查看文件,我们现在看到00000001000000760000007D文件已经更新(从O/S角度更改了日期/时间)。由于在后台发生了其他一些项目,下一个段00000001000000760000007E也在pg_switch_wal之后收到了一些写操作。

$ ls -larti 00*
169034323 -rw------- 1 bpace staff 376 Jan 30 09:13 0000000100000075000000A0.00000060.backup
167120667 -rw------- 1 bpace staff 16777216 Feb 13 15:50 00000001000000760000007F
167120673 -rw------- 1 bpace staff 16777216 Feb 13 16:00 000000010000007600000080
167120686 -rw------- 1 bpace staff 16777216 Feb 13 16:18 000000010000007600000081
169564722 -rw------- 1 bpace staff 16777216 Feb 13 16:24 00000001000000760000007D
169564733 -rw------- 1 bpace staff 16777216 Feb 13 16:24 00000001000000760000007E

在新创建的表中,插入一条记录。确保数据库是平稳的,并在更改之前和之后获取当前的WAL LSN。请注意,我们将1个字节(‘a’)插入到具有单列的单个表中。

[postgres] # select pg_current_wal_lsn(), pg_current_wal_insert_lsn();
pg_current_wal_lsn | pg_current_wal_insert_lsn
--------------------+---------------------------
76/7E000060 | 76/7E000060
(1 row)

[postgres] # insert into test (a) values ('a');
INSERT 0 1

[postgres] # select pg_current_wal_lsn(), pg_current_wal_insert_lsn();
pg_current_wal_lsn | pg_current_wal_insert_lsn
--------------------+---------------------------
76/7E000108 | 76/7E000108
(1 row)

使用这两个LSN位置,我们可以计算INSERT生成的WAL的数量(在本例中为168字节)。

[postgres] # select '76/7E000108'::pg_lsn - '76/7E000060'::pg_lsn size_bytes;
size_bytes
------------
168
(1 row)

获取当前的WAL LSN,然后WAL插入LSN,再执行一次切换。然后列出文件。

[postgres] # select pg_switch_wal();
pg_switch_wal
---------------
76/7E0001D0
(1 row)


$ ls -larti 00*
169034323 -rw------- 1 bpace staff 376 Jan 30 09:13 0000000100000075000000A0.00000060.backup
167120673 -rw------- 1 bpace staff 16777216 Feb 13 16:00 000000010000007600000080
167120686 -rw------- 1 bpace staff 16777216 Feb 13 16:18 000000010000007600000081
169564722 -rw------- 1 bpace staff 16777216 Feb 13 16:24 000000010000007600000082
169564733 -rw------- 1 bpace staff 16777216 Feb 13 16:26 00000001000000760000007E
167120667 -rw------- 1 bpace staff 16777216 Feb 13 16:26 00000001000000760000007F

使用我们在前面步骤中捕获的信息,使用pg_waldump获取人类可读的WAL段内容摘要。在下面的命令中,指定了起始位置(-s)和结束位置(-e)以及WAL文件名(00000001000000760000007E)。开始位置是INSERT之前的current_wal_lsn,结束位置是插入之后的current_wal_lsn。之前,仅使用lsn,我们确定有168个字节从事务写入到WAL。查看waldump可以发现INSERT占用103字节(INSERT占用57字节,COMMIT占用46字节)。

sh-4.4$ pg_waldump -s 76/7E000060 -e 76/7E000108 00000001000000760000007E
rmgr: Heap len (rec/tot): 57/ 57, tx: 59555584, lsn: 76/7E000060, prev 76/7E000028, desc: INSERT+INIT off 1 flags 0x08, blkref #0: rel 1663/5/53434 blk 0
rmgr: Transaction len (rec/tot): 46/ 46, tx: 59555584, lsn: 76/7E0000A0, prev 76/7E000060, desc: COMMIT 2023-02-13 16:25:19.441483 EST
rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 76/7E0000D0, prev 76/7E0000A0, desc: RUNNING_XACTS nextXid 59555585 latestCompletedXid 59555584 oldestRunningXid 59555585

从INSERT之前的点查看整个WAL文件,可以看到INSERT本身,然后是COMMIT。最后,记录检查点和执行的pg_wal_switch()切换。如果wal_level设置为replica或更高,则添加RUNNING_XACTS项。RUNNING_XACTS条目捕获当前快照(活动事务)的详细信息。最后一个条目SWITCH是执行的pg_switch_wal。

sh-4.4$ pg_waldump 00000001000000760000007E
rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 76/7E000028, prev 76/7D018FC0, desc: RUNNING_XACTS nextXid 59555584 latestCompletedXid 59555583 oldestRunningXid 59555584
rmgr: Heap len (rec/tot): 57/ 57, tx: 59555584, lsn: 76/7E000060, prev 76/7E000028, desc: INSERT+INIT off 1 flags 0x08, blkref #0: rel 1663/5/53434 blk 0
rmgr: Transaction len (rec/tot): 46/ 46, tx: 59555584, lsn: 76/7E0000A0, prev 76/7E000060, desc: COMMIT 2023-02-13 16:25:19.441483 EST
rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 76/7E0000D0, prev 76/7E0000A0, desc: RUNNING_XACTS nextXid 59555585 latestCompletedXid 59555584 oldestRunningXid 59555585
rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 76/7E000108, prev 76/7E0000D0, desc: RUNNING_XACTS nextXid 59555585 latestCompletedXid 59555584 oldestRunningXid 59555585
rmgr: XLOG len (rec/tot): 114/ 114, tx: 0, lsn: 76/7E000140, prev 76/7E000108, desc: CHECKPOINT_ONLINE redo 76/7E000108; tli 1; prev tli 1; fpw true; xid 0:59555585; oid 61620; multi 799; offset 1657; oldest xid 716 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 59555585; online
rmgr: XLOG len (rec/tot): 24/ 24, tx: 0, lsn: 76/7E0001B8, prev 76/7E000140, desc: SWITCH

结束

我希望您不需要太频繁地钻研WAL,希望您的pg_wal_switch事件很少。提醒一下,各种initdb选项和编译时选项可以改变计算和假设的结果。WAL是一个复杂的主题,任何与WAL相关的元素都应该非常小心

参考链接:https://www.crunchydata.com/blog/postgres-wal-files-and-sequuence-numbers

作者:Brian Pace

本文分享自微信公众号 - 开源软件联盟PostgreSQL分会(kaiyuanlianmeng)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部