PostgreSQL的WAL(2)--Write-Ahead Log
2020-09-19 11:21 abce 阅读(628) 评论(0) 编辑 收藏 举报
为了避免RAM中数据丢失,必须将所有必需的东西妥善保存到磁盘(或其他非易失性介质)中。为此,做了以下的操作。在更改数据时,还维护了这些更改的日志。当我们更改buffer cache中页面上的某些内容时,我们会在日志中创建此更改的记录。 该记录包含必要时足以重做更改的最少信息。为此,日志记录必须在更改后的页面写入磁盘之前必须先写入磁盘。 这解释了名称:预写日志(WAL)。
如果发生故障,磁盘上的数据可能会不一致:某些页面写得较早,而另一些页面写得较晚。但是WAL仍然存在,我们可以读取并重做在故障之前执行的操作,但是操作的结果会晚一点到磁盘。
为什么不强行将数据页面本身写入磁盘,为什么要重复工作呢?
首先,WAL是按顺序追加顺序流数据。甚至HDD磁盘也可以很好地进行顺序写入。但是,由于页面或多或少地分散在磁盘上,因此数据本身是以随机方式写入的。
其次,WAL记录可能比页面小很多。
第三,在写入磁盘时,我们不必在每个时间点都要维护磁盘上数据的一致性。
第四,正如我们稍后将看到的,WAL(一旦可用)不仅可以用于恢复,还可以用于备份和复制。
必须对所有操作进行WAL记录,以防发生故障时导致磁盘上的数据不一致。具体来说,以下操作是WAL记录的:
·buffer cache中对页面的修改(主要是表页面和索引页面)—因为页面更改需要花费一些时间才能到达磁盘。
·事务的提交和中止-因为状态更改是在XACT缓冲区中完成的,所以更改也需要一些时间才能到达磁盘。
·文件操作(创建和删除文件和目录,例如在创建表期间创建文件)-因为这些操作必须与数据更改同步。
以下内容未进行WAL记录:
·对unlogged属性的表的操作。
·对临时表的操作-日志记录没有意义,因为此类表的生存期不超过创建它们的会话的生存期。
·与记录相关的事务ID。
·资源管理器-负责记录的系统组件。
·校验和(CRC)-用于检测数据是否损坏。
·记录的长度,并链接到上一个记录。
pg_waldump -r list
XLOG Transaction Storage CLOG Database Tablespace MultiXact RelMap Standby Heap2 Heap Btree Hash Gin Gist Sequence SPGist BRIN CommitTs ReplicationOrigin Generic LogicalMessage
WAL作为$PGDATA/pg_wal目录中的文件存储在磁盘上。默认情况下,每个文件为16 MB。可以增加此大小,以避免在一个目录中包含多个文件。在PostgreSQL 11之前,只能在编译源代码时执行此操作,但是现在可以在初始化集群时指定大小(使用--wal-segsize选项)。
WAL记录写入当前使用的文件,一旦结束,将使用下一个文件。
在服务器的共享内存中,为WAL分配了特殊的缓冲区。wal_buffers参数指定WAL缓存的大小(默认值表示自动设置:已分配buffer cache的1/32)。
WAL缓存的结构类似于buffer cache,但是以循环模式下工作:将记录添加到“ head”,但从“ tail”开始写入磁盘。
pg_current_wal_lsn和pg_current_wal_insert_lsn函数分别返回写(«tail»)和插入(«head»)位置:
=> SELECT pg_current_wal_lsn(), pg_current_wal_insert_lsn(); pg_current_wal_lsn | pg_current_wal_insert_lsn --------------------+--------------------------- 0/331E4E64 | 0/331E4EA0 (1 row)
我们可以知道在哪个文件中可以找到所需的位置以及与文件开头的偏移量:
# select pg_current_wal_lsn(),pg_current_wal_insert_lsn(); pg_current_wal_lsn | pg_current_wal_insert_lsn --------------------+--------------------------- 23C/5AFCAE38 | 23C/5AFCAE38 (1 row) s=# SELECT file_name, upper(to_hex(file_offset)) file_offset postgres-# FROM pg_walfile_name_offset('23C/5AFCAE38'); file_name | file_offset --------------------------+------------- 000000010000023C0000005A | FCAE38 (1 row) =#
文件名由两部分组成。8位高位十六进制数字显示时间线的编号(用于从备份还原),其余部分对应于LSN的高位(LSN其余的低位显示偏移量)。
在文件系统中,可以在$PGDATA/pg_wal/目录中看到WAL文件,但是从PostgreSQL 10开始,还可以使用专门的功能来查看它们:
=> SELECT * FROM pg_ls_waldir() WHERE name = '000000010000000000000033'; name | size | modification --------------------------+----------+------------------------ 000000010000000000000033 | 16777216 | 2019-07-08 20:24:13+03 (1 row)
让我们看看如何进行WAL以及如何确保提前写入。让我们创建一个表:
=> CREATE TABLE wal(id integer); => INSERT INTO wal VALUES (1);
我们来研究page的header部分。为此,我们需要一个著名的扩展:
=> CREATE EXTENSION pageinspect;
让我们开始一个事务,并记住插入WAL的位置:
=> BEGIN; => SELECT pg_current_wal_insert_lsn(); pg_current_wal_insert_lsn --------------------------- 0/331F377C (1 row)
现在我们将执行一些操作,例如,更新一行:
=> UPDATE wal set id = id + 1;
此更改已写WAL记录,并且插入位置已更改:
=> SELECT pg_current_wal_insert_lsn(); pg_current_wal_insert_lsn --------------------------- 0/331F37C4 (1 row)
为了确保在WAL写入之前不会将更改后的数据页刷新到磁盘,与该页相关的最后一个WAL记录的LSN存储在页头中:
=> SELECT lsn FROM page_header(get_raw_page('wal',0)); lsn ------------ 0/331F37C4 (1 row)
请注意,WAL是整个集群的,新记录始终都在那里。因此,页面上的LSN可以小于pg_current_wal_insert_lsn函数刚返回的值。但是由于在我们的系统中什么也没有发生,因此数字是相同的。
=> COMMIT;
提交也被WAL记录,并且位置再次更改:
=> SELECT pg_current_wal_insert_lsn(); pg_current_wal_insert_lsn --------------------------- 0/331F37E8 (1 row)
每次提交都会变更XACT结构中事务状态(我们已经讨论过)。状态存储在文件中,但它们也使用自己的缓存,该缓存在共享内存中占据128页。因此,对于XACT页面,也必须跟踪最后一个WAL记录的LSN。但是,此信息存储在RAM中,而不是存储在页面本身中。
=> SELECT pg_current_wal_lsn(), pg_current_wal_insert_lsn(); pg_current_wal_lsn | pg_current_wal_insert_lsn --------------------+--------------------------- 0/331F37E8 | 0/331F37E8 (1 row)
此后,数据和XACT页面可以刷新到磁盘。但是,如果我们不得不更早地刷新它们,它将被检测到,并且WAL记录将被迫首先进入磁盘。
=> SELECT '0/331F37E8'::pg_lsn - '0/331F377C'::pg_lsn; ?column? ---------- 108 (1 row)
在这种情况下,行和提交的更新在WAL中需要108个字节。
我们可以用相同的方式评估服务器在一定负载下每单位时间生成的WAL记录的数量。这是重要的信息,需要进行调优(我们将在下次讨论)。
现在,让我们使用pg_waldump实用工具查看创建的WAL记录。
postgres$ /usr/lib/postgresql/11/bin/pg_waldump -p /var/lib/postgresql/11/main/pg_wal -s 0/331F377C -e 0/331F37E8 000000010000000000000033 rmgr: Heap len (rec/tot): 69/ 69, tx: 101085, lsn: 0/331F377C, prev 0/331F3014, desc: HOT_UPDATE off 1 xmax 101085 ; new off 2 xmax 0, blkref #0: rel 1663/16386/33081 blk 0 rmgr: Transaction len (rec/tot): 34/ 34, tx: 101085, lsn: 0/331F37C4, prev 0/331F377C, desc: COMMIT 2019-07-08 20:24:13.945435 MSK
在这里,我们看到两个记录的header。
=> SELECT pg_relation_filepath('wal'); pg_relation_filepath ---------------------- base/16386/33081 (1 row)
第二个记录是COMMIT,与事务资源管理器有关。
这种格式几乎不容易阅读,但是可以让我们在需要时使用。
当我们启动服务器时,首先启动postmaster进程,然后启动startup进程,该启动进程的任务是确保在发生故障时进行恢复。
为了确定是否需要恢复,startup进程会在专用控制文件$PGDATA/global/pg_control中查看集群状态。但是我们也可以通过pg_controldata实用程序自己检查状态:
postgres$ /usr/lib/postgresql/11/bin/pg_controldata -D /var/lib/postgresql/11/main | grep state Database cluster state: in production
常规方式关闭的postgresql server将处于“shutdown”状态。如果postgresql server不工作,但状态仍处于“in production”,则表示DBMS已关闭,并且恢复将自动完成。
但是也有例外。某些记录被创建为FPI(full page image),该记录将覆盖页面内容,因此无论其状态如何都可以将其应用于页面。可以将事务状态的更改应用于XACT页的任何版本,因此无需在此类页中存储LSN。
在恢复过程中,与常规工作一样,页面将在buffer cache中更改。为此,postmaster进程启动所需的后台进程。
WAL记录以类似的方式应用于文件。
And at the very end of the recovery process, respective
这是该算法的非常简化的描述。具体来说,到目前为止,我们还没有说起从哪里开始阅读WAL记录。
最后要提的是:恢复过程包括两个阶段。在第一(前滚)阶段,将应用日志记录,并且服务器将重做由于故障而丢失的所有工作。在第二(回滚)阶段,将回滚在故障时刻尚未提交的事务。但是PostgreSQL不需要第二阶段。如前所述,由于多版本并发控制的实现功能,无需物理回滚事务-因此无需在XACT中设置commit位。
原文地址:https://habr.com/en/company/postgrespro/blog/494246/