9.3 9.4 9.5 9.6 10 11 12 13 14
阿里云PostgreSQL 问题报告 纠错本页面

25.3. 连续归档和时间点恢复(PITR)

25.3.1. 建立WAL归档
25.3.2. 制作一个基础备份
25.3.3. 使用低级API制作一个基础备份
25.3.4. 使用一个连续归档备份进行恢复
25.3.5. 时间线
25.3.6. 建议和例子
25.3.7. 警告

在任何时间,PostgreSQL在数据集簇目录的pg_wal/子目录下都保持有一个预写式日志(WAL)。这个日志存在的目的是为了保证崩溃后的安全:如果系统崩溃,可以重放从最后一次检查点以来的日志项来恢复数据库的一致性。该日志的存在也使得第三种备份数据库的策略变得可能:我们可以把一个文件系统级别的备份和WAL文件的备份结合起来。当需要恢复时,我们先恢复文件系统备份,然后从备份的WAL文件中重放来把系统带到一个当前状态。这种方法比之前的方法管理起来要更复杂,但是有其显著的优点:

注意

pg_dumppg_dumpall不会产生文件系统级别的备份,并且不能用于连续归档方案。这类转储是逻辑的并且不包含足够的信息用于WAL重放。

就简单的文件系统备份技术来说,这种方法只能支持整个数据库集簇的恢复,却无法支持其中一个子集的恢复。另外,它需要大量的归档存储:一个基础备份的体积可能很庞大,并且一个繁忙的系统将会产生大量需要被归档的WAL流量。尽管如此,在很多需要高可靠性的情况下,它是首选的备份技术。

要使用连续归档(也被很多数据库厂商称为在线备份)成功地恢复,你需要一个从基础备份时间开始的连续的归档WAL文件序列。为了开始,在你建立第一个基础备份之前,你应该建立并测试用于归档WAL文件的过程。对应地,我们首先讨论归档WAL文件的机制。

25.3.1. 建立WAL归档

抽象地来说,一个运行中的PostgreSQL系统产生一个无穷长的WAL记录序列。系统从物理上将这个序列划分成WAL 段文件,通常是每个16MB(段尺寸在构建PostgreSQL时可修改)。段文件会被分配一个数字名称以便反映它在整个抽象WAL序列中的位置。在没有使用WAL归档时,系统通常只创建少量段文件,并且通过重命名不再使用的段文件为更高的段编号来回收它们。系统假设内容位于最后一个检查点之前的段文件是无用的且可以被回收。

在归档WAL数据时,我们需要在每一段被填充满时捕捉其内容,并且在段文件被回收重用之前保存该数据。依靠应用和可用的硬件,有很多不同的方法来保存数据:我们可以将段文件拷贝到一个已挂载的位于另一台机器上的NFS目录,或者将它们写出到一个磁带驱动器(确保你有办法标识每个文件的原始文件名),或者将它们批量烧录到CD上,或者其他什么方法。为了向数据库管理员提供灵活性,PostgreSQL不对如何归档做任何假设。取而代之的是,PostgreSQL让管理员声明一个shell命令来拷贝一个完整的段文件到它需要去的地方。 该命令可以简单得就是一个cp,或者它可以调用一个复杂的 shell 脚本 — 所有都由你决定。

要启用WAL归档,需设置wal_level配置参数为replica或更高,设置archive_modeon,并且使用archive_command配置参数指定一个shell命令。实际上,这些设置总是被放置在postgresql.conf文件中。在archive_command中,%p会被将要归档的文件路径所替代,而%f只会被文件名所替代(路径名是相对于当前工作目录而言的,即集簇的数据目录)。如果你需要在命令中嵌入一个真正的%字符,可以使用%%。最简单的命令类似于:

archive_command = 'test ! -f /mnt/server/archivedir/%f && cp %p /mnt/server/archivedir/%f'  # Unix
archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"'  # Windows

它将把 WAL 段拷贝到目录/mnt/server/archivedir(这个只是一个例子,并非我们建议的方法,可能不能在所有系统上都正确运行)。在%p%f参数被替换之后,实际被执行的命令看起来可能是:

test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp pg_wal/00000001000000A900000065 /mnt/server/archivedir/00000001000000A900000065

对每一个将要被归档的新文件都会生成一个类似的命令。

归档命令将在运行PostgreSQL服务器的同一个用户的权限下执行。因为被归档的一系列WAL 文件实际上包含你的数据库里的所有东西,所以你应该确保自己的归档数据不会被别人窥探; 比如,归档到一个没有组或者全局读权限的目录里。

有一点很重要:当且仅当归档命令成功时,它才返回零退出。在得到一个零值结果之后,PostgreSQL将假设该文件已经成功归档, 因此它稍后将被删除或者被新的数据覆盖。但是,一个非零值告诉PostgreSQL该文件没有被归档; 因此它会周期性的重试直到成功。

归档命令通常应该被设计成拒绝覆盖已经存在的归档文件。这是一个非常重要的安全特性, 可以在管理员操作失误(比如把两个不同的服务器的输出发送到同一个归档目录)的时候保持你的归档的完整性。

我们建议你首先要测试你准备使用到归档命令,以保证它实际上不会覆盖现有的文件, 并且在这种情况下它返回非零状态。以上Unix中的命令例子通过包含一个独立的test步骤来保证这一点。在某些Unix平台上,cp具有诸如-i的开关,可用来更简洁地完成这一切,但是在没有验证返回的退出状态正确之前你不能依赖它们(特别地,GNU的cp在使用-i时将对已存在的目标文件返回状态零,这并是我们所期望的行为)。

在设计你的归档环境时,请考虑一下如果归档命令不停失败会发生什么情况, 因为有些情况要求操作者的干涉,或者是归档空间不够了。 例如,如果你往磁带机上写,但是没有自动换带机,那么就有可能发生这种情况; 如果磁带满了,除非换磁带,否则任何事也做不了。 你应该确保任何错误情况或者任何要求操作员干涉的情况都会被正确报告, 这样才能迅速解决这些问题。否则pg_wal/目录会不停地被WAL段文件填充, 直到问题解决(如果包含pg_wal/的文件系统被填满,PostgreSQL将会做一次致命关闭。不会有未提交事务被丢失,但是数据库将会保持离线直到你释放一部分空间)。

归档命令的速度并不要紧,只要它能跟上你的服务器生成 WAL 数据的平均速度即可。即使归档进程稍微落后,正常的操作也会继续进行。 如果归档进程慢很多,就会增加灾难发生的时候丢失的数据量。这同时也意味着pg_wal/目录包含大量未归档的段文件, 并且可能最后超出了可用磁盘空间。我们建议你监控归档进程,确保它是按照你的期望运转的。

在写自己的归档命令的时候,你应该假设被归档的文件名最长为64个字符并且可以包含 ASCII 字母、数字以及点的任意组合。 我们不需要保持原始的相对路径(%p),但是有必要保持文件名(%f)。

请注意尽管 WAL 归档允许你恢复任何对你的PostgreSQL数据库中数据所做的修改, 但它不会恢复对配置文件的修改(即postgresql.conf、pg_hba.conf 和 pg_ident.conf),因为这些文件都是手工编辑的,而不是通过 SQL 操作来编辑的。 所以你可能会需要把你的配置文件放在一个日常文件系统备份过程可处理的位置。如何重定位配置文件请参阅第 19.2 节

归档命令只会为完成的WAL段调用。因此如果你的服务器产生了一点点WAL流量(或者在产生时有宽松的周期),从一个事务完成到它被安全地记录在归档存储中之间将会有较长的延迟。要为未归档数据设置一个年龄限制,你可以设置archive_timeout来强制要求服务器按照其设定的频度切换到一个新的WAL段。注意由于强制切换而被归档的文件还是具有和完全归档的文件相同的长度。因此设置一个很短的archive_timeout是很不明智的 — 它会膨胀你的归档存储。将archive_timeout设置为1分钟左右通常是合理的。

同样,如果你希望确保一个刚刚完成的事务能被尽快归档,可以使用pg_switch_xlog进行一次手动段切换。其他与WAL管理相关的使用函数在表 9.79中列出。

第 14.4.7 节所述,当wal_levelminimal时,一些SQL命令被优化为避免记录WAL日志。在这些语句的其中之一的执行过程中如果打开了归档或流复制,WAL中将不会包含足够的信息用于归档恢(崩溃恢复不受影响)。出于这个原因,wal_level只能在服务器启动时修改。但是,archive_command可以通过重载配置文件来修改。如果你希望暂时停止归档,一种方式是将archive_command设置为空串('')。这将导致WAL文件积累在pg_wal/中,直到一个可用的archive_command被重新建立。

25.3.2.  制作一个基础备份

执行一次基础备份最简单的方法是使用pg_basebackup工具。它将会以普通文件或一个tar归档的方式创建一个基础备份。如果需要比pg_basebackup更高的灵活性,你也可以使用低级API(见第 25.3.3 节)来制作一个基础备份。

没有必要关心创建一个基础备份所需的时间。但是,如果你正常地运行停用了full_page_writes的服务器,你可能会注意到备份运行时的性能下降,因为full_page_writes在备份模式期间会被实际强制实施。

要使用备份,你将需要保留所有在文件系统备份期间及之后生成的WAL段文件。为了便于你做这些,基础备份过程会创建一个备份历史文件,它将被立刻存储到WAL归档区域。该文件以文件系统备份中你需要的第一个WAL段文件命名。例如,如果开始的WAL文件是0000000100001234000055CD,则备份历史文件将被命名为0000000100001234000055CD.007C9330.backup.(文件名的第二部分表明WAL文件中的一个准确位置,一般可以被忽略)。一旦你已经安全地归档了文件系统备份和在备份过程中被使用的WAL段文件(如备份历史文件中所指定的) ,所有名字在数字上低于备份历史文件中记录值的已归档WAL段对于恢复文件系统备份就不再需要了,并且可以被删除。但是你应该考虑保持多个备份集以绝对保证你能恢复你的数据。

备份历史文件是一个很小的文本文件。它包含你指定给pg_basebackup的标签字符串,以及备份的起止时间以及起止WAL段。如果你使用该标签来标识相关转储文件,则已归档的历史文件足以说明需要哪个转储文件进行恢复。

由于你不得不保存最后一次基础备份之后的所有归档WAL文件,基础备份之间的间隔通常应该根据你希望在归档WAL文件上花费的存储空间来设定。你也应该考虑你准备花多长时间来进行恢复,如果需要恢复 — 系统将不得不重放所有那些WAL段,如果这些WAL段覆盖了最后一次基础备份以后的很长时间,重放过程将会花费一些时间。

25.3.3. 使用低级API制作一个基础备份

使用低级API制作一个基础备份的过程比pg_basebackup方法要包含更多的步骤,但相对要更简单。很重要的一点是,这些步骤要按照顺序执行,并且在执行下一步之前要验证上一步是否成功。

可以用非排他或者排他的方法来制作低级基础备份。我们推荐非排他方法,而排他 的方法已经被废弃并且最终将被去除。

25.3.3.1. 制作一个非排他低级备份

非排他低级备份允许其他并发备份运行(既包括那些使用同样的 备份 API 开始的备份,也包括那些使用 pg_basebackup开始的备份)。

  1. 确保WAL归档被启用且正在工作。

  2. 作为一个具有运行 pg_start_backup 权利的用户(超级用户,或者被授予在该 函数上 EXECUTE 的用户)连接到服务器(不在乎是哪个数据库)并且发出命令:

    SELECT pg_start_backup('label', false, false);

    其中label是用来唯一标识这次备份操作的任意字符串。调用 pg_start_backup的连接必须被保持到备份结束,否则备份 将被自动中止。

    默认情况下,pg_start_backup可能需要较长的时间完成。 这是因为它会执行一个检查点,并且该检查点所需要的 I/O 将会分散到一段 显著的时间上,默认情况下是你的检查点间隔(见配置参数 checkpoint_completion_target)的一半。这通常 是你所想要的,因为它可以最小化对查询处理的影响。如果你想要尽可能快地 开始备份,请把第二个参数改成true, 这将使用尽可能多的I/O发出即时检查点。

    第三个参数为false会告诉pg_start_backup 开始一次非排他基础备份。

  3. 使用任何趁手的文件系统备份工具(例如tar或者 cpio,不是pg_dump 或者pg_dumpall)执行备份。当你做这些 时,不需要也不值得停止正常的数据库操作。在这类备份期间要考虑的事情 请见第 25.3.3.3 节

  4. 在同一个连接中,发出命令:

    SELECT * FROM pg_stop_backup(false);

    这会终止备份模式。在主服务器上,它还自动切换到下一个 WAL 段。 在备服务器上,无法自动切换WAL段,因此您可能希望在主服务器上运行 pg_switch_wal以执行手动切换。 切换的原因是让备份间隔期间写入的最后一个WAL段文件准备归档。

    pg_stop_backup将返回一个具有三个值的行。这些域的 第二个应该被写入到该备份根目录中名为backup_label的 文件。第三个域应该被写入到一个名为tablespace_map 的文件,除非该域为空。这些文件对该备份正常工作来说是至关重要的, 不能被随意修改。

  5. 一旦备份期间活动的 WAL 段文件被归档,备份就完成了。由 pg_stop_backup的第一个返回值标识的文件是构成一个 完整备份文件集合所需的最后一个段。在主服务器上,如果 archive_mode被启用,并且wait_for_archive 参数为true,则直到最后一个段被归档前pg_stop_backup都不会 返回。在备用服务器上,为了使pg_stop_backup等待, archive_mode必须是always。 从你已经配置好archive_command之后这些文件的 归档就会自动发生。在大部分情况下,这些归档会很快发生,但是建议你监 控你的归档系统确保没有延迟。如果归档进程由于归档命令的失败而落后, 它将会持续重试直到归档成功并且备份完成。如果你希望对 pg_stop_backup的执行给出一个时间限制,可以设置一个 合适的statement_timeout值,但要注意如果 pg_stop_backup因此而中止会致使备份可能失效。

    如果备份进程监视并确保备份所需的所有WAL段文件已成功归档,则可以将 wait_for_archive参数(默认为true)设置为false以使 停止备份记录写入WAL后,pg_stop_backup立即返回。 默认情况下,pg_stop_backup将一直等到所有WAL归档完毕, 这可能需要一些时间。必须谨慎使用此选项:如果未正确监控WAL归档, 则备份可能未包括所有WAL文件,因此将不完整且无法恢复。

25.3.3.2. 制作一个排他低级备份

一个排他备份的处理绝大部分都和非排他备份相同,但是在一些关键步骤上 不同。这种类型的备份只能在主服务器上进行,并且不允许并发备份。 在PostgreSQL 9.6 之前,这是唯一可用的低级方法,但是现在 推荐所有用户在可能的情况下升级他们的脚本来使用非排他备份。

  1. 确保 WAL 归档被启用且正常工作。

  2. 作为一个具有运行 pg_start_backup 权利的用户(超级用户,或者被授予在该 函数上 EXECUTE 的用户)连接到服务器(不在乎是哪个数据库)并且发出命令:

    SELECT pg_start_backup('label');

    这里label是任何你希望用来唯一标识这个备份操作的字符串。 pg_start_backup在集簇目录中创建一个关于备份信息的 备份标签文件,也被称为backup_label, 其中包括了开始时间和标签字符串。该函数也会在集簇目录中创建一个 名为tablespace_map表空间映射文件, 如果在pg_tblspc/中有一个或者多个表空间符号链接存在, 该文件会包含它们的信息。如果你需要从备份中恢复,这两个文件对于备份的 完整性都至关重要。

    默认情况下,pg_start_backup会花费很长时间来完成。这是因为它会执行一个检查点,而检查点所需要的I/O在相当一段时间内将会被传播,默认情况下这段时间是内部检查点间隔的一半(参见配置参数checkpoint_completion_target)。这通常是你所希望的,因为它能将对查询处理的影响最小化。如果你要尽快开始备份,可使用:

    SELECT pg_start_backup('label', true);

    这会使检查点尽可能快地被完成。

  3. 使用任何方便的文件系统备份工具执行备份,例如tarcpio(不是pg_dumppg_dumpall)。在此期间,不需要也 不值得停止正常的数据库操作。在备份期间要考虑的事情可见 第 25.3.3.3 节

    注意,如果服务器在备份期间崩溃,直到从PGDATA 目录手动删除backup_label文件,才可能重新启动。

  4. 再次以具有运行 pg_stop_backup 权利的用户(超级用户,或者已经被授予 该函数上 EXECUTE 的用户)连接到数据库并且发出命令:

    SELECT pg_stop_backup();

    该函数终止备份模式,并且执行一个自动切换到下一个WAL段。 进行切换的原因是将在备份期间生成的最新WAL段安排为可归档。

  5. 一旦备份期间活动的WAL段文件被归档,你的工作就完成了。 pg_stop_backup的结果所标识的文件是构成一个完整备份 文件组所需的最新段。如果archive_mode被启用,直到最 新段被归档pg_stop_backup都不会返回。由于你已经配置了 archive_command,这些文件的归档过程会自动发生。在 大部分情况下这会很快发生,但还是建议你监控你的归档系统来确保不会有 延迟。如果归档处理由于归档命令的错误而延迟,它会保持重试直到归档成 功和备份完成。如果你希望在pg_stop_backup的执行上 设置一个时间限制,可对statement_timeout设 置一个合适的值,但要注意如果pg_stop_backup因此而 中止会致使备份可能失效。

25.3.3.3. 备份数据目录

如果被拷贝的文件在拷贝过程中发生变化,某些文件系统备份工具会发出警告或错误。在建立一个活动数据库的基础备份时,这种情况是正常的,并非一个错误。然而,你需要确保你能够把它们和真正的错误区分开。例如,某些版本的rsync消失的源文件返回一个独立的退出码,且你可以编写一个驱动脚本来将该退出码接受为一种非错误情况。同样,如果一个文件在被tar复制的过程中被截断,某些版本的GNU tar会返回一个与致命错误无法区分的错误代码。幸运的是,如果一个文件在备份期间被改变,版本为1.16及其后的GNU tar将会退出并返回1,而对于其他错误返回2。在版本1.23及其后的GNU tar中,你可以使用警告选项--warning=no-file-changed --warning=no-file-removed来隐藏相关的警告消息。

确认你的备份包含数据库集簇目录(例如/usr/local/pgsql/data)下的所有文件。如果你使用了不在此目录下的表空间,注意也把它们包括在内(并且确保你的备份将符号链接归档为链接,否则恢复过程将破坏你的表空间)。

不过,你应当从备份中忽略集簇的pg_wal/子目录中的文件。这种微小的调整是值得的,因为它降低了恢复时的错误风险。如果pg_wal/是一个指向位于集簇目录之外其他地方的符号链接就很容易安排了,这是一种出于性能原因的常见设置。你可能也希望排除postmaster.pidpostmaster.opts,它们记录了关于postmaster运行的信息,但与最终使用这个备份的postmaster无关(这些文件可能会使pg_ctl搞混淆)。

从备份中忽略集簇的pg_replslot/子目录中的文件通常也是个好主意,这样 主控机上存在的复制槽不会成为备份的一部分。否则,后续用该备份创建一个后备机可能会导致该 后备机上的WAL文件被无限期保留,并且在启用了热后备反馈的情况下可能导致主控机膨胀,因为使用 那些复制槽的客户端将继续连接到主控机(而不是后备机)并且继续更新其上的槽。即使该备份是要被 用来创建一个新的主控机,拷贝复制槽也不是特别有用,因为这些槽的内容在新主控机上线时很可能已 经过时。

pg_dynshmem/pg_notify/pg_serial/pg_snapshots/pg_stat_tmp/pg_subtrans/目录的内容()(但不是目录本身)可以从备份中省略, 因为它们将在postmaster启动时初始化。如果设置了 stats_temp_directory并且是在数据目录下, 那么也可以省略该目录的内容。

任何以pgsql_tmp开头的文件或目录都可以从备份中省略。 这些文件在postmaster启动时被删除,并且根据需要重新创建目录。

备份标签文件包含你指定给 pg_start_backup的标签字符串,以及 pg_start_backup被运行的时刻和起始WAL文件的名字。 在发生混乱的情况下就可以在备份文件中查看并准确地决定该转储文件来 自于哪个备份会话。表空间映射文件包括存在于目录pg_tblspc/ 中的符号链接名称以及每一个符号链接的完整路径。这些文件不仅是为了供参考, 它们的存在和内容对于系统恢复过程的正确操作是至关重要。

在服务器停止时也可以创建一个备份。在这种情况下,你显然不能使用 pg_start_backuppg_stop_backup, 并且因此你只能依靠你的自己的策略来跟踪哪个备份是哪个, 以及相关WAL文件应该走回到什么程度。通常最好遵循上面的连续归档过程。

25.3.4. 使用一个连续归档备份进行恢复

好,现在最坏的情况发生了,你需要从你的备份进行恢复。这里是其过程:

  1. 如果服务器仍在运行,停止它。

  2. 如果你具有足够的空间,将整个集簇数据目录和表空间复制到一个临时位置,稍后你将用到它们。注意这种预防措施将要求在你的系统上有足够的空闲空间来保留现有数据库的两个拷贝。如果你没有足够的空间,你至少要保存集簇的pg_wal子目录的内容,因为它可能包含在系统垮掉之前还未被归档的日志。

  3. 移除所有位于集簇数据目录和正在使用的表空间根目录下的文件和子目录。

  4. 从你的文件系统备份中恢复数据库文件。注意它们要使用正确的所有权恢复(数据库系统用户,不是root!)并且使用正确的权限。如果你在使用表空间,你应该验证pg_tblspc/中的符号链接被正确地恢复。

  5. 移除pg_wal/中的任何文件,这些是来自于文件系统备份而不是当前日志,因此可以被忽略。如果你根本没有归档pg_wal/,那么以正确的权限重建它。注意如果以前它是一个符号链接,请确保你也以同样的方式重建它。

  6. 如果你有在第2步中保存的未归档WAL段文件,把它们拷贝到pg_wal/(最好是拷贝而不是移动它们,这样如果在开始恢复后出现问题你任然有未修改的文件)。

  7. 在集簇数据目录中创建一个恢复命令文件recovery.conf(见第 27 章)。你可能还想临时修改pg_hba.conf来阻止普通用户在成功恢复之前连接。

  8. 启动服务器。服务器将会进入到恢复模式并且进而根据需要读取归档WAL文件。恢复可能因为一个外部错误而被终止,可以简单地重新启动服务器,这样它将继续恢复。恢复过程结束后,服务器将把recovery.conf重命名为recovery.done(为了阻止以后意外地重新进入恢复模式),并且开始正常数据库操作。

  9. 检查数据库的内容来确保你已经恢复到了期望的状态。如果没有,返回到第1步。如果一切正常,通过恢复pg_hba.conf为正常来允许用户连接。

所有这些的关键部分是设置一个恢复配置文件,它描述你希望如何恢复以及恢复要运行到什么程度。你可以使用recovery.conf.sample(通常在安装的share/目录中)作为一个原型。你绝对必须在recovery.conf中指定的是restore_command,它告诉PostgreSQL如何获取归档WAL文件段。与archive_command相似,这也是一个shell命令字符串。它可以包含%f(将被期望的日志文件名替换)和%p(将被日志文件被拷贝的目标路径名替换)。(路径名是相对于当前工作目录的,即集簇的数据目录)。如果你需要在命令中嵌入一个真正的%字符,可以写成%%。最简单的命令类似于:

restore_command = 'cp /mnt/server/archivedir/%f %p'

它将从目录/mnt/server/archivedir中拷贝之前归档的WAL段。当然,你可以使用更复杂的,甚至是一个要求操作者装载合适磁带的shell脚本。

重要的是命令在失败时返回非零退出状态。该命令被调用来请求不在归档中的文件, 在这种情况下它应该返回非零值。这不是一种错误情况。一种例外是该命令被一个信号(除了被用作数 据库服务器关闭动作一部分的SIGTERM)终止或者被shell的错误 (例如命令未找到)终止,那样恢复将中止并且服务器将不会启动。

并非所有被请求的文件都是WAL段文件,你也许还会请求一些具有.backup.history后缀的文件。还要注意的是,%p路径的基本名字将会和%f 不同,但不要期望它们可以互换。

归档中找不到的WAL段可以在pg_wal/中看到,这使得可以使用最近未归档的段。但是,在归档中可用的段将会被优先于pg_wal/中的文件被使用。

通常,恢复将会处理完所有可用的WAL段,从而将数据库恢复到当前时间点(或者尽可能接近给定的可 用WAL段)。因此,一个正常的恢复将会以一个文件未找到消息结束,错误消息的准确文 本取决于你选择的restore_command。你也可能在恢复的开始看到一个针对名称类 似于00000001.history文件的错误消息。这也是正常的并且不表示在简单恢复情 况中的问题,对此的讨论见第 25.3.5 节

如果你希望恢复到之前的某个时间点(例如,恢复到幼稚的DBA丢弃了你主要的交易表之前),只需要 在recovery.conf中指定要求的停止点。你可以使用日期/时间、命名恢复点或一个 指定事务ID的结束时间来定义停止点(也被称为恢复目标)。在这种写法中,只有日期/时 间和命名恢复点选项非常有用,因为没有工具可以帮助你准确地确定要用哪个事务ID。

注意

停止点必须位于基础备份的完成时间之后,即pg_stop_backup的完成时间。在备份过程中你不能使用基础备份来恢复(要恢复到这个时间,你必须回到你之前的基础备份并且从这里开始前滚)。

如果恢复找到被破坏的WAL数据,恢复将会停止于该点并且服务器不会启动。在这种情况下,恢复进程需要从开头重新开始运行,并指定一个在损坏点之前的恢复目标以便恢复能够正常完成。如果恢复由于一个外部原因失败,例如一个系统崩溃或者WAL归档变为不可访问,则该次恢复可以被简单地重启并且它将会从几乎是上次失败的地方继续。恢复重启工作起来很像普通操作时的检查点:服务器周期性地强制把它的所有状态写到磁盘中,然后更新pg_control文件来说明已经处理过的WAL数据,这样它们就不会被再次扫描。

25.3.5. 时间线

将数据库恢复到一个之前的时间点的能力带来了一些复杂性,这和有关时间旅行和平行宇宙的科幻小说有些相似。例如,在数据库的最初历史中,假设你在周二晚上5:15时丢弃了一个关键表,但是一直到周三中午才意识到你的错误。不用苦恼,你取出你的备份,恢复到周二晚上5:14的时间点,并上线运行。在数据库宇宙的这个历史中,你从没有丢弃该表。但是假设你后来意识到这并非一个好主意,并且想回到最初历史中周三早上的某个时间。你没法这样做,在你的数据库在线运行期间,它重写了某些WAL段文件,而这些文件本来可以将你引向你希望回到的时间。因此,为了避免出现这种状况,你需要将完成时间点恢复后生成的WAL记录序列与初始数据库历史中产生的WAL记录序列区分开来。

要解决这个问题,PostgreSQL有一个时间线概念。无论何时当一次归档恢复完成,一个新的时间线被创建来标识恢复之后生成的WAL记录序列。时间线ID号是WAL段文件名的一部分,因此一个新的时间线不会重写由之前的时间线生成的WAL数据。实际上可以归档很多不同的时间线。虽然这可能看起来是一个无用的特性,但是它常常扮演救命稻草的角色。考虑到你不太确定需要恢复到哪个时间点的情况,你可能不得不做多次时间点恢复尝试和错误,直到最终找到从旧历史中分支出去的最佳位置。如果没有时间线,该处理将会很快生成一堆不可管理的混乱。而有了时间线,你可以恢复到任何之前的状态,包括早先被你放弃的时间线分支中的状态。

每次当一个新的时间线被创建,PostgreSQL会创建一个时间线历史文件,它显示了新时间线是什么时候从哪个时间线分支出来的。系统在从一个包含多个时间线的归档中恢复时,这些历史文件对于允许系统选取正确的WAL段文件非常必要。因此,和WAL段文件相似,它们也要被归档到WAL归档区域。历史文件是很小的文本文件,因此将它们无限期地保存起来的代价很小,而且也是很合适的(而段文件都很大)。如果你喜欢,你可以在一个历史文件中增加注释来记录如何和为什么要创建该时间线。当你由于试验的结果拥有了一大堆错综复杂的不同时间线时,这种注释将会特别有价值。

恢复的默认行为是沿着相同的时间线进行恢复,该时间线是基础备份创建时的当前时间线。如果你希望恢复到某个子女时间线(即,你希望回到在一次恢复尝试后产生的某个状态),你需要在recovery.conf中指定目标时间线ID。你不能恢复到早于该基础备份之前分支出去的时间线。

25.3.6. 建议和例子

这里将给出一些配置连续归档的建议。

25.3.6.1. 单机热备份

可以使用PostgreSQL的备份功能来产生单机热备份。这些备份不能被用于时间点恢复,然而备份和恢复时要比使用pg_dump转储更快(它们也比pg_dump转储更大,所以在某些情况下速度优势可能会被否定)。

在基础备份的帮助下,产生一个单机热备份最简单的方式是使用 pg_basebackup工具。如果你在调用它时使用了 -X参数,使用该备份所需的所有预写式日志将会被自动包含在该备份中,并且恢复该备份也不需要特殊的动作。

如果在复制备份文件时需要更多灵活性,也可以使用一个较低层的处理来创建单机热备份。要为低层 单机热备份做准备,确保将wal_level设置为replica或更高, archive_mode设置为on,并且设置一个archive_command,该命令只当一个开关文件存在时执行归档。例如:

archive_command = 'test ! -f /var/lib/pgsql/backup_in_progress || (test ! -f /var/lib/pgsql/archive/%f && cp %p /var/lib/pgsql/archive/%f)'

该命令在/var/lib/pgsql/backup_in_progress存在时执行归档,否则会安静地返回0值退出状态(让PostgreSQL能回收不需要的WAL文件)。

通过这样的准备,可以使用一个如下所示的脚本来建立备份:

touch /var/lib/pgsql/backup_in_progress
psql -c "select pg_start_backup('hot_backup');"
tar -cf /var/lib/pgsql/backup.tar /var/lib/pgsql/data/
psql -c "select pg_stop_backup();"
rm /var/lib/pgsql/backup_in_progress
tar -rf /var/lib/pgsql/backup.tar /var/lib/pgsql/archive/

开关文件/var/lib/pgsql/backup_in_progress首先被创建,这使对于未完成WAL文件的归档操作发生。备份完成之后开关文件会被删除。归档的WAL文件则被加入到备份中,这样基础备份和所有需要的WAL文件都是同一个tar文件的组成部分。请记住在你的备份脚本中加入错误处理。

25.3.6.2. 压缩的归档日志

如果担心归档存储的尺寸,你可以使用gzip来压缩归档文件:

archive_command = 'gzip < %p > /var/lib/pgsql/archive/%f'

那么在恢复时你将需要使用gunzip

restore_command = 'gunzip < /mnt/server/archivedir/%f > %p'

25.3.6.3. archive_command脚本

很多人选择使用脚本来定义他们的archive_command,这样他们的postgresql.conf项看起来非常简单:

archive_command = 'local_backup_script.sh "%p" "%f"'

任何时候如果你希望在归档处理中使用多个命令,明智的方法是使用一个独立的脚本文件。这可以使脚本更为复杂,它可以使用一种流行的脚本语言来编写,例如bashperl

需要在一个脚本内解决的需求例子包括:

  • 将数据拷贝到安全的场外数据存储

  • 批处理WAL文件,这样它们可以每三小时被传输一次,而不是一次一个

  • 与其他备份和恢复软件交互

  • 与监控软件交互以报告错误

提示

在使用一个archive_command脚本时,最好启用logging_collector。任何从该脚本被写到stderr的消息将出现在数据库服务器日志中,这允许在复杂配置失败后能更容易被诊断。

25.3.7. 警告

在编写此文档时,连续归档技术存在一些限制。这可能会在未来的发布中被修复:

  • 如果一个CREATE DATABASE命令在基础备份时被执行,然后在基础备份进行时CREATE DATABASE所复制的模板数据库被修改,恢复中可能会导致这些修改也被传播到已创建的数据库中。这当然是我们不希望的。为了避免这种风险,最好不要在创建基础备份时修改任何模板数据库。

  • CREATE TABLESPACE命令会WAL以其字面绝对路径记录,并且因此将在重放时以相同的绝对路径来创建表空间。当日志在一台不同的机器上被重放时,这可能也不是我们希望的。即使日志在同一台机器上被重放也是危险的,就算是恢复到一个新的数据目录重放过程也会覆盖原来表空间的内容。为了避免这种潜在的陷阱,最佳做法是在创建或丢弃表空间后创建一个新的基础备份。

还需要注意的是,默认的WAL格式相当庞大,因为它包括了很多磁盘页快照。这些页快照被设计用于支持崩溃恢复,因为我们可能需要修复断裂的磁盘页。依靠你的系统硬件和软件,页断裂的风险可能会小到可以忽略,在此种情况下你可以通过使用full_page_writes参数关闭页快照来显著降低归档日志的总容量(在这样做之前阅读第 30 章中的注解和警告)。关闭页快照并不会阻止使用日志进行PITR操作。一个未来的开发点是通过移除不需要的页拷贝来压缩归档的WAL数据,即使full_page_writes为on。同时,管理员可能希望通过尽可能增大检查点间隔参数来减少WAL中包含的页快照数量。