天天看点

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

事务日志是数据库的重要组成部分,存储了数据库系统中所有更改和操作的历史,以确保数据库不会因为故障(例如掉电或其他导致服务器崩溃的故障)而丢失数据。在PostgreSQL(以下简称PG)中,事务日志文件称为Write Ahead Log(以下简称WAL)。

使用WAL不仅可以保证数据库事务操作的持久性,也可以显著地减少写磁盘的次数,因为只需要把日志文件刷新到磁盘就可以保证事务被提交,而不需要把事务改动过的每一个数据文件都刷新到磁盘。日志文件是连续写的,所以同步log的花销远小于刷新数据页的花销。特别是服务器要处理涉及数据存储不同部分的大量小事务时更是这样。

WAL还使得在线备份和时间点恢复成为可能。通过归档WAL数据,我们可以恢复到WAL数据覆盖范围内的任何时间点:只需install一份数据库的物理备份,并恢复WAL日志到所需时间即可。更重要的是,这个物理备份并不必须是一个数据库状态的瞬时快照。

WAL 步骤简介

WAL机制实际是在这个写数据的过程中加入了对应的写wal log的过程,步骤一样是先到Buffer,再刷新到Disk。

  • Change发生时:
    • 先将变更后内容记入WAL Buffer
    • 再将更新后的数据写入Data Buffer
  • Commit发生时:
    • WAL Buffer刷新到Disk
    • Data Buffer写磁盘推迟
  • Checkpoint发生时:
    • 将所有Data Buffer刷新到磁盘

如下图所示:

图1:change 发生时

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

图2:commit及checkpoint发生时

《postgresql指南--内幕探索》第九章 预写式日志(WAL)
WAL术语介绍

为方便理解下文内容,先介绍以下WAL术语:

1、 REDO log

Redo log通常称为重做日志,在写入数据文件前,每个变更都会先行写入到Redo log中。其用途和意义在于存储数据库的所有修改历史,用于数据库故障恢复(Recovery)、增量备份(Incremental Backup)、PITR(Point In Time Recovery)和复制(Replication)。

2、 WAL segment file

为了便于管理,PG把事务日志文件划分为N个segment,每个segment称为WAL segment file,每个WAL segment file大小默认为16MB。

3、 XLOG Record

这是一个逻辑概念,可以理解为PG中的每一个变更都对应一条XLOG Record,这些XLOG Record存储在WAL segment file中。PG读取这些XLOG Record进行故障恢复/PITR等操作。

4、 WAL buffer

WAL缓冲区,不管是WAL segment file的header还是XLOG Record都会先行写入到WAL缓冲区中,在"合适的时候"再通过WAL writer写入到WAL segment file中。

5、 LSN

LSN即日志序列号Log Sequence Number。表示XLOG record记录写入到事务日志中位置。LSN的值为无符号64位整型(uint64)。在事务日志中,LSN单调递增且唯一。

6、 checkpointer

checkpointer是PG中的一个后台进程,该进程周期性地执行checkpoint。当执行checkpoint时,该进程会把包含checkpoint信息的XLOG Record写入到当前的WAL segment file中,该XLOG Record记录包含了最新Redo point的位置。

7、 checkpoint

检查点checkpoint由checkpointer进程执行,主要的处理流程如下:

(1) 获取Redo point,构造包含此Redo point检查点(详细请参考Checkpoint结构体)信息的XLOG Record并写入到WAL segment file中;

(2) 刷新Dirty Page到磁盘上;

(3) 更新Redo point等信息到pg_control文件中。

8、 REDO point

REDO point是PG启动恢复的起始点,是最后一次checkpoint启动时事务日志文件的末尾亦即写入Checkpoint XLOG Record时的位置(这里的位置可以理解为事务日志文件中偏移量)。

9、 pg_control

pg_control是磁盘上的物理文件,保存检查点的基本信息,在数据库恢复中使用,可通过命令pg_controldata查看该文件中的内容。

WAL机制介绍

下文描述了在不同情况下,不使用WAL和使用WAL机制对数据库数据持续性和恢复的影响。为了简化描述,仅使用了只包含一页的表TABLE_A。

无 WAL 的插入操作

如第8 章所述,为了高效访问关系页面,每个 DBMS 都实现了共享缓冲池。

假设我们在不执行 WAL 功能的 PostgresQL 上将一些数据图块插入到TABLE_A中:

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

如上图所示:

  1. 发布第一个插入语句,PostgreSQL 将TABLE_A的页面从数据库集群加载到内存共享缓冲池中,并将一个 tuple 插入页面。此页面不会立即写入数据库集群。(如第 8 章所述,修改后的页面通常称为脏页)
  2. 发布第二个插入语句,PostgreSQL 在缓冲池上的页面中插入一个新的 Tuple。此页面尚未写入存储。
  3. 如果操作系统或 PostgreSQL 服务器因任何原因(如电源故障)而发生故障,则所有插入的数据都将丢失。

因此,没有 WAL 的数据库容易受到系统故障的影响。

插入操作和数据库恢复

为了在不影响性能的情况下处理上述系统故障,PostgreSQL 支持 WAL。此子节中的一些关键字和关键概念,请参考前文。

PostgreSQL 将所有修改作为历史数据写入持久存储,以便为失败做好准备。在PG中,历史数据称为XLOG记录或WAL数据。

XLOG 记录通过插入、删除或提交操作等操作将更改写入内存WAL 缓冲区。当事务提交/中止时,它们会立即写入存储中的WAL segment文件。

XLOG 记录的LSN(日志序列号)表示其记录写在事务日志上的位置。XLOG 记录的 LSN 用作 XLOG 记录的特有ID。

顺便说一句,当我们考虑数据库系统如何恢复时,可能有一个问题:PG从什么位置开始恢复?答案是redo point:即在启动最新检查点时编写 XLOG 记录的位置。事实上,数据库恢复处理与检查点处理紧密相连,这两种处理都是不可分割的。

下图为带WAL日志的插入:

《postgresql指南--内幕探索》第九章 预写式日志(WAL)
  1. checkpointer、后台进程定期执行检查。每当检查点进程启动时,它都会向当前的 WAL segment编写称为checkpoint记录的 XLOG 记录。此记录包含最新REDO point的位置。
  2. 发布第一个插入语句,PostgreSQL 将TABLE_A的页面加载到共享缓冲池中,将tuple插入页面,在LSN_1位置的 WAL 缓冲器中创建并写入此语句的 XLOG 记录,并将TABLE_A LSN 从LSN_0更新到LSN_1。

在此示例中,此 XLOG 记录是一组头数据和整个tuple。

  1. 当此事务提交时,PostgreSQL 创建并将此提交操作的 XLOG 记录创建并写入 WAL 缓冲区,然后从LSN_1将 WAL 缓冲器上的所有 XLOG 记录写入并冲洗到 WAL 段文件中。
  2. 发布第二个插入声明,PostgreSQL 将一个新的 tuple 插入页面,创建并将此 tuple 的 XLOG 记录写到LSN_2的 WAL 缓冲器中,并将TABLE_A LSN 从LSN_1更新到LSN_2。
  3. 当本声明的事务提交时,PostgreSQL 的操作方式与步骤 3 相同。
  4. 想象操作系统此时发生故障。尽管共享缓冲池上的所有数据都丢失了,但页面的所有修改都已作为历史数据写入 WAL 段文件。

以下说明显示了如何立即将数据库集群恢复到崩溃前状态。无需做任何特别的事情,因为 PostgreSQL 将通过重新启动自动进入恢复模式。PG将依次从REDO点在适当的WAL段文件中读取和重放XLOG记录。

《postgresql指南--内幕探索》第九章 预写式日志(WAL)
  1. PostgreSQL 从适当的 WAL 段文件中读取第一个插入语句的 XLOG 记录,将数据库集群中的TABLE_A页面加载到共享缓冲池中。
  2. 在尝试重放XLOG 记录之前,PostgreSQL 应将 XLOG 记录的 LSN 与相应页面的 LSN 进行比较,重放XLOG 记录的规则如下所示。

    如果 XLOG 记录的 LSN 大于页面的 LSN,则 XLOG 记录的数据部分将插入页面,页面的 LSN 将更新为 XLOG 记录的 LSN。另一方面,如果 XLOG 记录的 LSN 较小,则阅读下一个 WAL 数据。

    在此示例中,XLOG 记录被重放,因为 XLOG 记录的 LSN(LSN_1) 大于TABLE_ALSN(LSN_0)。然后,TABLE_A的LSN从LSN_0更新到LSN_1。

  3. PG以同样的方式重放剩余的XLOG记录。

PostgreSQL 可以通过按时间顺序重放 WAL 段文件中编写的 XLOG 记录来恢复自身。因此,PG的XLOG记录显然是REDO日志。

整页写

假设因为操作系统在编写脏页的过程中出现故障,导致TABLE_A存储上的页面数据损坏。由于 XLOG 记录无法在损坏的页面上重放,因此我们需要额外的操作。

PostgreSQL 支持一项称为整页写的功能,以处理此类故障。如果启用,PostgreSQL 将在每个检查点后每个页面的第一次更改中将一对头数据和整个页面作为 XLOG 记录写入。PG默认已启用整页写。在 PostgreSQL 中,包含整个页面的此类 XLOG 记录称为备份块(或整页图像)。

让我们再次描述一下启用了整页写的Tuple 插入,请参阅下图及以下描述:

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

步骤如下:

  1. 启动checkpoint进程。
  2. 在执行第一个插入语句时,虽然 PostgreSQL 的操作方式与前一小节几乎相同,但此 XLOG 记录是此页面的备份块(即包含整个页面),因为这是最新检查点之后此页面的第一个写入。
  3. 进行事务的提交,PostgreSQL 的操作方式与前一小节相同。
  4. 在执行第二个插入语句时,PostgreSQL 的操作方式与前一小节相同,因为此 XLOG 记录不是备份块。
  5. 当此事务提交时,PostgreSQL 的操作方式与前一小节相同。
  6. 为了证明整页写的有效性,我们在此考虑的是,由于操作系统故障,存储中的TABLE_A页面因后台写进程写到硬盘而损坏的情况。

重新启动PG服务器以修复损坏的集群。请参阅下图以及以下描述:

《postgresql指南--内幕探索》第九章 预写式日志(WAL)
  1. PostgreSQL 读取第一个插入语句的 XLOG 记录,并将损坏的TABLE_A页面从数据库集群加载到共享缓冲池中。在此示例中,XLOG 记录是备份块,因为根据整页写的规则,每个页面的第一个 XLOG 记录始终是其备份块。
  2. 当 XLOG 记录是其备份块时,应用了另一种重放规则:无论 LSN 的值如何,记录的数据部分(即页面本身)都将被覆盖到页面上,并将页面的 LSN 更新到 XLOG 记录的 LSN。

    在此示例中,PostgreSQL 将记录的数据部分覆盖到已损坏的页面,并将TABLE_A LSN 更新为LSN_1。这样,损坏的页面将通过备份块进行恢复。

  3. 由于第二个 XLOG 记录是非备份块,PostgreSQL 的运行方式与前一小节中的相同。

    即使发生了某些数据写入故障,PostgreSQL也可以恢复。(当然,如果文件系统发生故障,则不适用)。

事务日志及WAL文件

事务日志存储了数据库系统中所有更改和操作的历史,随着数据库的运行,事务日志大小不断的增长,那么事务日志有大小限制吗?在PG中,答案是肯定的:大小有限制。

PG使用无符号64bit整型(uint64)作为事务日志文件的寻址空间,理论上,PG的事务日志空间最大为2^64Bytes(即16EB)。这个大小有多大呢?假设某个数据库比较繁忙,每天可以产生16TB的日志文件,那么要达到事务日志文件大小的上限需要的时间是1024*1024/365天≈2800年。也就是说,虽然大小有限制,但从现阶段来看已然足够了。

显然,对于16EB的文件,OS是无法高效管理的,为此,PG把事务日志文件划分为N个大小为16M(默认值)的WAL segment file,其总体结构如下图所示:

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

WAL segment file文件名称为24个字符,由3部分组成,每个部分是8个字符,每个字符是一个16进制值(即0~F)。其命名规则如下:

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

每一部分的解析如下(在WAL segment file文件大小为16MB的情况下):

  • 第1部分是TimeLineID,取值范围是0x00000000 -> 0xFFFFFFFF
  • 第2部分是逻辑文件ID,取值范围是0x00000000 -> 0xFFFFFFFF
  • 第3部分是物理文件ID,取值范围是0x00000000 -> 0x000000FF

逻辑文件ID、物理文件ID和文件大小这三部分的组合,实现了64bit的寻找空间:

  • 逻辑文件ID是32bit的uint32(unsigned int 32bit)
  • 物理文件ID是8bit的unit8
  • 16M的文件大小是24bit的unit24

三者共同组成unit64(32+8+24),达到最大64bit的文件寻址空间。

第一个WAL segment 文件名为000000010000000000000001,若该WAL segment写满,那么第二个WAL segment文件为000000010000000000000002。而若0000000100000000000000FF文件写满,那么下一个WAL segment 文件名为000000010000000100000000。

使用内置函数pg_xlogfile_name(9.6 及以前版本)或pg_walfile_name(10 及以后版本),可以找到包含指定 LSN 的 WAL 段文件名。 示例如下:

testdb=# SELECT pg_xlogfile_name('1/00002D3E');  
# In version 10 or later, "SELECT pg_walfile_name('1/00002D3E');"
     pg_xlogfile_name     
--------------------------
 000000010000000100000000
(1 row)
           

WAL Segment的内部结构

WAL segment file内部划分为N个page(Block),每个page大小为8192 Bytes即8K,每个WAL segment file第1个page的header在PG源码中相应的数据结构是XLogLongPageHeaderData,后续其他page的header对应的数据结构是XLogPageHeaderData。在一个page中,page header之后是N个XLOG Record。如下图所示:

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

XLOG记录的内部结构

XLOG Record由两部分组成,第一部分是XLOG Record的头部信息,大小固定(24 Bytes),对应的结构体是XLogRecord;第二部分是XLOG Record data。

XLOG记录头

所有的XLOG记录都有一个由XLogRecord结构体定义的通用的头部。其结构如下:

typedef struct XLogRecord
{
   uint32          xl_tot_len;   /* total len of entire record */
   TransactionId   xl_xid;       /* xact id */
   uint32          xl_len;       /* total len of rmgr data */
   uint8           xl_info;      /* flag bits, see below */
   RmgrId          xl_rmid;      /* resource manager for this record */
   /* 2 bytes of padding here, initialize to zero */
   XLogRecPtr      xl_prev;      /* ptr to previous record in log */
   pg_crc32        xl_crc;       /* CRC for this record */
} XLogRecord;
           

xl_rmid和xl_info这两个变量是和资源管理相关的,这些变量是与 WAL 功能相关的操作的集合,例如写和重放 XLOG 记录。

XLOG记录的数据部分

在此只介绍pg9.5及以后版本的结构。

在版本 9.4 或更早的时候,没有 XLOG 记录的常见格式,因此每个资源管理器必须定义自己的格式。在这种情况下,维护源代码和实施与 WAL 相关的新功能变得越来越困难。为了处理这个问题,9.5版本引入了一种不依赖于资源管理器的常见结构化格式。

XLOG 记录的数据部分可分为两部分:记录头部分和数据部分。如下图所示:

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

头部分包含零个或多个 XLogRecordBlockHeaders和零或一个 XLogRecordDataHeaderShort(或 XLogRecordDataHeaderLong):它必须至少包含其中之一。当其记录存储整页(即备份块)时,XLogRecordBlockHeader包括XLogRecordBlockImageHeader,如果其块被压缩,还包括XLogRecordBlockCompressHeader。

数据部分由零或多个块数据和零或一个主数据组成,分别对应于XLogRecordBlockHeader和 XLogRecordDataHeader。

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

下面显示了一些具体示例:

备份块

插入语句创建的备份块如上图(a) 显示。它由四个数据结构和一个数据对象组成,如下所示:

  • 结构体XLogRecord(头部分)
  • 结构体XLogRecordBlockHeader包括一个LogRecordBlockImageHeader
  • 结构体XLogRecordDataHeaderShort
  • 备份块(块数据)
  • 结构体xl_heap_insert(主数据)

XLogRecordBlockHeader包含用于标识数据库集群中的块的变量(relfilenode,fork编号和块编号); XLogRecordImageHeader包含此块的长度和偏移号。

XLogRecordDataHeaderShort存储xl_heap_insert结构的长度,该结构是记录的主要数据。

非备份块

接下来,INSERT语句创建的非备份块记录将描述如下(也参见上图(b))。它由四个数据结构和一个数据对象组成,如下所示:

  • 结构体XLogRecord(头部分)
  • 结构体XLogRecordBlockHeader
  • 结构体XLogRecordDataHeaderShort
  • 一个插入的元组(严格地说,一个xl_heap_header结构和一个插入的整个数据)
  • 结构体xl_heap_insert(主数据)

XLogRecordBlockHeader包含三个值(relfilenode,fork编号和块编号),用于指定插入元组的块,以及插入元组的数据部分的长度。 XLogRecordDataHeaderShort包含新xl_heap_insert结构的长度,该结构是此记录的主数据。

新的xl_heap_insert只包含两个值:块内该元组的偏移量,以及可见性标志;它变得非常简单,因为XLogRecordBlockHeader存储了旧数据中包含的大部分数据。

最后一个例子,检查点记录如上图(c)所示。它由三个数据结构组成,如下所示:

  • 结构体XLogRecord(头部分)
  • 结构体XLogRecordDataHeaderShort包含主数据长度
  • 结构体CheckPoint(主数据)

XLOG记录写入

我们完成了热身练习,现在我们已经准备好了解XLOG记录的写入。我将在本节中尽可能准确地解释它。

首先,执行以下语句来探索PostgreSQL内部:

通过发出上述语句,调用内部函数exec_simple_query()。

exec_simple_query()的伪代码如下所示:

exec_simple_query() @postgres.c

(1) ExtendCLOG() @clog.c                  /* Write the state of this transaction
                                           * "IN_PROGRESS" to the CLOG.
                                           */
(2) heap_insert()@heapam.c                /* Insert a tuple, creates a XLOG record,
                                           * and invoke the function XLogInsert.
                                           */
(3)   XLogInsert() @xlog.c (9.5 or later, xloginsert.c)
                                          /* Write the XLOG record of the inserted tuple
                                           *  to the WAL buffer, and update page's pd_lsn.
                                           */
(4) finish_xact_command() @postgres.c     /* Invoke commit action.*/   
      XLogInsert() @xlog.c  (9.5 or later, xloginsert.c)
                                          /* Write a XLOG record of this commit action 
                                           * to the WAL buffer.
                                           */
(5)   XLogWrite() @xlog.c                 /* Write and flush all XLOG records on 
                                           * the WAL buffer to WAL segment.
                                           */
(6) TransactionIdCommitTree() @transam.c  /* Change the state of this transaction 
                                           * from "IN_PROGRESS" to "COMMITTED" on the CLOG.
                                           */
           

在以下段落中,将解释伪代码的每一行以理解XLOG记录的写入。

  1. 函数ExtendCLOG()将此事务的状态“IN_PROGRESS”写入(内存中)CLOG中。
  2. 函数heap_insert()将堆元组插入共享缓冲池的目标页面,创建该页面的XLOG记录,并调用函数XLogInsert()。
  3. 函数XLogInsert()将heap_insert()创建的XLOG记录写入LSN_1的WAL缓冲区,然后将修改后的页面的pd_lsn从LSN_0更新为LSN_1。
  4. 调用函数finish_xact_command()提交事务,并创建此提交操作的XLOG记录,然后函数XLogInsert()将此记录写入LSN_2的WAL缓冲区。
  5. 函数XLogWrite()将WAL缓冲区上的所有XLOG记录刷新到WAL段文件。

    如果参数wal_sync_method设置为“open_sync”或“open_datasync”,则同步写入记录,因为会使用指定标志O_SYNC或O_DSYNC的open()函数通过系统调用写入所有记录。如果参数设置为’fsync’,‘fsync_writethrough’或’fdatasync’,则系统将调用相应的函数 - 带有F_FULLFSYNC选项的fsync(),fcntl()或fdatasync()。所以在任何情况下,都会确保将所有XLOG记录写入存储。

  6. 函数TransactionIdCommitTree()将此事务的状态从“IN_PROGRESS”更改为“COMMITTED”。

在上面的示例中,commit动作导致将XLOG记录写入WAL段,但是当发生以下任何一种情况时,会发生写入操作:

  1. 一个正在运行的事务已提交或已中止。
  2. WAL缓冲区已经填满了许多元组。 (WAL缓冲区大小设置为参数wal_buffers。)
  3. WAL写进程定期写入。 (见下一节。)

    如果出现上述情况之一,WAL缓冲区上的所有WAL记录都将写入WAL段文件,无论它们的事务是否已提交。

当然,DML操作会写XLOG记录,但非DML操作也是如此。如上所述,提交操作会写入包含已提交事务的id的XLOG记录。另一个例子是检查点会将该检查点的一般信息写入到XLOG记录。此外,SELECT语句在特殊情况下也会创建XLOG记录,但一般情况下不会创建XLOG。例如,如果在SELECT语句处理期间删除了不必要的元组,而且在页面中发生了由HOT(Heap Only Tuple)引发的碎片整理,则修改页面的XLOG记录将写入WAL缓冲区。

WAL写进程

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

WAL写进程默认工作的,并且无法禁用。检查间隔设置为配置参数wal_writer_delay,默认值为200毫秒。

Postgresql中的检查点进程

在PostgreSQL中,checkpointer(后台)进程执行检查点;当下列之一发生时,其进程开始:

  1. 从前一个检查点发生过后的时间超过checkpoint_timeout设置的间隔(默认间隔为300秒(5分钟))。
  2. 在版本9.4或更早版本中,自上一个检查点以来,超过使用checkpoint_segments(默认数量为3)设置的WAL段文件数量。
  3. 在9.5或更高版本中,pg_xlog(版本10或更高版本,pg_wal)中WAL段文件的总大小已超过参数max_wal_size的值(默认值为1GB(64个文件))。
  4. PostgreSQL服务在smart和fast模式下停止。

当超级用户手动发出CHECKPOINT命令时,也会执行检查点。

在以下章节中,将描述检查点的概述和保存当前检查点的元数据的pg_control文件。

检查点进程概要

检查点进程负责两个方面:为数据库恢复做准备和共享缓冲池上的脏页清除。在本小节中,将重点关注前一个方面。参见下图和以下描述。

《postgresql指南--内幕探索》第九章 预写式日志(WAL)
  1. 检查点开始后,REDO点存储在内存中; REDO点是在最新检查点启动时写入XLOG记录的位置,并且是数据库恢复的起点。
  2. 将此检查点的XLOG记录(即检查点记录)写入WAL缓冲区。记录的数据部分由结构CheckPoint定义,结构包含几个变量,例如存储在步骤(1)中的REDO点。

    此外,写入检查点记录的位置实际上称为检查点。

  3. 共享缓冲池中的所有数据(例如,CLOG的内容等)被刷新到磁盘。
  4. 共享缓冲池中的所有脏页都会逐渐写入并刷新到存磁盘。
  5. 更新pg_control文件。此文件包含一些基本信息,例如检查点记录所写的位置(a.k.a.检查点位置)。稍后详细描述此文件。

为了从数据库恢复的角度总结上述描述,检查点创建包含REDO点的检查点记录,并将检查点位置和更多内容存储到pg_control文件中。因此,PostgreSQL可以通过从pg_control文件提供的REDO点(从检查点记录获得)重放WAL数据来恢复自身。

pg_control文件

由于pg_control文件包含检查点的基本信息,因此它对于数据库恢复肯定是必不可少的。如果它被破坏或不可读,则恢复过程无法启动以便无法获得起点。

即使pg_control文件存储了40多个项目,但是下一节中仅需要三个项目,如下所示:

  • State - 最新检查点开始时数据库服务的状态。共有七个状态:“start up”是系统启动的状态;'shutdown’是系统出关闭命令正常关闭的状态; “in production”是系统运行的状态;等等。
  • 最新检查点位置 - LSN最新检查点记录的位置。
  • 先前检查点位置 - LSN前一检查点记录的位置。请注意,它在版本11中已弃用。

pg_control文件存储在global子目录中;可以使用pg_controldata实用程序显示其内容。

postgres> pg_controldata  /usr/local/pgsql/data
pg_control version number:            937
Catalog version number:               201405111
Database system identifier:           6035535450242021944
Database cluster state:               in production
pg_control last modified:             Sun Aug 8 15:16:38 2021
Latest checkpoint location:           0/C000F48
Prior checkpoint location:            0/C000E70
           

PostgreSQL 11中将会删除先前检查点

PostgreSQL 11或更高版本只存储包含最新或更新的检查点的WAL段;将不会存储包含先前检查点的旧的段文件,这样做为了以减少用于在pg_xlog(pg_wal)子目录下保存WAL段文件的磁盘空间。

Postgresql中数据库恢复

PostgreSQL实现了基于重做日志的恢复功能。如果数据库服务器崩溃,PostgreSQL通过从REDO点顺序重放WAL段文件中的XLOG记录来恢复数据库集群。

在本节之前我们已经多次讨论了数据库恢复,因此我将描述两方面有关恢复的,但尚未介绍过的内容。

第一点是PostgreSQL如何开始恢复处理。当PostgreSQL启动时,它首先读取pg_control文件。以下是从那个时间点开始恢复处理的细节。参见下图和以下描述。

《postgresql指南--内幕探索》第九章 预写式日志(WAL)
  1. PostgreSQL在启动时读取pg_control文件的信息。如果状态项处于’in production’,PostgreSQL将进入恢复模式,因为这意味着数据库没有正常停止;如果’shutdown’,它将进入正常的启动模式。
  2. PostgreSQL从相应的WAL段文件中读取最新的检查点记录,该记录位于pg_control文件中,并从记录中获取REDO点。如果最新的检查点记录无效,PostgreSQL将读取前一个检查点的记录。如果两个记录都没有读取到,将自行放弃恢复。 (请注意,先前的检查点在从PostgreSQL 11中不会再存储。)
  3. 适当的资源管理器从REDO点开始按顺序读取和重放XLOG记录,直到它们到达最新WAL段的最后一个点。当重放XLOG记录并且它是备份块时,无论其LSN如何,它都将覆盖相应表的页面。否则,仅当此记录的LSN大于相应页面的pd_lsn时,才会重放(非备份块)XLOG记录。

第二点是关于LSN的比较:为什么应该比较非备份块的LSN和相应页面的pd_lsn。与前面的示例不同,这里将使用强调两个LSN之间进行比较的特定示例来解释。

图1:后台写进程工作期间的插入操作

《postgresql指南--内幕探索》第九章 预写式日志(WAL)
  1. PostgreSQL将一个元组插入TABLE_A,并在LSN_1处写入一条XLOG记录。
  2. 后台写进程将TABLE_A的页面写入磁盘。此时,此页面的pd_lsn为LSN_1。
  3. PostgreSQL在TABLE_A中插入一个新元组,并在LSN_2处写入一条XLOG记录。修改后的页面还没有写入磁盘。

与概述中的示例不同,在这种情况下,TABLE_A的页面已写入磁盘。

使用immediate模式关闭,然后启动。

图二:数据库恢复

《postgresql指南--内幕探索》第九章 预写式日志(WAL)
  1. PostgreSQL加载第一个XLOG记录和TABLE_A的页面,但不重放它,因为该记录的LSN不大于TABLE_A的LSN(两个值都是LSN_1)。
  2. 接下来,PostgreSQL重放第二个XLOG记录,因为该记录的LSN(LSN_2)大于当前TABLE_A的LSN(LSN_1)。

从此示例中可以看出,如果非备份块的重放顺序不正确或者多次重放非备份块,则数据库集群将不一致。简而言之,非备份块的重做(重放)操作不是幂等的。因此,为了保留正确的重放顺序,当且仅当其LSN大于相应页面的pd_lsn时,才重放非备份块记录。

另一方面,由于备份块的重做操作是幂等的,因此备份块可以重放任意次,而不管其LSN如何。

WAL段文件切换

PostgreSQL将XLOG记录写入存储在pg_xlog子目录(版本10或更高版本,pg_wal子目录)中的一个WAL段文件中,如果已填满旧文件,则切换为新文件。 WAL文件的数量根据服务端几个参数的配置而有所不同。此外,他们的管理策略在9.5版本中得到了改进。

在以下小节中,描述了WAL段文件的切换和管理。

WAL段的切换

发生以下任一情况时,会发生WAL段切换:

  1. WAL段已经填满。
  2. 调用函数pg_switch_xlog。
  3. archive_mode已启用,并超出archive_timeout设置的时间。

切换后的段文件通常被回收(重命名和重用)以供将来使用,如果没有必要,也会删除掉。

9.5版本以及更新的版本中的WAL段管理

每当检查点启动时,PostgreSQL都会预估和准备下一个检查点周期所需的WAL段文件数。这种预估是参考前一个检查点周期中消耗的文件数量。它们从前一个检点点包含REDO点的段做统计,并且该值在min_wal_size(默认情况下,80 MB,即5个文件)和max_wal_size(1 GB,即64个文件)之间。如果检查点启动,将保留或回收必要的文件,同时删除不必要的文件。

具体例子如图9.17所示。假设在检查点启动之前有六个文件,WAL_3包含先前的REDO点(在版本10或更早版本中是先前的REDO点;在版本11或更高版本中,是最新REDO点),并且PostgreSQL预估需要五个文件。在这种情况下,WAL_1将重命名为WAL_7以进行回收,并且将删除WAL_2。

比前一个REDO点更老的的文件会被删除,因为从第上一节中描述的恢复机制可以清楚地看出,它们永远不会被使用。

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

如果由于WAL活动激增而需要更多文件,则在WAL文件的总大小小于max_wal_size时将创建新文件。例如,在下图中,如果已填充WAL_7,则新创建WAL_8。

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

WAL文件的数量会根据数据库的繁忙程度自适应地改变。如果WAL数据写入的数量不断增加,则WAL段文件的预估数量以及WAL文件的总大小也逐渐增加。相反的情况下(即WAL数据写入量减少),这些减少。

如果WAL文件的总大小超过max_wal_size,则将启动检查点。下图说明了这种情况。通过检查点,将创建一个新的REDO点,之前最新的REDO点变为了前一个REDO点;然后将回收不必要的旧文件。通过这种方式,PostgreSQL将始终只保存数据库恢复所需的WAL段文件。

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

配置参数wal_keep_segments和复制槽功能也会影响WAL段文件的数量。

持续归档和归档日志

持续归档是postgresql的一个功能,可在WAL段切换时将WAL段文件复制到归档区域,由归档(后台)进程执行。复制的文件称为归档日志。这项功能通常用于第10章中描述的物理热备份和PITR(时间点恢复)。

归档区域的路径设置为配置参数archive_command。例如,使用以下参数,每当段切换时,WAL段文件都会复制到目录’/home/postgres/archives/’:

其中,%p被复制WAL段目录的占位符,%f是归档日志文件的占位符。

《postgresql指南--内幕探索》第九章 预写式日志(WAL)

如上图所示:在切换WAL段文件WAL_7时,WAL_7作为archive log 7复制到归档区域。

参数archive_command可以使用任何Unix命令和工具设置,因此您可以通过scp命令将归档日志传输到其他主机,也可以使用文件备份工具替代普通的复制命令传输至其他主机。

PostgreSQL不会自动清理已经创建的归档日志,因此打开归档时应妥善管理日志。如果你什么都不做,归档日志的数量将持续增加。

pg_archivecleanup是归档日志有效的管理工具之一。

翻译整理自:https://www.interdb.jp/pg/pgsql09.html

继续阅读