codecamp

PostgreSQL 热备

26.5.1. 用户概览
26.5.2. 处理查询冲突
26.5.3. 管理员概览
26.5.4. 热备参数参考
26.5.5. 警告

术语热备用来描述处于归档恢复或后备模式中的服务器连接到服务器并运行只读查询的能力。这有助于复制目的以及以高精度恢复一个备份到一个期望的状态。术语热备也指服务器从恢复转移到正常操作而用户能继续运行查询并且保持其连接打开的能力。

在热备模式中运行查询与正常查询操作相似,尽管如下所述存在一些用法和管理上的区别。

26.5.1. 用户概览

hot_standby参数在一台后备服务器上被设置为真时,一旦恢复将系统带到一个一致的状态它将开始接受连接。所有这些连接都被限制为只读,甚至临时表都不能被写入。

后备服务器上的数据需要一些时间从主服务器到达后备服务器,因此在主服务器和后备服务器之间将有一段可以度量的延迟。近乎同时在主服务器和后备服务器上运行相同的查询可能因此而返回不同的结果。我们说后备服务器上的数据与主服务器是最终一致的。一旦一个事务的提交记录在后备服务器上被重播,那个事务所作的修改将对后备服务器上所有新取得的快照可见。快照可以在每个查询或每个事务的开始时取得,这取决于当前的事务隔离级别。详见第 13.2 节

在热备期间开始的事务可能发出下列命令:

  • 查询访问: SELECTCOPY TO

  • 游标命令: DECLAREFETCHCLOSE

  • 设置: SHOWSETRESET

  • 事务管理命令:

    • BEGINENDABORTSTART TRANSACTION

    • SAVEPOINTRELEASEROLLBACK TO SAVEPOINT

    • EXCEPTION块或其他内部子事务

  • LOCK TABLE,不过只在下列模式之一中明确发出: ACCESS SHAREROW SHAREROW EXCLUSIVE.

  • 计划和资源: PREPAREEXECUTEDEALLOCATEDISCARD

  • 插件和扩展: LOAD

  • UNLISTEN

在热备期间开始的事务将不会被分配一个事务 ID 并且不能被写入到系统的预写式日志。因此,下列动作将产生错误消息:

  • 数据操纵语言(DML): INSERTUPDATEDELETECOPY FROMTRUNCATE。注意不允许在恢复期间导致一个触发器被执行的动作。这个限制甚至被应用到临时表,因为不分配事务 ID 表行就不能被读或写,而当前不可能在一个热备环境中分配事务 ID。

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

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

  • SELECT语句上的能产生 DML 命令的规则。

  • 显式请求一个高于ROW EXCLUSIVE MODE的模式的LOCK

  • 默认短形式的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,因为即使只读事务也需要在准备阶段(两阶段提交中的第一个阶段)写 WAL。

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

  • LISTEN,NOTIFY

在正常操作中,只读事务被允许使用LISTENNOTIFY,因此热备会话在比普通只读会话更紧一点的限制下操作。这些限制中的某些可能会在一个未来的发行中被放松。

在热备期间,参数transaction_read_only总是为真并且不可以被改变。但是只要不尝试修改数据库,热备期间的连接工作起来更像其他数据库连接。如果发生故障转移或切换,该数据库将切换到正常处理模式。当服务器改变模式时会话将保持连接。一旦热备结束,它将可以发起读写事务(即使是一个在热备期间启动的会话)。

用户将可以通过发出SHOW transaction_read_only来了解他们的会话是不是只读的。此外,一组函数(表 9.86)允许用户访问关于后备服务器的信息。这些允许你编写关心数据库当前状态的程序。这些可以被用来监控恢复的进度,或者允许你编写恢复数据库到特定状态的复杂程序。

26.5.2. 处理查询冲突

主服务器和后备服务器在多方面都松散地连接在一起。主服务器上的动作将在后备服务器上产生效果。结果是在它们之间有潜在的负作用或冲突。最容易理解的冲突是性能:如果在主服务器上发生一次大数据量的载入,那么着将在后备服务器上产生一个相似的 WAL 记录流,因而后备服务器查询可能要竞争系统资源(例如 I/O)。

随着热备发生的还可能有其他类型的冲突。对于可能需要被取消的查询和(某些情况中)解决它们的已断开会话来说,这些冲突是硬冲突。用户可以用几种方式来处理这种冲突。冲突情况包括:

  • 在主服务器上取得了访问排他锁(包括显式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 规则的正确的数据可见性。不过,这个规则只能被应用于执行在主控机上的事务。因此有可能主控机上的清除会移除对一个后备服务器事务还可见的行版本。

有经验的用户应当注意行版本清除和行版本冻结都可能与后备查询冲突。即便在一个没有被更新或被删除行的表上运行一次手工VACUUM FREEZE也可能导致冲突。

用户应当清楚,主服务器上被正常和重度更新的表将快速地导致后备服务器上长时间运行的查询被取消。在这样的情况下,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 项导致的快速取消。

另一个选项是增加主服务器上的vacuum_defer_cleanup_age,这样死亡行不会像平常那么快地被清理。这将允许在后备服务器上的查询能在被取消前有更多时间执行,并且不需要设置一个很高的max_standby_streaming_delay。但是,这种方法很难保证任何指定的执行时间窗口,因为 vacuum_defer_cleanup_age是用主服务器上被执行的事务数来衡量的。

查询取消的数量和原因可以使用后备服务器上的pg_stat_database_conflicts系统视图查看。pg_stat_database系统视图也包含汇总信息。

26.5.3. 管理员概览

如果hot_standbypostgresql.conf中被设置为on并且存在一个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_delaymax_standby_streaming_delay选择适当的设置很重要。最好的选择取决于业务的优先级。例如如果服务器主要的任务是作为高可用服务器,那么你将想要低延迟设置,甚至是零(尽管它是一个非常激进的设置)。如果后备服务器的任务是作为一个用于决策支持查询的额外服务器,那么将其最大延迟值设置为很多小时甚至 -1 (表示无限等待)可能都是可以接受的。

在主服务器上写出的事务状态 "hint bits" 是不被 WAL 记录的,因此后备服务器上的数据将可能重新写出该提示。这样,即使所有用户都是只读的,后备服务器仍将执行磁盘写操作;但数据值本身并没有发生改变。用户将仍写出大的排序临时文件并且重新生成 relcache 信息文件,这样在热备模式中数据库没有哪个部分是真正只读的。还要注意使用dblink模块写到远程数据库以及其他使用 PL 函数位于数据库之外的操作仍将可用,即使该事务是本地只读的。

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

  • 数据定义语言(DDL): e.g., CREATE INDEX

  • 特权和所有权: GRANT, REVOKE,

  • 维护命令: ANALYZE, VACUUM,CLUSTER, REINDEX

注意这些命令中的某些实际上在主服务器上的“只读”模式事务期间是被允许的。

结果是,你无法创建只存在于后备服务器上的额外索引以及统计信息。如果需要这些管理命令,它们应该在主服务器上被执行,并且最后那些改变将被传播到后备服务器。

pg_cancel_backend()pg_terminate_backend()将在用户后端上工作,而不是执行恢复的 Startup 进程。pg_stat_activity不会为 Startup 进程显示一个项,也不会把恢复事务显示为活动。结果是在恢复期间pg_prepared_xacts总是为空。如果你希望解决不能确定的预备事务,查看主服务器上的 pg_prepared_xacts并且发出命令来解决那里的事务或者在恢复结束后来解决它们。

和平常一样,pg_locks将显示被后端持有的锁。pg_locks也会显示一个由 Startup 进程管理的虚拟事务,它拥有被恢复重播的事务所持有的所有AccessExclusiveLocks。注意 Startup 进程不请求锁来做数据库更改,并且因此对于 Startup 进程除AccessExclusiveLocks之外的锁不显示在 pg_locks中,它们仅被假定存在。

Nagios的插件check_pgsql将可以工作,因为它检查的简单信息是存在的。check_postgres监控脚本也将能工作,尽管某些被报告的值可能给出不同或者混乱的结果。例如,上一次清理时间将不会被维护,因为在后备服务器上不会发生清理。在主服务器上运行的清理仍会把它们的改变发送给后备服务器。

WAL 文件控制命令在恢复期间将不会工作,如pg_start_backuppg_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不会使后备服务器上的用户断开。

在恢复期间统计收集器是活动的。所有扫描、读、阻塞、索引使用等将在后备服务器上被正常的记录。被重播的动作将不会重复它们在主服务器上的效果,因此重播一个插入将不会导致pg_stat_user_tables的 Inserts 列上的递增。在恢复的开始 stats 文件会被删除,因此来自主服务器和后备服务器的 stats 将不同;这被认为是一种特性而不是缺陷。

在恢复期间自动清理不是活动的。它将在恢复末尾正常启动。

检查点进程和后台写入进程在恢复期间是活动状态的。检查点进程将执行重启动点(与主服务器上的检查点相似),后台写入进程将执行正常的块清理活动。 这可以包括存储在后备服务器上的提示位信息的更新。在恢复期间,CHECKPOINT命令会被接受,然而它会执行一个重启点而不是一个新的检查点。

26.5.4. 热备参数参考

多个参数已经在第 26.5.2 节第 26.5.3 节中提到过。

在主服务器上,可以使用参数wal_levelvacuum_defer_cleanup_age。在主服务器上设置max_standby_archive_delaymax_standby_streaming_delay不会产生效果。

在主服务器上,可以使用参数hot_standbymax_standby_archive_delaymax_standby_streaming_delay。只要服务器保持在后备模式 vacuum_defer_cleanup_age就没有效果,然而当后备服务器变成主服务器时它将变得相关。

26.5.5. 警告

热备有一些限制。这些限制很可能在未来的发行中被修复:

  • 在能够取得快照之前,需要正在运行的事务的完整知识。使用大量子事务(目前指超过 64 个)的事务将延迟只读连接的启动,直到最长的运行着的写事务完成。如果发生这种情况,说明消息将被发送到服务器日志。

  • 主服务器上的每一个检查点将产生用于后备查询的可用启动点。如果后备服务器在主控机处于关闭状态时被关闭,就没有办法在主服务器启动之前重新进入热后备,因此它在 WAL 日志中产生一个进一步启动点。这种情况在它可能发生的大部分常见情况中不是一个问题。通常,如果主服务器被关闭并且不再可用,这可能是由于某种严重错误要求后备服务器被转变成为一个新的主服务器来操作。并且在主服务器被故意关闭的情况下,协调保证后备服务器平滑地过渡为新的主服务器也是一种标准过程。

  • 在恢复尾声,由预备事务持有的AccessExclusiveLocks将要求两倍的正常锁表项。如果你计划运行大量并发的通常要求AccessExclusiveLocks的预备事务,或者你计划运行一个需要很多AccessExclusiveLocks的大型事务,我们建议你为max_locks_per_transaction选择一个更大的值,也许是主服务器上该参数值的两倍。如果你的 max_prepared_transactions设置为 0,你根本不需要考虑这个问题。

  • 可序列化事务隔离级别目前在热备中不可用(详见第 13.2.3 节第 13.4.1 节)。尝试在热备模式中将一个事务设置为可序列化隔离级别将产生一个错误。


PostgreSQL 日志传送的替代方法
PostgreSQL 监控数据库活动
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

PostgreSQL SQL语言

PostgreSQL 服务器管理

PostgreSQL 客户端接口

PostgreSQL 服务器编程

PostgreSQL 参考

PostgreSQL 内部

PostgreSQL 附录

PostgreSQL 参考书目

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }