热备是用来描述服务器在归档恢复或备库模式下仍然可以接受连接并运行只读查询的能力的术语。这对于复制用途以及把备份以极高精度恢复到目标状态都很有用。术语热备还指服务器能够在用户持续运行查询和/或保持连接不断开的同时,从恢复状态切换到正常运行状态的能力。
在热备模式下运行查询与正常查询操作类似,不过正如下文所述,在使用和管理上存在一些差异。
当备库上的hot_standby参数被设置为真时,一旦恢复把系统带到一致状态并为热备做好准备,它就会开始接受连接。所有这类连接都严格是只读的,甚至不能写入临时表。
备库上的数据需要一些时间才能从主库到达,因此主库和备库之间会有可测量的延迟。因此,在主库和备库上几乎同时运行同一查询,可能会返回不同的结果。我们说备库上的数据与主库是最终一致的。一旦某个事务的提交记录在备库上被重放,该事务所做的修改就会对备库上之后取得的所有新快照可见。快照可以在每个查询开始时取得,也可以在每个事务开始时取得,这取决于当前的事务隔离级别。详见Section 13.2。
热备期间启动的事务可以执行以下命令:
查询访问 - SELECT、COPY TO
游标命令 - DECLARE、FETCH、CLOSE
参数 - SHOW、SET、RESET
事务管理命令
BEGIN、END、ABORT、START TRANSACTION
SAVEPOINT、RELEASE、ROLLBACK TO SAVEPOINT
EXCEPTION块或其他内部子事务
LOCK TABLE,不过只限于显式指定下列模式之一时: ACCESS SHARE、ROW SHARE 或 ROW EXCLUSIVE
计划和资源 - PREPARE、EXECUTE、DEALLOCATE、DISCARD
插件和扩展 - LOAD
UNLISTEN
热备期间启动的事务不会被分配事务 ID,也不能写入系统的预写式日志。因此,以下操作会产生错误消息:
数据操纵语言(DML)- INSERT、UPDATE、DELETE、COPY FROM、TRUNCATE。注意,恢复期间不允许任何会导致触发器执行的操作。这一限制也适用于临时表,因为不分配事务 ID 就无法读写表中的行,而目前热备环境中无法分配事务 ID。
数据定义语言(DDL)- CREATE、DROP、ALTER、COMMENT。这一限制也适用于临时表,因为执行这些操作需要更新系统目录表。
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 TRANSACTION、COMMIT PREPARED、ROLLBACK PREPARED,因为即使只读事务也需要在准备阶段(两阶段提交的第一阶段)写入 WAL。
序列更新 - nextval()、setval()
LISTEN、NOTIFY
在正常运行中,“只读”事务允许使用LISTEN和NOTIFY,因此热备会话受到的限制要比普通只读会话稍微更严格一些。将来的版本中,这些限制中的某些可能会被放宽。
在热备期间,参数transaction_read_only始终为真,并且不能更改。不过,只要不尝试修改数据库,热备期间的连接行为与其他数据库连接大致相同。如果发生故障切换或计划内切换,数据库将切换到正常处理模式。服务器切换模式时,会话仍会保持连接。一旦热备结束,就可以发起读写事务(即使该会话是在热备期间开始的)。
用户可以执行 SHOW transaction_read_only,判断自己的会话是否只读。此外,还有一组函数(Table 9.85)可用于获取备库信息。借助它们,可以编写感知数据库当前状态的程序,用来监控恢复进度,或编写将数据库恢复到特定状态的复杂程序。
主库和备库在许多方面都是松耦合的。主库上的动作会对备库产生影响,因此它们之间可能出现负面交互或冲突。最容易理解的冲突是性能:如果主库上正在执行一次大规模数据装载,那么备库上也会产生类似的 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_delay和max_standby_streaming_delay这两个参数,用于定义 WAL 应用允许的最大延迟。一旦应用新收到的 WAL 数据所花费的时间超过相应延迟设置,冲突查询就会被取消。之所以有两个参数,是为了分别针对从归档读取 WAL 数据的场景(即从基础备份进行初始恢复,或者“追赶”一台已经远远落后的备库)以及通过流复制读取 WAL 数据的场景指定不同的延迟值。
如果一台备库主要用于高可用性,那么最好把这些延迟参数设置得较短,这样服务器就不会因为备库查询导致的延迟而远远落后于主库。但是,如果该备库的用途是执行长时间运行的查询,那么较高甚至无限的延迟值可能更合适。不过要记住,如果某个长时间运行的查询延迟了 WAL 记录的应用,它也可能使备库上的其他会话看不到主库上的最新变更。
一旦max_standby_archive_delay或max_standby_streaming_delay指定的延迟被超越,冲突查询将被取消。这通常仅导致一个取消错误,尽管在重放一个DROP DATABASE的情况下整个冲突会话都将被中断。另外,如果冲突发生在一个被空闲事务持有的锁上,该冲突会话会被中断(这种行为可能在未来被改变)。
被取消的查询可能会立即被重试(当然是在开始一个新的事务后)。因为查询取消依赖于 WAL 记录被重放的本质,如果一个被取消的查询被再次执行,它可能会很好地成功完成。
记住延迟参数是从 WAL 数据被备库收到后流逝的时间。因此,留给备库上任何一个查询的宽限期从不会超过延迟参数,并且如果备库已经由于等待之前的查询完成而落后或者因为过重的更新负载而无法跟上主库,宽限期可能会更少。
备库查询与 WAL 重放发生冲突的最常见原因是“过早清理”。正常情况下,当没有事务需要看到旧行版本时,PostgreSQL允许清理这些旧行版本,以保证按照 MVCC 规则得到正确的数据可见性。不过,这条规则只能应用于在主库上执行的事务。因此,主库上的清理有可能移除某个备库事务仍然可见的行版本。
有经验的用户应注意,行版本清理和行版本冻结都有可能与备库查询冲突。手动运行 VACUUM FREEZE 很可能导致冲突,即使表中没有更新过或删除过的行也是如此。
用户应当清楚,主库上经常并且大量更新的表,会很快导致备库上的长时间运行查询被取消。在这种情况下,把max_standby_archive_delay或max_standby_streaming_delay设置为有限值,可以视作类似于设置statement_timeout。
如果备库查询被取消的次数多得令人无法接受,也存在补救办法。第一种选择是设置hot_standby_feedback参数,它会阻止VACUUM移除最近死亡的行,因此不会发生清理冲突。如果这样做,你应当注意这会延迟主库上死行的清理,从而可能导致不希望出现的表膨胀。不过,清理情况不会比直接在主库上运行这些备库查询更糟,而且你仍然可以获得把执行卸载到备库上的好处。如果备库经常连接又断开,你可能还需要进行一些调整,以应对无法提供hot_standby_feedback反馈的那段时间。例如,可以考虑增大max_standby_archive_delay,使查询在断开期间不会因为 WAL 归档文件中的冲突而被迅速取消。你也应考虑增大max_standby_streaming_delay,以避免重新连接后由于新到达的流式 WAL 条目而被快速取消。
另一种选择是增大主库上的 vacuum_defer_cleanup_age,使死行不会像通常那样很快被清理。这样,无需将 max_standby_streaming_delay 设得很大,就能让备库查询在被取消前有更多执行时间。不过,这种方法难以保证具体的执行时间窗口,因为 vacuum_defer_cleanup_age 是按主库上执行的事务数来衡量的。
查询取消的数量及其原因可以通过备库上的pg_stat_database_conflicts系统视图查看。pg_stat_database系统视图也包含汇总信息。
如果 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 未设为 replica 或 logical,则读取这一期间写入的 WAL 时不能启用热备。如果同时满足以下两个条件,达到一致状态也可能延迟:
某个写事务包含超过 64 个子事务
非常长生命周期的写事务
如果运行基于文件的日志传送(“温备”),可能需要等到下一个 WAL 文件到达,最长等待时间可达到主库上的 archive_timeout 设置值。
如果在主库上更改了某些参数,就需要重新配置备库上的相应设置。对于这些参数,备库上的值必须大于或等于主库上的值。因此,要增大这些值,应先修改所有备库,再修改主库。反之,要减小这些值,应先修改主库,再修改所有备库。如果这些参数设置得不够大,备库会拒绝启动。此时可以提高参数值,然后重启服务器,再次开始恢复。这些参数是:
max_connections
max_prepared_transactions
max_locks_per_transaction
max_wal_senders
max_worker_processes
管理员为max_standby_archive_delay和max_standby_streaming_delay选择合适的设置非常重要。最佳选择取决于业务优先级。例如,如果服务器的主要任务是充当高可用服务器,那么你会希望延迟设置较低,甚至可能设为零,尽管这是一个非常激进的设置。如果备库承担的是决策支持查询的附加服务器角色,那么把最大延迟设置为数小时甚至 -1(意味着永远等待查询完成)也可能是可以接受的。
主库上写出的事务状态 "hint bits" 不会被 WAL 记录,因此备库上的数据很可能会再次写出这些提示位。这样一来,即使所有用户都是只读的,备库仍然会执行磁盘写操作;不过数据值本身并不会发生变化。用户仍然会写出大型排序临时文件,并重新生成 relcache 信息文件,因此在热备模式下,数据库没有任何部分是真正只读的。还要注意,使用dblink模块写入远程数据库,以及借助 PL 函数执行其他数据库外部操作,依然是可能的,即使该事务在本地是只读的。
恢复模式下不接受以下类型的管理命令:
数据定义语言(DDL)- 例如 CREATE INDEX
权限和所有权 - GRANT、REVOKE、REASSIGN
维护命令 - ANALYZE、VACUUM、CLUSTER、REINDEX
再次注意,这些命令中的某些在主库上的“只读”事务中实际上是被允许的。
因此,你不能创建只存在于备库上的额外索引,也不能创建只存在于备库上的统计信息。如果需要这些管理命令,应在主库上执行,最终这些变更会传播到备库。
pg_cancel_backend()和pg_terminate_backend()可以作用于用户后端,但不能作用于执行恢复操作的启动进程。pg_stat_activity不会把正在恢复的事务显示为活动状态。因此,恢复期间pg_prepared_xacts始终为空。如果你希望解决处于不确定状态的预备事务,请查看主库上的pg_prepared_xacts,并在那里发出命令解决这些事务,或者在恢复结束后再解决它们。
与正常情况一样,pg_locks会显示后端持有的锁。pg_locks还会显示一个由启动进程管理的虚拟事务,它拥有所有正在被恢复重放的事务所持有的AccessExclusiveLocks。请注意,启动进程不会为了修改数据库而获取锁,因此除了AccessExclusiveLocks之外,其他锁不会在启动进程的pg_locks中显示;它们只是被假定存在。
Nagios的check_pgsql插件可以工作,因为它检查的简单信息是存在的。check_postgres监控脚本也可以工作,尽管其中某些被报告的值可能会给出不同或令人困惑的结果。例如,最近一次清理时间不会被维护,因为备库上不会发生清理。不过,主库上执行的清理仍然会把其变更发送到备库。
恢复期间,WAL 文件控制命令不可用,例如pg_start_backup、pg_switch_wal等。
可动态载入的模块可以工作,包括pg_stat_statements。
咨询锁在恢复期间可以正常工作,包括死锁检测。注意,咨询锁从不会被 WAL 记录,因此主库或备库上的咨询锁都不可能与 WAL 重放发生冲突。同样,也不可能在主库上获取一个咨询锁,却在备库上触发一个类似的咨询锁。咨询锁只与获取它们的那台服务器相关。
基于触发器的复制系统,如Slony、Londiste和Bucardo,根本无法在备库上运行;不过只要它们的变更不会被发送到备库并在那里应用,它们在主库上就可以正常工作。WAL 重放并不是基于触发器的,因此你不能把备库作为任何需要额外数据库写操作或依赖触发器的系统的中继节点。
不能分配新的 OID,不过某些UUID生成器仍可工作,只要它们不依赖于向数据库写入新的状态。
目前,不允许在只读事务中创建临时表,因此现有脚本在某些情况下无法正确运行。未来版本可能会放宽这一限制。这既涉及 SQL 标准兼容性,也涉及技术问题。
只有在表空间为空时DROP TABLESPACE才能成功。某些备库用户可能正在通过他们的temp_tablespaces参数使用该表空间。如果该表空间中存在临时文件,所有活动查询都将被取消,以确保临时文件被移除,这样该表空间才能被移除并且 WAL 重放可以继续。
在主库上执行DROP DATABASE或ALTER DATABASE ... SET TABLESPACE会生成一条 WAL 记录,从而强制断开备库上所有连接到该数据库的用户。这个动作会立即发生,而不受max_standby_streaming_delay设置的影响。注意,ALTER DATABASE ... RENAME不会断开用户,这在大多数情况下不会被注意到,但如果程序依赖某种基于数据库名的机制,在某些情况下可能会导致混乱。
在正常(非恢复)模式下,如果对具有登录能力的角色执行 DROP USER 或 DROP ROLE,而该用户仍处于连接状态,已连接的用户不会受到影响,会继续保持连接。不过,该用户无法重新连接。恢复期间也是如此,因此,在主库上执行 DROP USER 不会断开该用户在备库上的连接。
统计收集器在恢复期间保持活动状态。所有扫描、读取、块、索引使用情况等,都会在备库上正常记录。重放操作不会重复记录其在主库上产生的统计影响,因此重放一次插入不会增加 pg_stat_user_tables 的 Inserts 列。统计文件会在恢复开始时被删除,因此主库和备库的统计信息不同;这是特性,不是缺陷。
恢复期间自动清理不会运行。它会在恢复结束时正常启动。
检查点进程和后台写入进程在恢复期间是活动的。检查点进程会执行重启点(类似于主库上的检查点),后台写入进程会执行正常的块清理活动。这可能包括更新存储在备库上的提示位信息。恢复期间接受CHECKPOINT命令,不过它执行的是重启点,而不是新的检查点。
多个参数已经在Section 26.5.2和Section 26.5.3中提到过。
在主库上,可以使用 wal_level 和 vacuum_defer_cleanup_age 参数。max_standby_archive_delay 和 max_standby_streaming_delay 在主库上设置时没有效果。
在备库上,可以使用 hot_standby、max_standby_archive_delay 和 max_standby_streaming_delay 参数。只要服务器仍处于备库模式,vacuum_defer_cleanup_age 就没有效果,不过备库成为主库后,它就会起作用。
热备有几个限制。这些限制能够、也很可能会在未来版本中消除:
必须完整掌握正在运行的事务,才能取得快照。如果事务使用了大量子事务(目前为超过 64 个),只读连接的开始时间就会延迟,直到运行时间最长的写事务完成。发生这种情况时,服务器日志中会记录说明消息。
备库查询的有效起始点是在主库的每个检查点上生成的。如果主库处于关闭状态时备库被关闭,那么在主库再次启动并在 WAL 日志中生成更多起始点之前,备库可能无法重新进入热备状态。不过在最常见的场景中,这通常不是问题。一般来说,如果主库关闭且不再可用,很可能是出现了严重故障,此时无论如何都需要把备库提升为新的主库。而在主库被有意关闭的情况下,协调好备库顺利成为新主库,本来也就是标准流程。
在恢复结束时,由预备事务持有的AccessExclusiveLocks将需要正常数量两倍的锁表项。如果你计划运行大量并发的预备事务,而这些事务通常会持有AccessExclusiveLocks,或者你计划运行一个会持有许多AccessExclusiveLocks的大事务,那么建议选择更大的max_locks_per_transaction值,可能要达到主库上该参数值的两倍。如果你的max_prepared_transactions设置为 0,则完全不需要考虑这一点。
热备中尚不支持可串行化事务隔离级别。(详见Section 13.2.3和Section 13.4.1。)在热备模式下尝试把事务设置为可串行化隔离级别会产生错误。