选择 打开 改范围 完整检索页
受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10
当前 PostgreSQL 版本不在支持生命周期内。
您可以参阅当前版本的对应页面,或其他在上面列出的活跃大版本。

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

在任何时候,PostgreSQL都会维护一个预写式日志(WAL),它位于集簇数据目录的pg_wal/子目录中。该日志记录对数据库数据文件所做的每一次修改。这个日志首先是为崩溃安全而存在:如果系统崩溃,可以通过重放自上次检查点以来的日志记录,将数据库恢复到一致状态。不过,日志的存在也使第三种数据库备份策略成为可能:我们可以把文件系统级备份与 WAL 文件备份结合起来。如果需要恢复,就先恢复文件系统备份,再重放已备份的 WAL 文件,使系统回到当前状态。与前两种方法相比,这种方法管理起来更复杂,但它有一些显著优点:

  • 我们不需要一个完全一致的文件系统备份作为起点。备份中的任何内部不一致性都会通过日志重放纠正(这与崩溃恢复期间发生的事情并没有本质不同)。因此,我们不需要文件系统快照能力,只需要tar或类似的归档工具。

  • 由于我们可以把任意长的一串 WAL 文件串联起来进行重放,只要持续归档 WAL 文件,就可以实现连续备份。这对于大型数据库尤其有价值,因为此时频繁进行完整备份可能并不方便。

  • 不必把 WAL 记录一直重放到最后。我们可以在任意位置停止重放,并得到数据库在当时的一致快照。因此,这项技术支持时间点恢复:可以把数据库恢复到自基础备份之后任意时刻的状态。

  • 如果我们持续把这一串 WAL 文件输送给另一台已经装载了同一基础备份文件的机器,就得到了一个温备系统:我们随时都可以启用第二台机器,而它将拥有数据库几乎是最新的副本。

Note

pg_dumppg_dumpall不会生成文件系统级备份,因此不能作为持续归档方案的一部分使用。这类转储是逻辑备份,不包含 WAL 重放所需的足够信息。

和普通文件系统备份技术一样,这种方法只能支持整个数据库集簇的恢复,而不支持其中某个子集的恢复。此外,它需要大量归档存储:基础备份可能很庞大,繁忙系统也会产生大量必须归档的 WAL 流量。尽管如此,在很多需要高可靠性的场景中,它仍是首选的备份技术。

要想利用持续归档(许多数据库厂商也称之为在线备份)成功恢复,你需要一串连续的已归档 WAL 文件,其时间至少要追溯到备份的开始时刻。因此,入门时应在进行第一次基础备份之前就建立并测试好归档 WAL 文件的流程。下面先讨论归档 WAL 文件的机制。

25.3.1. 设置 WAL 归档 #

从抽象意义上说,一个运行中的PostgreSQL系统会产生一串无限长的 WAL 记录序列。系统在物理上把该序列划分为 WAL 段文件,通常每个 16MB(段大小可在initdb期间修改)。这些段文件会被赋予数字名称,以反映它们在抽象 WAL 序列中的位置。不使用 WAL 归档时,系统通常只创建少量段文件,然后通过把不再需要的段文件重命名为更高的段号来回收它们。系统假定内容早于最后一个检查点的段文件已不再有价值,因此可以被回收。

在归档 WAL 数据时,我们需要在每个段文件写满后捕获其内容,并在该段文件被回收重用之前把数据保存到某处。根据应用场景和可用硬件的不同,把数据保存到某处可以有很多不同的方法:可以把段文件复制到另一台机器上的 NFS 挂载目录,把它们写到磁带机中(确保你有办法识别每个文件的原始文件名),把它们批量打包后刻录到 CD 上,或者采用完全不同的方式。为了给数据库管理员提供灵活性,PostgreSQL尽量不对归档方式作任何假设。相反,PostgreSQL允许管理员指定一个 shell 命令或一个归档库,以便把已完成的段文件复制到它应去的地方。这既可以只是一个使用cp的 shell 命令,也可以调用一个复杂的 C 函数,全由你决定。

要启用 WAL 归档,请将 wal_level 配置参数设为 replica 或更高值,将 archive_mode 设为 on,并在 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将执行 PANIC 关闭。不会丢失已提交的事务,但在释放出一些空间之前,数据库会一直离线。)

归档命令或归档库的速度并不重要,只要它能跟上服务器生成 WAL 数据的平均速度即可。即使归档过程稍有滞后,正常操作也会继续。如果归档明显落后,灾难发生时可能丢失的数据量就会增加。这还意味着pg_wal/目录会包含大量尚未归档的段文件,最终可能耗尽可用磁盘空间。建议监控归档过程,确保它按你的预期工作。

在编写归档命令或归档库时,应假定待归档文件名最长可达 64 个字符,并且可以包含 ASCII 字母、数字和点号的任意组合。无需保留原始相对路径(%p),但必须保留文件名(%f)。

注意,虽然 WAL 归档允许你恢复对PostgreSQL数据库中数据所做的任何修改,但它不会恢复对配置文件(即postgresql.confpg_hba.confpg_ident.conf)的修改,因为这些文件是手工编辑的,而不是通过 SQL 操作修改的。你可能希望把配置文件放在常规文件系统备份过程能够覆盖的位置。关于如何重定位配置文件,见Section 19.2

归档命令只会在已完成的 WAL 段上调用。因此,如果服务器产生的 WAL 流量很小(或者存在这样的低谷期),事务完成到其被安全写入归档存储之间可能会有很长延迟。为了限制未归档数据的最长滞留时间,可以设置 archive_timeout,强制服务器至少每隔这么长时间切换到一个新的 WAL 段文件。注意,由强制切换而提前归档的文件长度仍与装满的文件相同。因此,把 archive_timeout 设得很短并不明智,这会使归档存储膨胀。将 archive_timeout 设为大约一分钟通常是合理的。

此外,如果你希望确保一个刚刚完成的事务尽快被归档,可以使用pg_switch_wal手工强制一次段切换。其他与 WAL 管理相关的实用函数列在Table 9.85中。

wal_levelminimal时,某些 SQL 命令会像Section 14.4.7中所述那样被优化为避免 WAL 记录。如果在执行这些语句期间启用了归档或流复制,WAL 将不包含归档恢复所需的足够信息。(崩溃恢复不受影响。)因此,wal_level只能在服务器启动时更改。然而,archive_command可以通过重新加载配置文件来更改。如果你通过 shell 进行归档并希望暂时停止归档,一种办法是将archive_command设置为空字符串('')。这会导致 WAL 文件在pg_wal/中累积,直到重新建立可用的archive_command

25.3.2. 进行基础备份 #

执行基础备份最简单的方法是使用pg_basebackup工具。它可以把基础备份创建为普通文件或 tar 归档。如果需要比pg_basebackup提供的更高灵活性,也可以使用低级 API 制作基础备份(见Section 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 段;如果距离上次基础备份已经过去很久,这可能会花费一些时间。

25.3.3. 使用低级接口进行基础备份 #

使用低级 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. 使用任何方便的文件系统备份工具执行备份,例如tarcpio(不要使用pg_dumppg_dumpall)。在此过程中既没有必要,也不希望停止数据库的正常运行。关于执行此备份时需要注意的事项,见Section 25.3.3.3

  4. 在之前的同一个连接中,执行命令:

    SELECT * FROM pg_stop_backup(false, true);
    

    这会终止备份模式。在主库上,还会自动切换到下一个 WAL 段。在备库上,无法自动切换 WAL 段,因此可以运行 pg_switch_wal,在主库上手动切换。切换的目的是让备份期间写入的最后一个 WAL 段文件准备好归档。

    pg_stop_backup会返回一行,包含三个值。其中第二个字段应写入备份根目录下名为backup_label的文件中。第三个字段除非为空,否则应写入名为tablespace_map的文件中。这些文件对备份能否正常工作至关重要,必须逐字节原样写入,不能做任何修改,这可能意味着需要以二进制模式打开文件。

  5. 一旦备份期间活跃的 WAL 段文件都已归档,备份就完成了。由pg_stop_backup第一个返回值标识的文件,是形成完整备份文件集所需的最后一个段。在主库上,如果启用了archive_modewait_for_archive参数为true,则pg_stop_backup在最后一个段归档前不会返回。在备库上,只有当archive_modealways时,pg_stop_backup才会等待。由于你已经配置好了archive_command,这些文件的归档会自动进行。多数情况下这会很快完成,但仍建议监控归档系统,确保没有延迟。如果归档进程因归档命令失败而落后,它会持续重试,直到归档成功并且备份完成。如果你希望对pg_stop_backup的执行设置时间限制,请设置合适的statement_timeout值,但要注意如果pg_stop_backup因此终止,备份可能无效。

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

25.3.3.2. 制作排他低级备份 #

Note

排他备份方法已弃用,应避免使用。在 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)。在此过程中既没有必要,也不希望停止数据库的正常运行。关于执行此备份时需要注意的事项,见Section 25.3.3.3

    如上所述,如果服务器在备份期间崩溃,可能必须手动删除 PGDATA 目录中的 backup_label 文件后才能重启。务必注意,在恢复备份时绝不能删除 backup_label 文件,否则会导致数据损坏。使用这种方法时,混淆何时应删除此文件是造成数据损坏的常见原因;务必确认只在现有主库上删除此文件,绝不能在创建备库或恢复备份时删除,即使所创建的备库随后会被提升为新主库也不例外。

  4. 再次以有权运行 pg_stop_backup 的用户身份(超级用户,或已被授予该函数 EXECUTE 权限的用户)连接到数据库,并执行命令:

    SELECT pg_stop_backup();
    

    该函数会终止备份模式,并自动切换到下一个 WAL 段。切换的目的是让备份期间写入的最后一个 WAL 段准备好归档。

  5. 备份期间处于活动状态的 WAL 段文件全部归档后,备份就完成了。pg_stop_backup 返回结果所标识的文件,是构成完整备份文件集所需的最后一个段。如果启用了 archive_modepg_stop_backup 会等到最后一个段归档后才返回。由于已经配置了 archive_command,这些文件会自动归档。大多数情况下,归档很快就能完成,但建议监控归档系统,确保没有延迟。如果归档进程因归档命令失败而落后,它会不断重试,直到归档成功、备份完成。

    使用排他备份模式时,必须确保 pg_stop_backup 在备份结束时成功完成。即使备份本身失败(例如磁盘空间不足),未调用 pg_stop_backup 也会使服务器无限期地处于备份模式,导致后续备份失败,并增加 backup_label 存在期间重启失败的风险。

25.3.3.3. 备份数据目录 #

某些文件系统备份工具在复制过程中,如果它们试图复制的文件发生变化,就会发出警告或错误。对活动数据库进行基础备份时,这种情况是正常的,并不表示出错。不过,你需要确保能够把这类提示与真正的错误区分开来。例如,某些版本的rsync会针对vanished source files返回单独的退出码,你可以编写一个驱动脚本,把这一退出码视为非错误情况。此外,某些版本的 GNU tar在文件被tar复制时如果发生截断,会返回与致命错误无法区分的错误码。幸运的是,GNU tar 1.16 及之后版本在备份过程中如果文件被更改会以 1 退出,而其他错误则以 2 退出。对于 GNU tar 1.23 及之后版本,可以使用警告选项--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_internal.init的文件,就可以把它从备份中省略。这些文件包含关系缓存数据,而它们在恢复时总会被重新构建。

备份标签文件包含你提供给pg_start_backup的标签字符串、执行pg_start_backup的时间以及起始 WAL 文件的名称。因此,在发生混淆时,可以查看备份文件内容,精确确定该备份文件来自哪一次备份会话。表空间映射文件包含目录pg_tblspc/中存在的符号链接名称,以及每个符号链接的完整路径。这些文件不仅仅是给你参考;它们的存在及其内容对于系统恢复过程的正确运行至关重要。

服务器停止时也可以进行备份。在这种情况下,你显然无法使用pg_start_backuppg_stop_backup,因此只能自己追踪各个备份的身份以及相关 WAL 文件最早需要追溯到哪里。通常最好还是遵循上面的持续归档过程。

25.3.4. 使用持续归档备份进行恢复 #

如果最坏的情况已经发生,需要从备份恢复,请按以下流程操作:

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

  2. 如果你有足够空间,请把整个集簇数据目录以及所有表空间复制到临时位置,以备后续需要。注意,这一预防措施要求系统有足够空闲空间来保存现有数据库的两份副本。如果空间不够,至少也应保存集簇pg_wal子目录中的内容,因为其中可能包含系统停机前尚未归档的 WAL 文件。

  3. 删除集簇数据目录下以及所有正在使用的表空间根目录下的现有文件和子目录。

  4. 如果你正在恢复完整备份,可以把数据库文件直接恢复到目标目录中。务必确保它们以正确的所有者(数据库系统用户,而不是root!)和正确的权限恢复。如果使用了表空间,还应验证pg_tblspc/中的符号链接是否已正确恢复。

  5. 删除pg_wal/中现有的所有文件;这些文件来自文件系统备份,因此很可能已经过时而不是最新的。如果你根本没有归档pg_wal/,那么就以正确权限重新创建它,并注意如果它原先是符号链接,就要重新把它设置成符号链接。

  6. 如果你手头还有第 2 步中保存下来的未归档 WAL 段文件,请把它们复制到pg_wal/中。(最好复制,而不是移动,这样一旦出问题需要重来时,你手里仍然保留着未修改的原始文件。)

  7. postgresql.conf中设置恢复配置参数(见Section 19.5.4),并在集簇数据目录中创建recovery.signal文件。在确认恢复成功之前,你也可能希望临时修改pg_hba.conf,以防止普通用户连接。

  8. 启动服务器。服务器将进入恢复模式,并开始读取它所需的已归档 WAL 文件。如果恢复因外部错误而中止,可以直接重新启动服务器,它会继续恢复。恢复过程完成后,服务器会删除recovery.signal(以防止以后意外再次进入恢复模式),然后开始正常的数据库操作。

  9. 检查数据库内容,确认你已经恢复到期望状态。如果不是,就回到第 1 步。如果一切正常,就把pg_hba.conf恢复为正常设置,让用户重新连接。

整个过程的关键,是设置恢复配置,说明希望如何恢复,以及恢复到什么位置。必须指定的设置是 restore_command,它告诉 PostgreSQL 如何获取已归档的 WAL 段。与 archive_command 一样,它是一个 shell 命令字符串,可以包含 %f,该标记会被替换为所需日志文件的名称;还可以包含 %p,该标记会被替换为日志文件要复制到的路径名。(路径名相对于当前工作目录,即集簇的数据目录。)使用 %% 可以在命令中嵌入实际的 % 字符。最简单的可用命令类似于:

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

这会复制先前归档的 WAL 段,来源目录为 /mnt/server/archivedir。当然,也可以使用复杂得多的命令,甚至可以使用要求操作人员挂载相应磁带的 shell 脚本。

重要的是,该命令在失败时必须返回非零退出状态。系统调用该命令来请求归档中不存在的文件;遇到这种情况时,它就应返回非零值。这不是一种错误情况。例外是,如果该命令被信号终止(用于数据库服务器关闭的SIGTERM除外),或者因 shell 错误(如命令未找到)而失败,那么恢复将中止,服务器也不会启动。

被请求的文件并不全都是 WAL 段文件;你还应预期会收到对带有.history后缀文件的请求。另外请注意,%p路径的基本文件名会与%f不同;不要指望它们可以互换使用。

在归档中找不到的 WAL 段会转而在pg_wal/中查找;这使得可以使用最近尚未归档的段。不过,凡是归档中可用的段,都会优先于pg_wal/中的文件使用。

通常,恢复会处理完所有可用的 WAL 段,从而把数据库恢复到当前时间点(或者在可用 WAL 段所允许的情况下尽可能接近当前时间点)。因此,一次正常恢复通常会以一条file not found消息结束,具体错误文本取决于你选择的restore_command。在恢复开始时,你也可能看到一条针对类似00000001.history文件的错误消息。这同样是正常的,在简单恢复场景中并不表示有问题;相关讨论见Section 25.3.5

如果你希望恢复到过去的某个时间点(例如恢复到那位初级 DBA 删掉你的主事务表之前),只需指定所需的停止点即可。这个停止点也称为恢复目标,可以通过日期/时间、命名恢复点或者某个特定事务 ID 完成时刻来指定。在目前的实现下,只有日期/时间和命名恢复点这两种方式真正比较实用,因为没有工具能够帮助你足够准确地识别应使用哪个事务 ID。

Note

停止点必须晚于基础备份的结束时间,也就是pg_stop_backup的结束时间。你不能用某次基础备份恢复到该备份仍在进行中的时间点。(若要恢复到这样的时间点,必须回到更早的一次基础备份,再从那里向前滚动。)

如果恢复过程中发现了损坏的 WAL 数据,恢复会在该点停止,服务器也不会启动。在这种情况下,可以从头重新执行恢复,并指定一个位于损坏点之前的恢复目标,使恢复能够正常完成。如果恢复因外部原因失败,例如系统崩溃或 WAL 归档变得不可访问,那么只需重新启动恢复,它几乎会从上次失败的位置继续。恢复重启的工作方式很像正常运行时的检查点:服务器会周期性地把自身状态强制写盘,然后更新pg_control文件,表明已处理过的 WAL 数据无需再次扫描。

25.3.5. 时间线 #

能够将数据库恢复到过去某个时间点,也会带来一些类似科幻故事中时间旅行和平行宇宙的复杂情况。例如,假设在数据库原来的历史中,你在周二下午 5:15 删除了一张重要的表,直到周三中午才发现错误。你从容地取出备份,将数据库恢复到周二下午 5:14,然后重新投入运行。在数据库宇宙的这段历史中,你从未删除过那张表。但假设你后来发现这样做不太合适,希望回到原来历史中的周三上午某个时刻。如果数据库恢复运行后覆盖了通往该时刻所需的某些 WAL 段文件,就无法回去了。因此,为了避免这种情况,需要区分时间点恢复后产生的一系列 WAL 记录与数据库原来历史中产生的记录。

为解决这一问题,PostgreSQL 引入了时间线的概念。每当归档恢复完成,系统都会创建一条新时间线,用于标识此次恢复之后产生的一系列 WAL 记录。时间线 ID 是 WAL 段文件名的一部分,因此新时间线不会覆盖旧时间线产生的 WAL 数据。实际上,可以归档许多不同的时间线。这个功能看似没什么用,却常常能救急。比如,你不能确定应该恢复到哪个时间点,需要反复尝试时间点恢复,直到找到脱离旧历史的最佳分支点。没有时间线,这个过程很快就会乱得无法管理。有了时间线,就可以恢复到任何先前的状态,包括早先已放弃的时间线分支中的状态。

每次创建新时间线时,PostgreSQL 都会创建一个时间线历史文件,记录它从哪条时间线、在何时分支出来。从包含多条时间线的归档中恢复时,系统需要这些历史文件来选择正确的 WAL 段文件。因此,历史文件与 WAL 段文件一样,也会归档到 WAL 归档区。历史文件只是很小的文本文件,长期保留成本很低,也很合适(不像体积很大的段文件)。如果愿意,可以在历史文件中添加注释,记录这条时间线是如何创建的,以及为什么创建它。当反复试验产生了许多交错的时间线时,这些注释会格外有用。

默认的恢复行为是恢复到归档中找到的最新时间线。如果希望恢复到制作基础备份时的当前时间线,或者某条特定的子时间线(即希望返回到一次恢复尝试之后产生的某个状态),需要在 recovery_target_timeline 中指定 current 或目标时间线 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 存在时,这条命令会执行归档;否则会静默地返回零退出状态(允许 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 文件成批处理,使其每三个小时传输一次,而不是每生成一个就传一次

  • 与其他备份和恢复软件对接

  • 与监控软件对接以报告错误

Tip

使用archive_command脚本时,最好启用logging_collector。脚本写到stderr的任何消息都会出现在数据库服务器日志中,这样一来,当复杂配置失败时就更容易诊断。

25.3.7. 注意事项 #

撰写本文时,连续归档技术还有几个限制。这些限制可能会在未来版本中消除:

  • 如果在进行基础备份时执行了CREATE DATABASE命令,而该CREATE DATABASE所复制的模板数据库又在基础备份尚未结束时被修改,那么恢复时可能会把这些修改也传播到新建数据库中。这当然并不理想。为避免这种风险,最好在进行基础备份时不要修改任何模板数据库。

  • CREATE TABLESPACE命令会以字面绝对路径写入 WAL,因此重放时会按相同的绝对路径创建表空间。如果 WAL 在另一台机器上重放,这可能并不理想。即使 WAL 在同一台机器上、但重放到新的数据目录中,也可能有危险:重放仍会覆盖原表空间的内容。为避免此类潜在陷阱,最佳做法是在创建或删除表空间后重新执行一次基础备份。

还应注意,默认的WAL格式相当臃肿,因为它包含许多磁盘页面快照。这些页面快照是为支持崩溃恢复而设计的,因为我们可能需要修复部分写入的磁盘页。根据你的系统硬件和软件情况,部分写入的风险可能小到可以忽略;在这种情况下,可以通过full_page_writes参数关闭页面快照,从而显著减少已归档 WAL 文件的总量。(在这样做之前,请先阅读Chapter 29中的说明和警告。)关闭页面快照并不妨碍把 WAL 用于 PITR 操作。未来一个可能的开发方向,是在full_page_writes开启的情况下,通过去除不必要的页面副本来压缩归档 WAL 数据。在此之前,管理员可以考虑尽可能增大检查点间隔相关参数,以减少 WAL 中包含的页面快照数量。