[译] 10. 基础备份和时间点恢复
原文地址:https://www.interdb.jp/pg/pgsql10.html
原文作者:Hironobu SUZUKI
在线数据库备份大致可以分为两类:逻辑备份和物理备份。两者各有优缺点,但逻辑备份有一个缺点;为它的性能花费太多时间。尤其是备份一个大型的数据库需要非常长的时间,而从备份数据中恢复也需要很多时间。相反,物理备份使得在相对较短的时间内备份和恢复大型数据库成为可能,因此在实际系统中它是一个非常重要和有用的特性。
在 PostgreSQL 中,从 8.0 版本开始提供在线物理完整备份,运行中的整个数据库集簇(即物理备份数据)的快照称为基础备份(base backup)。
(Point-in-Time Recovery (PITR))时间点恢复也从8.0版本起可用,它使用一个基础备份和由连续归档功能创建的归档日志将数据库集簇恢复到任意时间点的功能。例如,即使你犯了一个严重的错误(例如:截断所有表),此功能能让你将数据库恢复到犯错前的时刻。
在本章将描述以下主题:
- 基础备份是什么
- PITR的工作原理
- timelineId 是什么
- 时间线历史文件是什么
在7.4或更早版本,PostgreSQL 仅支持逻辑备份(逻辑完整或部分备份和数据导出)。
10.1 基础备份(base backup)
首先,使用低级(low-level)命令进行基础备份的标准流程如下所示:
(1) 发起 pg_start_backup 命令
(2) 使用想要使用的归档命令对数据库集簇创建一个快照
(3) 发起 pg_stop_backup 命令
Fig. 10.1. Making a base backup.
这个简单流程对于数据库系统管理员来说很容易使用,因为它不需要特殊的工具,使用常用工具,如scp命令或类似的归档工具去创建一个基础备份。另外,在此过程中,不需要锁表且备份操作不会影响所有用户发查询请求。和其它主要的开源RDBMS相比,这是最大的优势。
使用 pg_basebackup 实用程序是一个更简单的创建基础备份方法,但它在其内部实际是调用低级命令。
由于这对低级命令是清晰理解PITR的关键点之一,于是将在以下小节中探讨它们。
10.1.1 pg_start_backup
pg_start_backup 准备进行基本备份。如第 9.8 节所述,恢复过程从重做(REDO)点开始,因此 pg_start_backup 必须执行检查点以在开始进行基本备份时显式创建重做(REDO)点。此外,其检查点的检查点位置必须保存在 pg_control 以外的文件中,因为在备份期间可能会多次执行常规检查点。因此 pg_start_backup 执行以下四个操作:
- 强制进入整页(full-page)写模式
- 切换当前的WAL段文件(8.4或更高版本)
- 执行检查点
- 创建一个备份标签(backup_label)文件——该文件在基目录(base directory)的顶层目录中创建,它包含有关基础备份本身的基本信息,如检查点位置。
第三,四个操作是此命令的核心;执行第一、二个操作是为了更加可靠地恢复数据集簇。
备份标签文件包含以下六项(11或更高版本是七项):
- 检查点位置(CHECKPOINT LOCATION):这是已记录的由此命令创建的检查点的LSN位置
- 开始WAL位置(START WAL LOCATION):他不与PITR一起使用,而是和第11章描述的流复制一起使用。它被命名为'START WAL LOCATION' 是因为处于复制模式(replication-mode)的备用服务器在初始化启动时仅读取一次该值。
- 备份方法(BACKUP METHOD):用于制作基础备份的方法('pg_start_backup' 或 'pg_basebackup')
- 备份源(BACKUP FROM):显示此备份来自主还是备
- 开始时间(START TIME):pg_start_backup 执行的时间戳
- 标签(LABEL):在 pg_start_backup 中指定的标签
- 开始时间线(START TIMELINE):备份开始的时间线。它是在11版本中引入的用于完整性检查。
backup_label(备份标签)
下面显示了9.6版本中备份标签文件的真实例子:
postgres> cat /usr/local/pgsql/data/backup_label START WAL LOCATION: 0/9000028 (file 000000010000000000000009) CHECKPOINT LOCATION: 0/9000060 BACKUP METHOD: pg_start_backup BACKUP FROM: master START TIME: 2022-3-27 11:45:19 GMT LABEL: Weekly Backup
正如您想象的一样,当使用此基础备份恢复数据库时,PostgreSQL 从备份标签文件中提取'CHECKPOINT LOCATION' 用于从相应的归档日志中读取检查点记录,然后从该检查点记录中获取重做(REDO)点并启动恢复进程。(详细内容将在下一节中描述)
10.1.2 pg_stop_backup
pg_stop_backup 执行以下五个操作来完成备份。
- 如果在pg_start_backup中强制进行了更改整页写模式,则重置为非整页(non-full-page)写入模式
- 写入一条备份端的XLOG记录
- 切换WAL段文件
- 创建备份历史文件——该文件包含备份标签文件的内容和pg_stop_backup 已执行的时间戳
- 删除备份标签文件——从基础备份恢复需要备份标签文件,一旦复制,在原始数据库集簇中就不再需要了。
备份历史文件的命名方式如下所示。
{WAL segment}.{offset value at the time the base backup was started}.backup
10.2 时间点恢复(PITR)
图10.2 显示了PITR的基本概念。PITR 模式中的 PostgreSQL 在基础备份上重放存档日志的 WAL 数据,从 pg_start_backup 创建的重做(REDO)点到你想恢复的点。在PostgreSQL 中,恢复的点称为恢复目标(recovery target)。
Fig. 10.2. Basic concept of PITR.
这里描述PITR的工作原理。假设您在2022年3月27日 12:05 GMT 执行了一个误操作。您应该删除数据库集簇并使用之前创建的基础备份恢复一个新的集簇。然后,在recovery.conf(11或更早版本)或 postgresql.conf (12或更高版本)中设置restore_command参数的命令,并设置 recovery_target_time 的时间为误操作的时间点(此例为12:05 GMT )。
# Place archive logs under /mnt/server/archivedir directory.
restore_command = 'cp /mnt/server/archivedir/%f %p'
recovery_target_time = "2022-3-27 12:05 GMT"
当 PostgreSQL 启动时,如果数据库集簇中有 recovery.conf(11 或更早版本)或 recovery.signal(12 或更高版本)和 备份标签文件,则进入 PITR 模式。
recovery.conf
recovery.conf 已在12版本中弃用,所有与复制相关(recovery-related) 的参数都应该写入到postgresql.conf中。详细看官方文档
在版本 12 或更高版本中,当您从基本备份恢复服务器时,您需要在数据库集簇目录中创建一个名为 recovery.signal 的空文件。
$ touch /usr/local/pgsql/data/recovery.signal
PITR 过程与第 9 章中描述的正常恢复过程几乎相同;它们之间的唯一区别是以下两点:
- WAL段/归档日志从哪里读取?
- 正常恢复模式(Normal recovery mode)—— 来自基目录下的pg_xlog子目录(在10或更高版本pg_wal子目录)
- PITR模式—— 来自配置参数 archive_command 指定的归档目录
- 检查点位置从哪里读取?
- 正常恢复模式(Normal recovery mode)—— 来自pg_control文件
- PITR模式 —— 来自备份标签文件
PITR流程的概要描述如下:
(1) 为了找到重做(REDO)点,PostgreSQL 通过内建函数read_backup_label从备份标签文件中读取'CHECKPOINT LOCATION'的值。
(2) PostgreSQL 从recovery.conf(11或更早版本)或 postgresql.conf (12或更高版本)中读取一些参数值;在此例中,restore_command 和 recovery_target_time。
(3) PostgreSQL 从由'CHECKPOINT LOCATION'的值中轻易获得的重做(REDO)点开始重放WAL数据。从归档日志中读取WAL数据,它们是通过执行写入到 restore_command 参数的命令将归档目录中归档日志复制到临时目录。(临时目录复制过来的日志文件在使用后被删除)
在此例中,PostgreSQL 从重做(REDO)点开始读取并重放WAL数据到时间戳“2022-3-27 12:05:00”之前的哪个归档日志,因为recovery_target_time 参数设置为该时间戳。如果 recovery.conf(11 或更早版本)或 recovery.signal(12 或更高版本)文件未设置恢复目标,PostgreSQL 将重放到归档日志的末端。
(4) 完成恢复进程后,在pg_xlog子目录(在10或更高版本为pg_wal子目录)创建一个时间线历史文件(timeline history file),如:'00000002.history';如果开启了日志归档功能,还会在归档目录下创建同名文件。该文件的内容和作用将在下面章节中描述。
提交和中止操作的记录包含每个操作完成的时间戳(两种操作的 XLOG 数据部分分别在 xl_xact_commit 和 xl_xact_abort 中定义)。因此,如果为参数 recovery_target_time设置目标时间,每当重放提交或中止操作的 XLOG 记录时,PostgreSQL 可以选择是否继续恢复。当重放XLOG记录中的每个操作时,PostgreSQL比较目标时间和记录中写入的每个时间戳;如果时间戳超过目标时间,则完成 PITR 过程。
read_backup_label 函数定义在 src/backend/access/transam/xlog.c
xl_xact_commit 和 xl_xact_abort 结构体定义在 src/include/access/xact.h
为什么我们可以使用常用的归档工具来进行基础备份?
恢复过程是使数据库集簇处于一致状态的过程,尽管集簇是不一致的。由于 PITR 基于恢复过程,即使基础备份是一堆不一致的文件,它也可以恢复数据库集簇。这就是我们可以在没有文件系统快照功能或专用工具的情况下使用常用归档工具的原因。
10.3 时间线ID( timelineId )和时间线历史文件(timeline history file)
PostgreSQL 中的 Timeline 用于区分源数据库集簇和恢复的数据库集簇,是 PITR 的核心概念。在本节中,描述了与时间线相关的两件事:timelineId 和时间线历史文件(timeline history files)。
10.3.1 timelineId
每个时间线都有一个对应的时间线 ID(timelineId),一个从 1 开始的 4 字节无符号整数。
为每个数据库集簇分配一个单独的时间线 ID。initdb实用程序创建的源端数据库集簇的timelineId为1。每当数据库集簇恢复时,timelineId都会增加1。比如上一节的例子,从源端恢复的集簇的timelineId是2。
图 10.3 从timelineId的角度说明了 PITR 过程。首先,我们移除当前的数据库集簇,然后用先前做的基础备份恢复,以便回到恢复的起点,这种情况如图中红色箭头曲线所示。接下来,我们启动 PostgreSQL 服务器,该服务器从 pg_start_backup 创建的重做(REDO)点开始重放归档日志中的 WAL 数据,通过沿着初始时间线(timelineId 1)进行跟踪,直到恢复目标,这种情况在图中的蓝色箭头线表示。然后,一个新的timelineId 2 被分配给恢复的数据库集簇,PostgreSQL 在新的时间线上运行。
Fig. 10.3. Relation of timelineId between an original and a recovered database clusters.
正如第 9 章中简要提到的,WAL 段文件名的前 8 位等于每个段创建的数据库集簇的时间线 ID。当timelineId改变时,WAL段文件名也会改变。
重点关注 WAL 段文件,将再次描述恢复过程。假设我们使用两个归档日志 '000000010000000000000009' 和 '00000001000000000000000A' 来恢复数据库集簇。新恢复的数据库集簇分配了timelineId 2,PostgreSQL 从'00000002000000000000000A' 开始创建WAL段。图 10.4 显示了这种情况。
Fig. 10.4. Relation of WAL segment files between an original and a recovered database clusters.
10.3.2 时间线历史文件
完成恢复进程后,在pg_xlog子目录下(在10或更高版本为pg_wal子目录)创建一个名为'00000002.history'的时间线历史文件。该文件记录了时间线分支来自哪里和时间线。
该文件的命名规则如下显示:
"8-digit new timelineId".history
每个时间线历史文件包含至少一行,每行由下面的三项组成:
- timelineId - 用于恢复的归档日志的时间线ID
- LSN - WAL段文件发生切换时的LSN位置
- reason - 对时间线更改原因的可读解释
一个具体的示例如下所示:
postgres> cat /home/postgres/archivelogs/00000002.history
1 0/A000198 before 2022-3-27 12:05:00.861324+00
含义如下:
数据库集簇(timelineId =2) 是基于timelineId为1的基础备份,通过归档日志重放直到0/A000198恢复到'2022-3-27 12:05:00.861324+00'之前的时间点。
这样,每个时间线历史文件告诉我们恢复数据库集簇的完成历史。此外,它也用于 PITR 过程本身。详细内容将在下一节中解释。
在9.3版本改变了时间线历史文件的格式。下面显示了9.3版本之前和之后的格式,但没有详细介绍
9.3后
timelineId LSN "reason"
直到9.2版本
timelineId WAL_segment "reason"
10.4 使用时间线历史文件进行时间点恢复
时间线历史文件在第二个和后续的 PITR 过程中起着重要作用。通过尝试第二次恢复,我们将探索它是如何使用的。
再次,假设您在 12:15:00 在timelineId 为2 的恢复数据库集簇中执行了一个误操作。在这种情况下,要恢复数据库集簇,您应该创建一个新的recovery.conf,如下所示:
restore_command = 'cp /mnt/server/archivedir/%f %p'
recovery_target_time = "2022-3-27 12:15:00 GMT"
recovery_target_timeline = 2
参数 recovery_target_time 设置为误错误的时刻,recovery_target_timeline 设置为'2',以便沿着这条时间线恢复。
重启PostgreSQL服务器以便进入PITR模式,沿着timelineId 2 恢复数据库直到目标时间。见图10.5。
Fig. 10.5. Recover the database at 12:15:00 along the timelineId 2.
(1) PostgreSQL 从备份标签文件中读取'CHECKPOINT LOCATION'的值
(2) 从 recovery.conf 中读取一些参数值;在此示例中的 restore_command、recovery_target_time 和 recovery_target_timeline
(3) PostgreSQL读取参数recovery_target_timeline的值对应的时间线历史文件'00000002.history'
(4) PostgreSQL 通过以下步骤重放 WAL 数据:
- 从重做(REDO)点到写入00000002.history文件的LSN '0/A000198',PostgreSQL读取并重放timelineId为1的相应归档日志的WAL数据
- 从LSN '0/A000198'之后到时间戳'2022-3-27 12:15:00'之前,PostgreSQL读取并重放timelineId为2的WAL数据(相应归档日志)
(5) 完成恢复进程后,当前的timelineId会推进到3,在pg_xlog子目录下(在10或更高版本为pg_wal子目录)和归档目录下创建一个名为'00000003.history'的新时间线历史文件。
```sh
postgres> cat /home/postgres/archivelogs/00000003.history
1 0/A000198 before 2022-3-27 12:05:00.861324+00
2 0/B000078 before 2022-3-27 12:15:00.927133+00
```
当您多次执行 PITR 时,您应该明确设置一个时间线 ID 以使用适当的时间线历史文件。
这样,时间线历史文件不仅是数据库集簇的历史日志,还是PITR过程的恢复说明文件。