受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

26.4. 热备 #

热备是用来描述服务器在归档恢复或备库模式下仍然可以接受连接并运行只读查询的能力的术语。这对于复制用途以及把备份以极高精度恢复到目标状态都很有用。术语热备还指服务器能够在用户持续运行查询和/或保持连接不断开的同时,从恢复状态切换到正常运行状态的能力。

在热备模式下运行查询与正常查询操作类似,不过正如下文所述,在使用和管理上存在一些差异。

26.4.1. 用户概览 #

当备库上的hot_standby参数被设置为真时,一旦恢复把系统带到一致状态,它就会开始接受连接。所有这类连接都严格是只读的,甚至不能写入临时表。

备库上的数据需要一些时间才能从主库到达,因此主库和备库之间会有可测量的延迟。因此,在主库和备库上几乎同时运行同一查询,可能会返回不同的结果。我们说备库上的数据与主库是最终一致的。一旦某个事务的提交记录在备库上被重放,该事务所做的修改就会对备库上之后取得的所有新快照可见。快照可以在每个查询开始时取得,也可以在每个事务开始时取得,这取决于当前的事务隔离级别。详见Section 13.2

在热备期间启动的事务可以发出下列命令:

  • 查询访问:SELECTCOPY TO

  • 游标命令:DECLAREFETCHCLOSE

  • 设置:SHOWSETRESET

  • 事务管理命令:

    • BEGINENDABORTSTART TRANSACTION

    • SAVEPOINTRELEASEROLLBACK TO SAVEPOINT

    • EXCEPTION块或其他内部子事务

  • LOCK TABLE,不过只限于显式指定下列模式之一时: ACCESS SHAREROW SHAREROW EXCLUSIVE

  • 计划和资源:PREPAREEXECUTEDEALLOCATEDISCARD

  • 插件和扩展:LOAD

  • UNLISTEN

在热备期间启动的事务永远不会被分配事务 ID,也不能写入系统预写式日志。因此,下列动作都会产生错误消息:

  • 数据操纵语言(DML):INSERTUPDATEDELETEMERGECOPY FROMTRUNCATE。请注意,恢复期间不存在任何允许执行触发器的动作。这个限制甚至适用于临时表,因为不分配事务 ID 就无法读取或写入表行,而这在热备环境中目前是不可能的。

  • 数据定义语言(DDL):CREATEDROPALTERCOMMENT。这个限制甚至适用于临时表,因为执行这些操作需要更新系统目录表。

  • SELECT ... FOR SHARE | UPDATE,因为不更新底层数据文件就无法获取行锁。

  • 作用在SELECT语句上、会生成 DML 命令的规则。

  • LOCK,如果它显式请求高于 ROW EXCLUSIVE MODE 的模式。

  • 简写默认形式的LOCK,因为它请求的是 ACCESS EXCLUSIVE MODE

  • 显式设置为非只读状态的事务管理命令:

    • BEGIN READ WRITE, START TRANSACTION READ WRITE

    • SET TRANSACTION READ WRITE, SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE

    • SET transaction_read_only = off

  • 两阶段提交命令:PREPARE TRANSACTIONCOMMIT PREPAREDROLLBACK PREPARED, 因为即使是只读事务,在 prepare 阶段(两阶段提交的第一阶段)也需要写入 WAL。

  • 序列更新:nextval()setval()

  • LISTENNOTIFY

在正常运行中,只读事务允许使用LISTENNOTIFY,因此热备会话受到的限制要比普通只读会话稍微更严格一些。将来的版本中,这些限制中的某些可能会被放宽。

在热备期间,参数transaction_read_only始终为真,并且不能更改。不过,只要不尝试修改数据库,热备期间的连接行为与其他数据库连接大致相同。如果发生故障转移或切换,数据库将切换到正常处理模式。服务器切换模式时,会话仍会保持连接。一旦热备结束,就可以发起读写事务(即使该会话是在热备期间开始的)。

用户可以通过执行SHOW in_hot_standby来判断自己的会话当前是否处于热备状态。(在服务器 14 之前的版本中,不存在in_hot_standby参数;对于更早版本的服务器,一个可行的替代方法是执行SHOW transaction_read_only。)此外,还有一组函数(Table 9.95)允许用户访问有关备库的信息。借助这些函数,你可以编写感知数据库当前状态的程序,用于监控恢复进度,或者编写把数据库恢复到特定状态的复杂程序。

26.4.2. 处理查询冲突 #

主库和备库在许多方面都是松耦合的。主库上的动作会对备库产生影响,因此它们之间可能出现负面交互或冲突。最容易理解的冲突是性能:如果主库上正在执行一次大规模数据装载,那么备库上也会产生类似的 WAL 记录流,因此备库查询可能会争用系统资源,例如 I/O。

热备还可能发生其他类型的冲突。这些冲突属于硬冲突,因为查询可能需要被取消,并且在某些情况下还需要断开会话来解决冲突。系统为用户提供了若干种处理这些冲突的方法。冲突场景包括:

  • 主库上获取的 Access Exclusive 锁,包括显式的LOCK命令和各种DDL操作,会与备库查询中的表访问发生冲突。

  • 主库上删除表空间,会与备库查询把该表空间用于临时工作文件发生冲突。

  • 主库上删除数据库,会与备库上连接到该数据库的会话发生冲突。

  • 来自 WAL 的清理记录在应用时,会与那些快照仍然能够看到将被删除行的备库事务发生冲突。

  • 来自 WAL 的清理记录在应用时,会与备库上访问目标页面的查询发生冲突,而不管待删除的数据是否可见。

在主库上,这些情况只会导致等待;用户可以选择取消冲突动作中的任意一方。但是在备库上没有这种选择:已经被写入 WAL 的动作早已在主库上发生,因此备库在应用它时不能失败。此外,让 WAL 应用无限期等待通常是很不可取的,因为备库的状态会越来越落后于主库。因此,系统提供了一种机制,用来强制取消那些与即将应用的 WAL 记录发生冲突的备库查询。

一个典型例子是,主库上的管理员在某个表上执行DROP TABLE,而备库上正有查询访问该表。显然,如果DROP TABLE在备库上被应用,该备库查询就无法继续。如果这种情况发生在主库上,DROP TABLE会等待直到其他查询结束。但在主库上执行DROP TABLE时,主库并不知道备库上正在运行哪些查询,因此它不会等待这些备库查询。于是,WAL 变更记录会在备库查询仍在运行时到达备库,从而引发冲突。备库要么延迟应用这条 WAL 记录(以及之后的所有记录),要么取消冲突查询,以便应用DROP TABLE

当冲突查询很短时,通常最好通过稍微延迟 WAL 应用来让它完成;但 WAL 应用长时间延迟通常并不可取。因此,取消机制提供了max_standby_archive_delaymax_standby_streaming_delay这两个参数,用于定义 WAL 应用允许的最大延迟。一旦应用新收到的 WAL 数据所花费的时间超过相应延迟设置,冲突查询就会被取消。之所以有两个参数,是为了分别针对从归档读取 WAL 数据的场景(即从基础备份进行初始恢复,或者追赶一台已经远远落后的备库)以及通过流复制读取 WAL 数据的场景指定不同的延迟值。

如果一台备库主要用于高可用性,那么最好把这些延迟参数设置得较短,这样服务器就不会因为备库查询导致的延迟而远远落后于主库。但是,如果该备库的用途是执行长时间运行的查询,那么较高甚至无限的延迟值可能更合适。不过要记住,如果某个长时间运行的查询延迟了 WAL 记录的应用,它也可能使备库上的其他会话看不到主库上的最新变更。

一旦max_standby_archive_delaymax_standby_streaming_delay指定的延迟被超越,冲突查询将被取消。这通常仅导致一个取消错误,尽管在重放一个DROP DATABASE的情况下整个冲突会话都将被中断。另外,如果冲突发生在一个被空闲事务持有的锁上,该冲突会话会被中断(这种行为可能在未来被改变)。

被取消的查询可能会立即被重试(当然是在开始一个新的事务后)。因为查询取消依赖于 WAL 记录被重放的本质,如果一个被取消的查询被再次执行,它可能会很好地成功完成。

记住延迟参数是从 WAL 数据被备库收到后流逝的时间。因此,留给备库上任何一个查询的宽限期从不会超过延迟参数,并且如果备库已经由于等待之前的查询完成而落后或者因为过重的更新负载而无法跟上主库,宽限期可能会更少。

备库查询与 WAL 重放发生冲突的最常见原因是过早清理。正常情况下,当没有事务需要看到旧行版本时,PostgreSQL允许清理这些旧行版本,以保证按照 MVCC 规则得到正确的数据可见性。不过,这条规则只能应用于在主库上执行的事务。因此,主库上的清理有可能移除某个备库事务仍然可见的行版本。

行版本清理并不是与备库查询发生冲突的唯一潜在原因。所有仅索引扫描(包括在备库上运行的那些)都必须使用一个MVCC快照,它与可见性映射一致。因此,每当VACUUM在可见性映射中把某个页面标记为全可见,而该页面中包含一行或多行对所有备库查询都不可见的数据时,就必须产生冲突。所以,即使针对一个没有更新或删除任何需要清理行的表执行VACUUM,也可能导致冲突。

用户应当清楚,主库上经常并且大量更新的表,会很快导致备库上的长时间运行查询被取消。在这种情况下,把max_standby_archive_delaymax_standby_streaming_delay设置为有限值,可以视作类似于设置statement_timeout

如果备库查询被取消的次数多得令人无法接受,也存在补救办法。第一种选择是设置hot_standby_feedback参数,它会阻止VACUUM移除最近死亡的行,因此不会发生清理冲突。如果这样做,你应当注意这会延迟主库上死行的清理,从而可能导致不希望出现的表膨胀。不过,清理情况不会比直接在主库上运行这些备库查询更糟,而且你仍然可以获得把执行卸载到备库上的好处。如果备库经常连接又断开,你可能还需要进行一些调整,以应对无法提供hot_standby_feedback反馈的那段时间。例如,可以考虑增大max_standby_archive_delay,使查询在断开期间不会因为 WAL 归档文件中的冲突而被迅速取消。你也应考虑增大max_standby_streaming_delay,以避免重新连接后由于新到达的流式 WAL 条目而被快速取消。

查询取消的数量及其原因可以通过备库上的pg_stat_database_conflicts系统视图查看。pg_stat_database系统视图也包含汇总信息。

当 WAL 重放因冲突而等待的时间超过deadlock_timeout时,用户可以控制是否生成日志消息。这由log_recovery_conflict_waits参数控制。

26.4.3. 管理员概览 #

如果hot_standby被设置为on(默认值),并且该设置位于postgresql.conf中,同时存在standby.signal文件,那么服务器就会运行在热备模式下。不过,允许热备连接可能还需要一段时间,因为服务器在完成足够多的恢复、能够提供一个可供查询运行的一致状态之前,不会接受连接。在此期间,尝试连接的客户端会收到错误消息而被拒绝。要确认服务器已经启动,可以让应用循环尝试连接,或者在服务器日志中查找以下消息:

LOG:  entering standby mode

... then some time later ...

LOG:  consistent recovery state reached
LOG:  database system is ready to accept read-only connections

一致性信息在主库上每个检查点时记录一次。在主库的wal_level未设置为replicalogical期间所写入的 WAL 被读取时,无法启用热备。如果同时存在以下两种情况,达到一致状态也可能会被推迟:

  • 某个写事务包含超过 64 个子事务

  • 非常长生命周期的写事务

如果你运行的是基于文件的日志传送(温备),那么可能需要等到下一个 WAL 文件到达,这个等待时间可能长达主库上archive_timeout的设置值。

某些参数的设置决定了用于跟踪事务 ID、锁以及预备事务的共享内存大小。为了确保备库在恢复过程中不会耗尽共享内存,这些共享内存结构在备库上绝不能比主库上更小。例如,如果主库曾使用过预备事务,而备库没有为跟踪预备事务分配任何共享内存,那么恢复就无法继续,直到修改备库的配置。受影响的参数有:

  • max_connections

  • max_prepared_transactions

  • max_locks_per_transaction

  • max_wal_senders

  • max_worker_processes

避免这类问题最简单的方法,就是让备库上的这些参数值等于或大于主库上的值。因此,如果你想增大这些值,应先在所有备库上增大,再把变更应用到主库。相反,如果想减小这些值,则应先在主库上减小,再把变更应用到所有备库。请记住,当一台备库被提升后,它会成为其后续备库所需参数设置的新参考。因此,为避免在切换或故障转移期间出现问题,建议在所有备库上保持这些设置相同。

WAL 会跟踪主库上这些参数的变化。如果热备在处理 WAL 时发现主库上的当前值高于自身设置值,它将记录一条警告并暂停恢复。例如:

WARNING:  hot standby is not possible because of insufficient parameter settings
DETAIL:  max_connections = 80 is a lower setting than on the primary server, where its value was 100.
LOG:  recovery has paused
DETAIL:  If recovery is unpaused, the server will shut down.
HINT:  You can then restart the server after making the necessary configuration changes.

到了这一步,必须先更新备库上的设置并重启实例,恢复才能继续。如果该备库不是热备,那么遇到这种不兼容的参数变化时,它会直接关闭而不会暂停,因为继续保持其运行没有意义。

管理员为max_standby_archive_delaymax_standby_streaming_delay选择合适的设置非常重要。最佳选择取决于业务优先级。例如,如果服务器的主要任务是充当高可用服务器,那么你会希望延迟设置较低,甚至可能设为零,尽管这是一个非常激进的设置。如果备库承担的是决策支持查询的附加服务器角色,那么把最大延迟设置为数小时甚至 -1(意味着永远等待查询完成)也可能是可以接受的。

主库上写出的事务状态 "hint bits" 不会被 WAL 记录,因此备库上的数据很可能会再次写出这些提示位。这样一来,即使所有用户都是只读的,备库仍然会执行磁盘写操作;不过数据值本身并不会发生变化。用户仍然会写出大型排序临时文件,并重新生成 relcache 信息文件,因此在热备模式下,数据库没有任何部分是真正只读的。还要注意,使用dblink模块写入远程数据库,以及借助 PL 函数执行其他数据库外部操作,依然是可能的,即使该事务在本地是只读的。

在恢复模式下,不接受下列类型的管理命令:

  • 数据定义语言:例如 CREATE INDEX

  • 权限和所有权:GRANTREVOKEREASSIGN

  • 维护命令:ANALYZEVACUUMCLUSTERREINDEX

再次注意,这些命令中的某些在主库上的“只读”事务中实际上是被允许的。

因此,你不能创建只存在于备库上的额外索引,也不能创建只存在于备库上的统计信息。如果需要这些管理命令,应在主库上执行,最终这些变更会传播到备库。

pg_cancel_backend()pg_terminate_backend()可以作用于用户后端,但不能作用于执行恢复操作的启动进程。pg_stat_activity不会把正在恢复的事务显示为活动状态。因此,恢复期间pg_prepared_xacts始终为空。如果你希望解决处于不确定状态的预备事务,请查看主库上的pg_prepared_xacts,并在那里发出命令解决这些事务,或者在恢复结束后再解决它们。

与正常情况一样,pg_locks会显示后端持有的锁。pg_locks还会显示一个由启动进程管理的虚拟事务,它拥有所有正在被恢复重放的事务所持有的AccessExclusiveLocks。请注意,启动进程不会为了修改数据库而获取锁,因此除了AccessExclusiveLocks之外,其他锁不会在启动进程的pg_locks中显示;它们只是被假定存在。

Nagioscheck_pgsql插件可以工作,因为它检查的简单信息是存在的。check_postgres监控脚本也可以工作,尽管其中某些被报告的值可能会给出不同或令人困惑的结果。例如,最近一次清理时间不会被维护,因为备库上不会发生清理。不过,主库上执行的清理仍然会把其变更发送到备库。

恢复期间,WAL 文件控制命令不可用,例如pg_backup_startpg_switch_wal等。

可动态载入的模块可以工作,包括pg_stat_statements

咨询锁在恢复期间可以正常工作,包括死锁检测。注意,咨询锁从不会被 WAL 记录,因此主库或备库上的咨询锁都不可能与 WAL 重放发生冲突。同样,也不可能在主库上获取一个咨询锁,却在备库上触发一个类似的咨询锁。咨询锁只与获取它们的那台服务器相关。

基于触发器的复制系统,如SlonyLondisteBucardo,根本无法在备库上运行;不过只要它们的变更不会被发送到备库并在那里应用,它们在主库上就可以正常工作。WAL 重放并不是基于触发器的,因此你不能把备库作为任何需要额外数据库写操作或依赖触发器的系统的中继节点。

不能分配新的 OID,不过某些UUID生成器仍可工作,只要它们不依赖于向数据库写入新的状态。

目前,在只读事务期间不允许创建临时表,因此某些现有脚本在这种情况下将无法正常运行。这个限制可能会在未来版本中放宽。这既涉及 SQL 标准兼容性问题,也涉及技术问题。

只有在表空间为空时DROP TABLESPACE才能成功。某些备库用户可能正在通过他们的temp_tablespaces参数使用该表空间。如果该表空间中存在临时文件,所有活动查询都将被取消,以确保临时文件被移除,这样该表空间才能被移除并且 WAL 重放可以继续。

在主库上执行DROP DATABASEALTER DATABASE ... SET TABLESPACE会生成一条 WAL 记录,从而强制断开备库上所有连接到该数据库的用户。这个动作会立即发生,而不受max_standby_streaming_delay设置的影响。注意,ALTER DATABASE ... RENAME不会断开用户,这在大多数情况下不会被注意到,但如果程序依赖某种基于数据库名的机制,在某些情况下可能会导致混乱。

在普通(非恢复)模式下,如果你对一个具有登录能力的角色执行DROP USERDROP ROLE,而该用户仍然处于连接状态,那么已连接用户不会发生任何变化 — 他们会继续保持连接,不过之后不能重新连接。这种行为在恢复期间同样适用,因此在主库上执行一次DROP USER并不会断开备库上的该用户连接。

累积统计系统在恢复期间是活动的。所有扫描、读取、块访问、索引使用等,都会在备库上照常记录。不过,WAL 重放不会增加关系和数据库级别的特定计数器。也就是说,重放不会增加pg_stat_all_tables中的列(例如n_tup_ins),启动进程执行的读写也不会被记录到pg_statio_视图中,相关的pg_stat_database列也不会增加。

恢复期间自动清理不会运行。它会在恢复结束时正常启动。

检查点进程和后台写入进程在恢复期间是活动的。检查点进程会执行重启点(类似于主库上的检查点),后台写入进程会执行正常的块清理活动。这可能包括更新存储在备库上的提示位信息。恢复期间接受CHECKPOINT命令,不过它执行的是重启点,而不是新的检查点。

26.4.4. 热备参数参考 #

多个参数已经在Section 26.4.2Section 26.4.3中提到过。

在主库上,可以使用wal_level参数。max_standby_archive_delaymax_standby_streaming_delay如果设置在主库上,则不会生效。

在备库上,可以使用hot_standbymax_standby_archive_delaymax_standby_streaming_delay这几个参数。

26.4.5. 注意事项 #

热备有若干限制。 这些限制在未来的版本中可以、也很可能会被修复:

  • 在能够取得快照之前,必须完整了解正在运行的事务。使用大量子事务(目前超过 64 个)的事务,会把只读连接的启动推迟到持续时间最长的写事务完成之后。如果发生这种情况,服务器日志中会发送解释性消息。

  • 备库查询的有效起始点是在主库的每个检查点上生成的。如果主库处于关闭状态时备库被关闭,那么在主库再次启动并在 WAL 日志中生成更多起始点之前,备库可能无法重新进入热备状态。不过在最常见的场景中,这通常不是问题。一般来说,如果主库关闭且不再可用,很可能是出现了严重故障,此时无论如何都需要把备库提升为新的主库。而在主库被有意关闭的情况下,协调好备库顺利成为新主库,本来也就是标准流程。

  • 在恢复结束时,由预备事务持有的AccessExclusiveLocks将需要正常数量两倍的锁表项。如果你计划运行大量并发的预备事务,而这些事务通常会持有AccessExclusiveLocks,或者你计划运行一个会持有许多AccessExclusiveLocks的大事务,那么建议选择更大的max_locks_per_transaction值,可能要达到主库上该参数值的两倍。如果你的max_prepared_transactions设置为 0,则完全不需要考虑这一点。

  • 热备中尚不支持可串行化事务隔离级别。(详见Section 13.2.3Section 13.4.1。)在热备模式下尝试把事务设置为可串行化隔离级别会产生错误。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。