运维人员指南

1. 日常清理

IvorySQL数据库要求周期性的清理维护。对于很多安装,让自动清理守护进程来执行清理已经足够,你也可能需要调整其中描述的自动清理参数来获得最佳结果。某些数据库管理员会希望使用手动管理的`VACUUM`命令来对后台进程的活动进行补充或者替换,这通常使用cron或任务计划程序脚本来执行。要正确地设置手动管理的清理。

1.1. 清理的基础知识

IvorySQL的 VACUUM命令出于几个原因必须定期处理每一个表:

  1. 恢复或重用被已更新或已删除行所占用的磁盘空间。

  2. 更新被IvorySQL查询规划器使用的数据统计信息。

  3. 更新可见性映射,它可以加速只用索引的扫描。

  4. 保护老旧数据不会由于事务ID回卷或多事务ID回卷而丢失。

正如后续小节中解释的,每一个原因都将指示以不同的频率和范围执行`VACUUM`操作。

有两种`VACUUM`的变体:标准`VACUUM`和`VACUUM FULL`。VACUUM FULL`可以收回更多磁盘空间但是运行起来更慢。另外,标准形式的`VACUUM`可以和生产数据库操作并行运行(`SELECTINSERTUPDATE`和`DELETE`等命令将继续正常工作,但在清理期间你无法使用`ALTER TABLE`等命令来更新表的定义)。`VACUUM FULL`要求在其工作的表上得到一个`ACCESS EXCLUSIVE 锁,因此无法和对此表的其他使用并行。因此,通常管理员应该努力使用标准`VACUUM`并且避免`VACUUM FULL`。

`VACUUM`会产生大量I/O流量,这将导致其他活动会话性能变差。可以调整一些配置参数来后台清理活动造成的性能冲击.

1.2. 恢复磁盘空间

在IvorySQL中,一次行的`UPDATE`或`DELETE`不会立即移除该行的旧版本。这种方法对于从多版本并发控制获益是必需的:当旧版本仍可能对其他事务可见时,它不能被删除。但是最后,任何事务都不会再对一个过时的或者被删除的行版本感兴趣。它所占用的空间必须被回收来用于新行,这样可避免磁盘空间需求的无限制增长。这通过运行`VACUUM`完成。

`VACUUM`的标准形式移除表和索引中的死亡行版本并将该空间标记为可在未来重用。不过,它将不会把该空间交还给操作系统,除非在特殊的情况中表尾部的一个或多个页面变成完全空闲并且能够很容易地得到一个排他表锁。相反,`VACUUM FULL`通过把死亡空间之外的内容写成一个完整的新版本表文件来主动紧缩表。这将最小化表的尺寸,但是要花较长的时间。它也需要额外的磁盘空间用于表的新副本,直到操作完成。

例行清理的一般目标是多做标准的`VACUUM`来避免需要`VACUUM FULL`。自动清理守护进程尝试这样工作,并且实际上永远不会发出`VACUUM FULL`。在这种方法中,其思想不是让表保持它们的最小尺寸,而是保持磁盘空间使用的稳定状态:每个表占用的空间等于其最小尺寸外加清理之间将使用的空间量。尽管`VACUUM FULL`可被用来把一个表收缩回它的最小尺寸并将该磁盘空间交还给操作系统,但是如果该表将在未来再次增长这样就没什么意义。因此,对于维护频繁被更新的表,适度运行标准`VACUUM`运行比少量运行`VACUUM FULL`要更好。

一些管理员更喜欢自己计划清理,例如在晚上负载低时做所有的工作。根据一个固定日程来做清理的难点在于,如果一个表有一次预期之外的更新活动尖峰,它可能膨胀得真正需要`VACUUM FULL`来回收空间。使用自动清理守护进程可以减轻这个问题,因为守护进程会根据更新活动动态规划清理操作。除非你的负载是完全可以预估的,完全禁用守护进程是不理智的。一种可能的折中方案是设置守护进程的参数,这样它将只对异常的大量更新活动做出反应,因而保证事情不会失控,而在负载正常时采用有计划的`VACUUM`来做批量工作。

对于那些不使用自动清理的用户,一种典型的方法是计划一个数据库范围的`VACUUM`,该操作每天在低使用量时段执行一次,并根据需要辅以在重度更新表上的更频繁的清理(一些有着极高更新率的安装会每几分钟清理一次它们的最繁忙的表)。如果你在一个集簇中有多个数据库,别忘记`VACUUM`每一个,你会用得上vacuumdb程序。

提示

当一个表因为大量更新或删除活动而包含大量死亡行版本时,纯粹的`VACUUM`可能不能令人满意。如果你有这样一个表并且你需要回收它占用的过量磁盘空间,你将需要使用`VACUUM FULL`,或者CLUSTER,或者ALTER TABLE的表重写变体之一。这些命令重写该表的一整个新拷贝并且为它构建新索引。所有这些选项都要求`ACCESS EXCLUSIVE` 锁。注意它们也临时使用大约等于该表尺寸的额外磁盘空间,因为直到新表和索引完成之前旧表和索引都不能被释放。

提示

如果你有一个表,它的整个内容会被周期性删除,考虑用TRUNCATE而不是先用`DELETE`再用`VACUUM`。`TRUNCATE`会立刻移除该表的整个内容,而不需要一次后续的`VACUUM`或`VACUUM FULL`来回收现在未被使用的磁盘空间。其缺点是会违背严格的 MVCC 语义。

1.3. 更新规划器统计信息

IvorySQL查询规划器依赖于有关表内容的统计信息来为查询产生好的计划。这些统计信息由ANALYZE命令收集,它除了直接被调用之外还可以作为`VACUUM`的一个可选步骤被调用。拥有适度准确的统计信息很重要,否则差的计划可能降低数据库性能。

自动清理守护进程如果被启用,当一个表的内容被改变得足够多时,它将自动发出`ANALYZE`命令。不过,管理员可能更喜欢依靠手动的`ANALYZE`操作,特别是如果知道一个表上的更新活动将不会影响“感兴趣的”列的统计信息时。守护进程严格地按照一个被插入或更新行数的函数来计划`ANALYZE`,它不知道那是否将导致有意义的统计信息改变。

正如用于空间恢复的清理一样,频繁更新统计信息对重度更新的表更加有用。但即使对于一个重度更新的表,如果该数据的统计分布没有很大改变,也没有必要更新统计信息。一个简单的经验法则是考虑表中列的最大和最小值改变了多少。例如,一个包含行被更新时间的`timestamp`列将在行被增加和更新时有一直增加的最大值;这样一列将可能需要更频繁的统计更新,而一个包含一个网站上被访问页面 URL 的列则不需要。URL 列可以经常被更改,但是其值的统计分布的变化相对很慢。

可以在指定表上运行`ANALYZE`甚至在表的指定列上运行,因此如果你的应用需要,可以更加频繁地更新某些统计。但实际上,通常只分析整个数据库是最好的,因为它是一种很快的操作。`ANALYZE`对一个表的行使用一种统计的随机采样,而不是读取每一个单一行。

提示

尽管对每列的`ANALYZE`频度调整可能不是非常富有成效,你可能会发现值得为每列调整被`ANALYZE`收集统计信息的详细程度。经常在`WHERE`中被用到的列以及数据分布非常不规则的列可能需要比其他列更细粒度的数据直方图。见`ALTER TABLE SET STATISTICS`,或者使用default_statistics_target配置参数改变数据库范围的默认值。

提示

自动清理守护进程不会为外部表发出`ANALYZE`命令,因为无法确定一个合适的频度。如果你的查询需要外部表的统计信息来正确地进行规划,比较好的方式是按照一个合适的时间表在那些表上手工运行`ANALYZE`命令。

提示

autovacuum守护进程不会对分区表发出ANALYZE命令。继承性父表只有在父表本身发生变化时才会被分析—​对子表的变化不会触发对父表的自动分析。如果你的查询需要对父表进行统计以进行正确的规划,那么有必要定期对这些表运行手动ANALYZE以保持统计的最新性。

1.4. 更新可见性映射

清理机制为每一个表维护着一个可见性映射,它被用来跟踪哪些页面只包含对所有活动事务(以及所有未来的事务,直到该页面被再次修改)可见的元组。这样做有两个目的。第一,清理本身可以在下一次运行时跳过这样的页面,因为其中没有什么需要被清除。

第二,这允许IvorySQL回答一些只用索引的查询,而不需要引用底层表。因为IvorySQL的索引不包含元组的可见性信息,一次普通的索引扫描会为每一个匹配的索引项获取堆元组,用来检查它是否能被当前事务所见。另一方面,一次*只用索引的扫描*会首先检查可见性映射。如果它了解到在该页面上的所有元组都是可见的,堆获取就可以被跳过。这对大数据集很有用,因为可见性映射可以防止磁盘访问。可见性映射比堆小很多,因此即使堆非常大,可见性映射也可以很容易地被缓存起来。

1.5. 防止事务ID回卷失败

IvorySQL的 MVCC 事务语义依赖于能够比较事务 ID(XID)数字:如果一个行版本的插入 XID 大于当前事务的 XID,它就是“属于未来的”并且不应该对当前事务可见。但是因为事务 ID 的尺寸有限(32位),一个长时间(超过 40 亿个事务)运行的集簇会遭受到*事务 ID 回卷*问题:XID 计数器回卷到 0,并且本来属于过去的事务突然间就变成了属于未来 — 这意味着它们的输出变成不可见。简而言之,灾难性的数据丢失(实际上数据仍然在那里,但是如果你不能得到它也无济于事)。为了避免发生这种情况,有必要至少每 20 亿个事务就清理每个数据库中的每个表。

周期性的清理能够解决该问题的原因是,VACUUM`会把行标记为 冻结,这表示它们是被一个在足够远的过去提交的事务所插入,这样从 MVCC 的角度来看,效果就是该插入事务对所有当前和未来事务来说当然都 是可见的。IvorySQL保留了一个特殊的 XID (`FrozenTransactionId),这个 XID 并不遵循普通 XID 的比较规则 并且总是被认为比任何普通 XID 要老。普通 XID 使用模-232算 法来比较。这意味着对于每一个普通 XID都有 20 亿个 XID “更老”并且 有 20 亿个“更新”,另一种解释的方法是普通 XID 空间是没有端点的环。因此,一旦一个行版本创建时被分配了一个特定的普通 XID,该行版本将成为接下 来 20 亿个事务的“过去”(与我们谈论的具体哪个普通 XID 无关)。如果在 20 亿个事务之后该行版本仍然存在,它将突然变得好像在未来。要阻止这一切 发生,被冻结行版本会被看成其插入 XID 为`FrozenTransactionId`, 这样它们对所有普通事务来说都是“在过去”,而不管回卷问题。并且这样的行版本将一直有效直到被删除,不管它有多旧。

vacuum_freeze_min_age控制在其行版本被冻结前一个 XID 值应该有多老。如果被冻结的行将很快会被再次修改,增加这个设置可以避免不必要 的工作。但是减少这个设置会增加在表必须再次被清理之前能够流逝的事务数。

`VACUUM`通常会跳过不含有任何死亡行版本的页面,但是不会跳过那些含有带旧 XID 值的行版本的页面。要保证所有旧的行版本都已经被冻结,需要对整个表做一次扫描。vacuum_freeze_table_age控制`VACUUM`什么时候这样做:如果该表经过`vacuum_freeze_table_age`减去`vacuum_freeze_min_age`个事务还没有被完全扫描过,则会强制一次全表清扫。将这个参数设置为 0 将强制`VACUUM`总是扫描所有页面而实际上忽略可见性映射。

一个表能保持不被清理的最长时间是 20 亿个事务减去`VACUUM`上次扫描全表时的`vacuum_freeze_min_age`值。如果它超过该时间没有被清理,可能会导致数据丢失。要保证这不会发生,将在任何包含比autovacuum_freeze_max_age配置参数所指定的年龄更老的 XID 的未冻结行的表上调用自动清理(即使自动清理被禁用也会发生)。

这意味着如果一个表没有被清理,大约每`autovacuum_freeze_max_age`减去`vacuum_freeze_min_age`事务就会在该表上调用一次自动清理。对那些为了空间回收目的而被正常清理的表,这是无关紧要的。然而,对静态表(包括接收插入但没有更新或删除的表)就没有为空间回收而清理的需要,因此尝试在非常大的静态表上强制自动清理的间隔最大化会非常有用。显然我们可以通过增加`autovacuum_freeze_max_age`或减少`vacuum_freeze_min_age`来实现此目的。

vacuum_freeze_table_age`的实际最大值是 0.95 * `autovacuum_freeze_max_age,高于它的设置将被上限到最大值。一个高于`autovacuum_freeze_max_age`的值没有意义,因为不管怎样在那个点上都会触发一次防回卷自动清理,并且 0.95 的乘数为在防回卷自动清理发生之前运行一次手动`VACUUM`留出了一些空间。作为一种经验法则,`vacuum_freeze_table_age`应当被设置成一个低于`autovacuum_freeze_max_age`的值,留出一个足够的空间让一次被正常调度的`VACUUM`或一次被正常删除和更新活动触发的自动清理可以在这个窗口中被运行。将它设置得太接近可能导致防回卷自动清理,即使该表最近因为回收空间的目的被清理过,而较低的值将导致更频繁的全表扫描。

增加`autovacuum_freeze_max_age`(以及和它一起的`vacuum_freeze_table_age`)的唯一不足是数据库集簇的`pg_xact`和`pg_commit_ts`子目录将占据更多空间,因为它必须存储所有向后`autovacuum_freeze_max_age`范围内的所有事务的提交状态和(如果启用了`track_commit_timestamp`)时间戳。提交状态为每个事务使用两个二进制位,因此如果`autovacuum_freeze_max_age`被设置为它的最大允许值 20 亿,`pg_xact`将会增长到大约 0.5 吉字节,`pg_commit_ts`大约20GB。如果这对于你的总数据库尺寸是微小的,我们推荐设置`autovacuum_freeze_max_age`为它的最大允许值。否则,基于你想要允许`pg_xact`和`pg_commit_ts`使用的存储空间大小来设置它(默认情况下 2 亿个事务大约等于`pg_xact`的 50 MB存储空间,`pg_commit_ts`的2GB的存储空间)。

减小`vacuum_freeze_min_age`的一个不足之处是它可能导致`VACUUM`做无用的工作:如果该行在被替换成`FrozenXID`之后很快就被修改(导致该行获得一个新的 XID),那么冻结一个行版本就是浪费时间。因此该设置应该足够大,这样直到行不再可能被修改之前,它们都不会被冻结。

为了跟踪一个数据库中最老的未冻结 XID 的年龄,`VACUUM`在系统表`pg_class`和`pg_database`中存储 XID 的统计信息。特别地,一个表的`pg_class`行的`relfrozenxid`列包含被该表的上一次全表`VACUUM`所用的冻结截止 XID。该表中所有被有比这个截断 XID 老的普通 XID 的事务插入的行 都确保被冻结。相似地,一个数据库的`pg_database`行的`datfrozenxid`列是出现在该数据库中的未冻结 XID 的下界 — 它只是数据库中每一个表的`relfrozenxid`值的最小值。一种检查这些信息的方便方法是执行这样的查询:

SELECT c.oid::regclass as table_name,
       greatest(age(c.relfrozenxid),age(t.relfrozenxid)) as age
FROM pg_class c
LEFT JOIN pg_class t ON c.reltoastrelid = t.oid
WHERE c.relkind IN ('r', 'm');

SELECT datname, age(datfrozenxid) FROM pg_database;

`age`列度量从该截断 XID 到当前事务 XID 的事务数。

`VACUUM`通常只扫描从上次清理后备修改过的页面,但是只有当全表被扫描时`relfrozenxid`才能被推进。当`relfrozenxid`比`vacuum_freeze_table_age`个事务还老时、当`VACUUM`的`FREEZE`选项被使用时或当所有页面正好要求清理来移除死亡行版本时,全表将被扫描。当`VACUUM`扫描全表时,在它被完成后,`age(relfrozenxid)`应该比被使用的`vacuum_freeze_min_age`设置略大(比在`VACUUM`开始后开始的事务数多)。如果在`autovacuum_freeze_max_age`被达到之前没有全表扫描`VACUUM`在该表上被发出,将很快为该表强制一次自动清理。

如果出于某种原因自动清理无法从一个表中清除旧的 XID,当数据库的最旧 XID 和回卷点之间达到 4 千万个事务时,系统将开始发出这样的警告消息:

WARNING:  database "mydb" must be vacuumed within 39985967 transactions
HINT:  To avoid a database shutdown, execute a database-wide VACUUM in that database.

(如该示意所建议的,一次手动的`VACUUM`应该会修复该问题;但是注意该次`VACUUM`必须由一个超级用户来执行,否则它将无法处理系统目录并且因而不能推进数据库的`datfrozenxid`)。如果这些警告被忽略,一旦距离回卷点只剩下 3 百万个事务时,该系统将会关闭并且拒绝开始任何新的事务:

ERROR:  database is not accepting commands to avoid wraparound data loss in database "mydb"
HINT:  Stop the postmaster and vacuum that database in single-user mode.

这 3 百万个事务的安全余量是为了让管理员能通过手动执行所要求的`VACUUM`命令进行恢复而不丢失数据。但是,由于一旦系统进入到安全关闭模式,它将不会执行命令。做这个操作的唯一方法是停止服务器并且以单一用户启动服务器来执行`VACUUM`。单一用户模式中不会强制该关闭模式。

*Multixact ID*被用来支持被多个事务锁定的行。由于在一个元组头部 只有有限的空间可以用来存储锁信息,所以只要有多于一个事务并发地锁住一个行, 锁信息将使用一个“多个事务 ID”(或简称多事务 ID)来编码。任何特定 多事务 ID 中包括的事务 ID 的信息被独立地存储在`pg_multixact`子目 录中,并且只有多事务 ID 出现在元组头部的`xmax`域中。和事务 ID 类似,多事务 ID 也是用一个 32 位计数器实现,并且也采用了相似的存储,这些都要 求仔细的年龄管理、存储清除和回卷处理。在每个多事务中都有一个独立的存储区域 保存成员列表,它也使用一个 32 位计数器并且也应被管理。

在一次`VACUUM`表扫描(部分或者全部)期间,任何比 vacuum_multixact_freeze_min_age 要老的多事务 ID 会被替换为一个不同的值,该值可以是零值、 一个单一事务 ID 或者一个更新的多事务 ID。 对于每一个表,pg_class.relminmxid 存储了在该表任意元组中仍然存在的最老可能多事务 ID。如果这个值比 vacuum_multixact_freeze_table_age老, 将强制一次全表扫描。可以在 pg_class.relminmxid 上使用`mxid_age()`来找到它的年龄。

全表`VACUUM`扫描(不管是什么导致它们)将为表推进该值。 最后,当所有数据库中的所有表被扫描并且它们的最老多事务值被推进, 较老的多事务的磁盘存储可以被移除。

作为一种安全设备,对任何多事务年龄超过 autovacuum_multixact_freeze_max_age的表, 都将发生一次全表清理扫描。当多事务成员占用的存储超过 2GB 时,从那些具有最老多事务年龄的表开始,全表清理扫描也将逐步在所有表上进行。即使自动清理被 在名义上被禁用,也会发生这两种主动扫描。

1.6. 自动清理后台进程

IvorySQL有一个可选的但是被高度推荐的特性*autovacuum*,它的目的是自动执行`VACUUM`和`ANALYZE`命令。当它被启用时,自动清理会检查被大量插入、更新或删除元组的表。这些检查会利用统计信息收集功能,因此除非track_counts被设置为`true`,自动清理不能被使用。在默认配置下,自动清理是被启用的并且相关配置参数已被正确配置。

“自动清理后台进程”实际上由多个进程组成。有一个称为 自动清理启动器*的常驻后台进程, 它负责为所有数据库启动*自动清理工作者*进程。 启动器将把工作散布在一段时间上,它每隔 autovacuum_naptime秒尝试在每个数据库中启动一个工作者 (因此,如果安装中有N个数据库,则每 autovacuum_naptime/N*秒将启动一个新的工作者)。 在同一时间只允许最多autovacuum_max_workers个工作者进程运行。如果有超过`autovacuum_max_workers` 个数据库需要被处理,下一个数据库将在第一个工作者结束后马上被处理。 每一个工作者进程将检查其数据库中的每一个表并且在需要时执行 VACUUM`和/或`ANALYZE。 可以设置log_autovacuum_min_duration来监控自动清理工作者的活动。

如果在一小段时间内多个大型表都变得可以被清理,所有的自动清理工作者可能都会被占用来在一段长的时间内清理这些表。这将会造成其他的表和数据库无法被清理,直到一个工作者变得可用。对于一个数据库中的工作者数量并没有限制,但是工作者确实会试图避免重复已经被其他工作者完成的工作。注意运行着的工作者的数量不会被计入max_connections或superuser_reserved_connections限制。

`relfrozenxid`值比autovacuum_freeze_max_age事务年龄更大的表总是会被清理(这页表示这些表的冻结最大年龄被通过表的存储参数修改过,参见后文)。否则,如果从上次`VACUUM`以来失效的元组数超过“清理阈值”,表也会被清理。清理阈值定义为:

清理阈值 = 清理基本阈值 + 清理缩放系数 * 元组数

其中清理基本阈值为autovacuum_vacuum_threshold, 清理缩放系数为autovacuum_vacuum_scale_factor, 元组数为`pg_class`.reltuples

如果自上次清理以来插入的元组数量超过了定义的插入阈值,表也会被清理,该阈值定义为:

清理插入阈值 = 清理基础插入阈值 + 清理插入缩放系数 * 元组数

清理插入基础阈值为autovacuum_vacuum_insert_threshold,清理插入缩放系数为autovacuum_vacuum_insert_scale_factor。 这样的清理可以允许部分的表被标识为*all visible*,并且也可以允许元组被冻结,可以减小后续清理的工作需要。 对于可以接收`INSERT`操作但是不能或几乎不能`UPDATE`/DELETE`操作的表, 可能会从降低表的autovacuum_freeze_min_age中受益,因为这可能允许元组在早期清理中被冻结。 废弃元组的数量和插入元组的数量可从统计收集器中获得;它是一个半精确的计数,由每个`UPDATEDELETEINSERT 操作进行更新。 (它只是半精确的,因为一些信息可能会在重负载情况下丢失。) 如果表的`relfrozenxid`值大于`vacuum_freeze_table_age` 事务老的, 执行一个主动的清理来冻结旧的元组,并推进`relfrozenxid`;否则,只有上次清理以后修改过的页面被扫描。

对于分析,也使用了一个相似的阈值:

分析阈值 = 分析基本阈值 + 分析缩放系数 * 元组数

该阈值将与自从上次`ANALYZE`以来被插入、更新或删除的元组数进行比较。

临时表不能被自动清理访问。因此,临时表的清理和分析操作必须通过会话期间的SQL命令来执行。

默认的阈值和缩放系数都取自于`postgresql.conf`,但是可以为每一个表重写它们(和许多其他自动清理控制参数), 详情参见Storage Parameters。 如果一个设置已经通过一个表的存储参数修改,那么在处理该表时使用该值,否则使用全局设置。

当多个工作者运行时,在所有运行着的工作者之间自动清理代价延迟参数是 “平衡的”,这样不管实际运行的工作者数量是多少, 对于系统的总体 I/O 影响总是相同的。不过,任何正在处理已经设置了每表 autovacuum_vacuum_cost_delay`或 `autovacuum_vacuum_cost_limit 存储参数的表的工作者不会被考虑在均衡算法中。

autovacuum工作进程通常不会阻止其他命令。如果某个进程尝试获取与autovacuum持有的`SHARE UPDATE EXCLUSIVE`锁冲突的锁,则锁获取将中断该autovacuum。有关冲突的锁定模式,但是,如果autovacuum正在运行以防止事务ID回卷(即在`pg_stat_activity`视图中的autovacuum查询名以`(to prevent wraparound)`结尾),则autovacuum不会被自动中断。

警告

定期运行需要获取与`SHARE UPDATE EXCLUSIVE`锁冲突的锁的命令(例如ANALYZE)可能会让autovacuum始终无法完成。

2. 日常重建索引

在某些情况下值得周期性地使用REINDEX命令或一系列独立重构步骤来重建索引。

已经完全变成空的B树索引页面被收回重用。但是,还是有一种低效的空间利用的可能性:如果一个页面上除少量索引键之外的全部键被删除,该页面仍然被分配。因此,在这种每个范围中大部分但不是全部键最终被删除的使用模式中,可以看到空间的使用是很差的。对于这样的使用模式,推荐使用定期重索引。

对于非B树索引可能的膨胀还没有很好地定量分析。在使用非B树索引时定期监控索引的物理尺寸是个好主意。

还有,对于B树索引,一个新建立的索引比更新了多次的索引访问起来要略快, 因为在新建立的索引上,逻辑上相邻的页面通常物理上也相邻(这样的考虑目前并不适用于非B树索引)。仅仅为了提高访问速度也值得定期重索引。

REINDEX在所有情况下都可以安全和容易地使用。 默认情况下,此命令需要一个`ACCESS EXCLUSIVE`锁,因此通常最好使用`CONCURRENTLY`选项执行它,该选项仅需要获取`SHARE UPDATE EXCLUSIVE`锁。

3. 日志文件维护

把数据库服务器的日志输出保存在一个地方是个好主意, 而不是仅仅通过`/dev/null`丢弃它们。 在进行问题诊断的时候,日志输出是非常宝贵的。不过,日志输出可能很庞大(特别是在比较高的调试级别上), 因此你不会希望无休止地保存它们。你需要轮转日志文件, 这样在一段合理的时间后会开始新的日志文件并且移除旧的。

如果你简单地把`postgres`的stderr定向到一个文件中,你会得到日志输出,但是截断该日志文件的唯一方法是停止并重起服务器。这样做对于开发环境中使用的IvorySQL可能是可接受的,但是你肯定不想在生产环境上这么干。

一个更好的办法是把服务器的stderr输出发送到某种日志轮转程序里。我们有一个内建的日志轮转程序,你可以通过在 `postgresql.conf`里设置配置参数`logging_collector`为`true`的办法启用它。你也可以使用这种方法把日志数据捕捉成机器可读的CSV(逗号分隔值)格式。

另外,如果在你已经使用的其他服务器软件中有一个外部日志轮转程序,你可能更喜欢使用它。 比如,包含在Apache发布里的rotatelogs工具就可以用于IvorySQL。要做到这一点,方法之一是把服务器的stderr用管道重定向到要用的程序。 如果你用`pg_ctl`启动服务器,那么stderr已经重定向到stdout, 因此你只需要一个管道命令,比如:

pg_ctl start | rotatelogs /var/log/pgsql_log 86400

您可以通过设置logrotate来收集由IvorySQL内置日志收集器生成的日志文件来组合这些方法。在这种情况下,日志收集器定义日志文件的名称和位置,而logrotate 则定期归档这些文件。启动日志轮转时,logrotate必须确保应用程序将进一步的输出发送到新文件。这通常是通过`postrotate`脚本完成的,该脚本向应用程序发送`SIGHUP`信号,使其重新打开日志文件。在IvorySQL中,您可以使用`logrotate`选项运行`pg_ctl`。服务器收到此命令后,服务器将切换到新的日志文件或重新打开现有文件,具体取决于日志记录配置。

注意

定期运行需要获取与`SHARE UPDATE EXCLUSIVE`锁冲突的锁的命令(例如ANALYZE)可能会让autovacuum始终无法完成使用静态日志文件名时,如果达到最大打开文件数限制或发生文件表溢出,则服务器可能无法重新打开日志文件。 在这种情况下,日志消息将发送到旧的日志文件,直到成功进行日志轮转为止。 如果将logrotate配置为压缩日志文件并将其删除,则服务器可能会丢失此时间范围内记录的消息。 为避免此问题,可以将日志收集器配置为动态分配日志文件名,并使用`prerotate`脚本忽略打开的日志文件。

另外一种生产级的管理日志输出的方法就是把它们发送给syslog,让syslog处理文件轮转。 要利用这个工具,我们需要设置`postgresql.conf`里的`log_destination`配置参数设置为`syslog`(记录`syslog`日志)。然后在你想强迫syslog守护进程开始写入一个新日志文件的时候, 你就可以发送一个 `SIGHUP`信号给它。 如果你想自动进行日志轮转,可以配置logrotate程序处理 来自syslog的日志文件。

不过,在很多系统上,syslog不是非常可靠,特别是在面对大量日志消息的情况下; 它可能在你最需要那些消息的时候截断或者丢弃它们。另外,在Linux,syslog会把每个消息刷写到磁盘上, 这将导致很差的性能(你可以在syslog配置文件里面的文件名开头使用一个“-”来禁用这种行为)。

请注意上面描述的所有解决方案关注的是在可配置的间隔上开始一个新的日志文件, 但它们并没有处理对旧的、不再需要的日志文件的删除。你可能还需要设置一个批处理任务来定期地删除旧日志文件。 另一种可能的方法是配置日志轮转程序,让它循环地覆盖旧的日志文件。

pgBadger 是一个外部项目,它可以进行日志文件的深度分析。check_postgres可在重要消息出现在日志文件中时向Nagios提供警告,也可以探测很多其他的特别情况。

4. 高可用、负载均衡和复制

4.1. 不同方案的比较

4.1.1. 共享磁盘故障转移

共享磁盘故障转移避免了只使用一份数据库拷贝带来的同步开销。它使用一个由多个服务器共享的单一磁盘阵列。如果主数据库服务器失效,后备服务器则可以挂载并启动数据库,就好像它从一次数据库崩溃中恢复过来了。这是一种快速的故障转移,并且不存在数据丢失。

共享硬件功能在网络存储设备中很常见。也可以使用一个网络文件系统,但是要注意的是该文件系统应具有完全的POSIX行为。这种方法的一个重大限制是如果共享磁盘阵列失效或损坏,主要和后备服务器都会变得无法工作。另一个问题是在主要服务器运行时,后备服务器永远不能访问共享存储。

4.1.2. 文件系统(块设备)复制

共享硬件功能的一种修改版本是文件系统复制,在其中对一个文件系统的所有改变会被镜像到位于另一台计算机上的一个文件系统。唯一的限制是该镜像过程必须能保证后备服务器有一份该文件系统的一致的拷贝 — 特别是对后备服务器的写入必须按照主控机上相同的顺序进行。DRBD是用于 Linux 的一种流行的文件系统复制方案。

4.1.3. 预写式日志传送

温备和热备服务器能够通过读取一个预写式日志(WAL)记录的流来保持为当前状态。如果主服务器失效,后备服务器拥有主服务器的几乎所有数据,并且能够快速地被变成新的主数据库服务器。这可以是同步的或异步的,并且只能用于整个数据库服务器。

可以使用基于文件的日志传送、流复制或两者的组合来实现一个后备服务器。

4.1.4. 逻辑复制

逻辑复制允许数据库服务器发送数据更新流给另一台服务器。IvorySQL逻辑复制从WAL构建出逻辑数据更新流。逻辑复制允许您逐个表复制数据更改。此外,发布数据更新的服务器可以同时订阅其他服务器的更改,从而允许数据在多个方向流动。第三方扩展也能提供类似的功能。

4.1.5. 基于触发器的主-备复制

基于触发器的复制通常会将修改数据的查询发送到指定的主服务器。它在逐个表的基础上工作,主服务器(通常)将数据更改异步发送到备用服务器。 主服务器运行时,备用服务器可以响应查询,并执行本地数据修改或写入操作。这种形式的复制通常用于减轻大数据分析型平台或者数据仓库查询负荷。

Slony-I是这种复制类型的一个例子。它使用表粒度,并且支持多个后备服务器。因为它会异步更新后备服务器(批量),在故障转移时可能会有数据丢失。

4.1.6. 基于SQL的复制中间件

通过基于SQL的复制中间件,一个程序拦截每一个 SQL 查询并把它发送给一个或所有服务器。每一个服务器独立地操作。读写查询必须被发送给所有服务器,这样每一个服务器都能接收到任何修改。但只读查询可以被只发送给一个服务器,这样允许读负载在服务器之间分布。

如果查询未经修改发送,则函数的`random()`随机值和`CURRENT_TIMESTAMP`函数的当前时间和序列值可能因不同服务器而异。 因为每个服务器独立运行,并且它发送 SQL 查询而没有真正的更改数据。如果这是不可接受的,那么中间件或应用程序必须从单一服务器源确定此类值,并将结果用于写入查询。 还必须注意确保所有服务器在提交或中止事务时都是相同的。这将涉及使用 两阶段提交PREPARE TRANSACTION和COMMIT PREPARED。 Pgpool-II和Continuent Tungsten就是这种复制的例子。

4.1.7. 异步多主控机复制

对于不会被定期连接或通讯链路较慢的服务器,如笔记本或远程服务器,保持服务器间的数据一致是一个挑战。通过使用异步的多主控机复制,每一个服务器独立工作并且定期与其他服务器通信来确定冲突的事务。这些冲突可以由用户或冲突解决规则来解决。Bucardo 是这种复制类型的一个例子。

4.1.8. 同步多主控机复制

在同步多主控机复制中,每一个服务器能够接受写请求,并且在每一个事务提交之前,被修改的数据会被从原始服务器传送给每一个其他服务器。繁重的写活动可能导致过多的锁定和提交延迟,进而导致很差的性能。读请求可以被发送给任意服务器。某些实现使用共享磁盘来减少通信负荷。同步多主控机复制主要对于读负载最好,尽管它的大优点是任意服务器都能接受写请求 — 没有必要在主服务器和后备服务器之间划分负载,并且因为数据修改被从一个服务器发送到另一个服务器,不会有非确定函数(如`random()`)的问题。

IvorySQL不提供这种复制类型,尽管在应用代码或中间件中可以使用 IvorySQL 的两阶段提交PREPARE TRANSACTION和COMMIT PREPARED来实现这种复制。

下表总结了上述多种方案的能力。

特性

共享磁盘

文件系统复制

预写式日志传送

逻辑复制

基于触发器的复制

SQL复制中间件

异步多主控机复制

同步多主控机复制

常用的示例

NAS

DRBD

内建流复制

内建逻辑复制,pglogical

Londiste,Slony

pgpool-II

Bucardo

通信方法

共享磁盘

磁盘块

WAL

逻辑解码

表行

SQL

表行

表行和行锁

不要求特殊硬件

允许多个主控机服务器

无主服务器负载

不等待多个服务器

with sync off

with sync off

主控机失效将永不丢失数据

with sync on

with sync on

复制体接受只读查询

with hot

每个表粒度

不需要冲突解决

有一些方案不适合上述的类别:

  • 数据分区

    数据分区将表分开成数据集。每个集合只能被一个服务器修改。例如,数据可以根据办公室划分,如伦敦和巴黎,每一个办公室有一个服务器。如果查询有必要组合伦敦和巴黎的数据,一个应用可以查询两个服务器,或者可以使用主/备复制来在每一台服务器上保持其他办公室数据的一个只读拷贝。
  • 多服务器并行查询执行

    上述的很多方案允许多个服务器来处理多个查询,但是没有一个允许一个单一查询使用多个服务器来更快完成。 这种方案允许多个服务器在一个单一查询上并发工作。 这通常通过把数据在服务器之间划分并且让每一个服务器执行该查询中属于它的部分,然后将结果返回给一个中心服务器,由它整合结果并发回给用户。 这也可以使用PL/Proxy工具集来实现这种方案。

4.2. 日志传送后备服务器

4.2.1. 规划

创建主服务器和后备服务器通常是明智的,因此它们可以尽可能相似,至少从数据库服务器的角度来看是这样。特别地,与表空间相关的路径名将被未经修改地传递,因此如果该特性被使用,主、备服务器必须对表空间具有完全相同的挂载路径。记住如果CREATE TABLESPACE在主服务器上被执行,在命令被执行前,它所需要的任何新挂载点必须在主服务器和所有后备服务器上先创建好。硬件不需要完全相同,但是经验显示,在应用和系统的生命期内维护两个相同的系统比维护两个不相似的系统更容易。在任何情况下硬件架构必须相同 — 从一个 32 位系统传送到一个 64 位系统将不会工作。

通常,不能在两个运行着不同主版本IvorySQL的服务器之间传送日志。IvorySQL 全球开发组的策略是不在次版本升级中改变磁盘格式,因此在主服务器和后备服务器上运行不同次版本将会成功地工作。不过,在这方面并没有提供正式的支持,因此我们建议让主备服务器上运行的版本尽可能相同。当升级到一个新的次版本时,最安全的策略是先升级后备服务器 — 一个新的次版本发行更可能兼容从前一个次版本读取 WAL 文件。

4.2.2. 后备服务器操作

服务器启动时,数据目录中存在 standby.signal 文件,服务器进入standby模式。

在后备模式中,服务器持续地应用从主控服务器接收到的 WAL。后备服务器可以从一个 WAL 归档restore_command或者通过一个 TCP 连接直接从主控机(流复制)读取 WAL。后备服务器将也尝试恢复在后备集簇的`pg_wal`目录中找到的 WAL。那通常在一次数据库重启后发生,那时后备机将在重启之前重播从主控机流过来的 WAL,但是你也可以在任何时候手动拷贝文件到`pg_wal`让它们被重播。

在启动时,后备机通过恢复归档位置所有可用的 WAL 来开始,这称为`restore_command`。一旦它到达那里可用的 WAL 的末尾并且`restore_command`失败,它会尝试恢复`pg_wal`目录中可用的任何 WAL。如果那也失败并且流复制已被配置,后备机会尝试连接到主服务器并且从在归档或`pg_wal`中找到的最后一个可用记录开始流式传送 WAL。如果那失败并且没有配置流复制,或者该连接后来断开,后备机会返回到步骤 1 并且尝试再次从归档里的文件恢复。这种尝试归档、`pg_wal`和流复制的循环会一直重复知道服务器停止或者一个触发器文件触发了故障转移。

当`pg_ctl promote`被运行,pg_promote()`被调用,或一个触发器文件被找到(`promote_trigger_file),后备模式会退出并且服务器会切换到普通操作。 在故障转移之前,在归档或`pg_wal`中立即可用的任何 WAL 将被恢复,但不会尝试连接到主控机。

4.2.3. 为后备服务器准备主控机

在主服务器上设置连续归档到一个后备服务器可访问的归档目录。即使主服务器垮掉该归档位置也应当是后备服务器可访问的,即它应当位于后备服务器本身或者另一个可信赖的服务器,而不是位于主控服务器上。

如果你想要使用流复制,在主服务器上设置认证来允许来自后备服务器的复制连接。即创建一个角色并且在`pg_hba.conf`中提供一个或多个数据库域被设置为`replication`的项。还要保证在主服务器的配置文件中`max_wal_senders`被设置为足够大的值。如果要使用复制槽,请确保`max_replication_slots`也被设置得足够高。

4.2.4. 建立一个后备服务器

要建立后备服务器,恢复从主服务器取得的基础备份。在后备服务器的集簇数据目录中创建一个文件standby.signal。将restore_command设置为一个从 WAL 归档中复制文件的简单命令。 如果你计划为了高可用性目的建立多个后备服务器,确认`recovery_target_timeline`被设置为`latest` (默认)来使得该后备服务器遵循发生在故障转移到另一个后备服务器之后发生的时间线改变。

注意

restore_command应该立即返回,如果必要该服务器将再次尝试该命令。

如果你想要使用流复制,在primary_conninfo中填入一个 libpq 连接字符串,其中包括主机名(或 IP 地址)和连接到主服务器所需的任何附加细节。如果主服务器需要一个口令用于认证,口令也应该被指定primary_conninfo中。

如果你正在为高性能目的建立后备服务器,像主服务器一样建立 WAL 归档、连接和认证,因为在故障转移后该后备服务器将作为一个主服务器工作。

如果你正在使用一个 WAL 归档,可以使用archive_cleanup_command参数来移除后备服务器不再需要的文件,这样可以最小化 WAL 归档的尺寸。pg_archivecleanup工具被特别设计为在典型单一后备配置下与`archive_cleanup_command`共同使用,见pg_archivecleanup。不过要注意,如果你正在为备份目的使用归档,有一些文件即使后备服务器不再需要你也需要保留它们,它们被用来从至少最后一个基础备份恢复。

配置的一个简单例子是:

primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass options=''-c wal_sender_timeout=5000'''
restore_command = 'cp /path/to/archive/%f %p'
archive_cleanup_command = 'pg_archivecleanup /path/to/archive %r'

你可以有任意数量的后备服务器,但是如果你使用流复制,确保你在主服务器上将`max_wal_senders`设置得足够高,这样可以允许它们能同时连接。

4.2.5. 流复制

流复制允许一台后备服务器比使用基于文件的日志传送更能保持为最新的状态。后备服务器连接到主服务器,主服务器则在 WAL 记录产生时即将它们以流式传送给后备服务器而不必等到 WAL 文件被填充。

默认情况下流复制是异步的,在这种情况下主服务器上提交一个事务与该变化在后备服务器上变得可见之间存在短暂的延迟。不过这种延迟比基于文件的日志传送方式中要小得多,在后备服务器的能力足以跟得上负载的前提下延迟通常低于一秒。在流复制中,不需要`archive_timeout`来缩减数据丢失窗口。

如果你使用的流复制没有基于文件的连续归档,该服务器可能在后备机收到 WAL 段之 前回收这些旧的 WAL 段。如果发生这种情况,后备机将需要重新从一个新的基础备 份初始化。通过设置`wal_keep_size`为一个足够高的值来确保旧 的 WAL 段不会被太早重用或者为后备机配置一个复制槽,可以避免发生这种情况。如 果设置了一个后备机可以访问的 WAL 归档,就不需要这些解决方案,因为该归档可以 为后备机保留足够的段,后备机总是可以使用该归档来追赶主控机。

要使用流复制,建立一个基于文件的日志传送后备服务器。将一个基于文件日志传送后备服务器转变成流复制后备服务器的步骤是把`recovery.conf`文件中的设置以指向主服务器。设置主服务器上的listen_addresses和认证选项(见`pg_hba.conf`),这样后备服务器可以连接到主服务器上的伪数据库`replication`。

在支持 keepalive 套接字选项的系统上,设置tcp_keepalives_idle、tcp_keepalives_interval和tcp_keepalives_count有助于主服务器迅速地注意到一个断开的连接。

设置来自后备服务器的并发连接的最大数目(详见max_wal_senders)。

当后备服务器被启动并且`primary_conninfo`被正确设置,后备服务器将在重放完归档中所有可用的 WAL 文件之后连接到主服务器。 如果连接被成功建立,你将在后备服务器中看到一个 walreceiver,并且在主服务器中有一个相应的 walsender 进程。

4.2.5.1. 认证

设置好用于复制的访问权限非常重要,这样只有受信的用户可以读取 WAL 流,因为很容易从 WAL 流中抽取出需要特权才能访问的信息。 后备服务器必须作为一个具有`REPLICATION`特权的账户或一个超级用户来向主服务器认证。 推荐为复制创建一个专用的具有`REPLICATION`和`LOGIN`特权的用户账户。 虽然`REPLICATION`特权给出了非常高的权限,但它不允许用户修改主系统上的任何数据,而`SUPERUSER`特权则可以。

复制的客户端认证由一个在*database*域中指定`replication`的`pg_hba.conf`记录控制。例如,如果后备服务器运行在主机 IP 192.168.1.100`并且用于复制的账户名为`foo,管理员可以在主服务器上向`pg_hba.conf`文件增加下列行:

# 允许来自 192.168.1.100 的用户 "foo" 在提供了正确的口令时作为一个
# 复制后备机连接到主控机。
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     foo             192.168.1.100/32        md5

主服务器的主机名和端口号、连接用户名和口令在primary_conninfo中指定。在后备服务器上还可以在`~/.pgpass`文件中设置口令(在*database*域中指定`replication`)。例如,如果主服务器运行在主机 IP 192.168.1.50、端口`5432`上,并且口令为`foopass`,管理员可以在后备服务器的`postgresql.conf`文件中增加下列行:

# 后备机要连接到的主控机运行在主机 192.168.1.50 上,
# 端口号是 5432,连接所用的用户名是 "foo",口令是 "foopass"。
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
4.2.5.2. 监控

流复制的一个重要健康指标是在主服务器上产生但还没有在后备服务器上应用的 WAL 记录数。你可以通过比较主服务器上的当前 WAL 写位置和后备服务器接收到的最后一个 WAL 位置来计算这个滞后量。这些位置分别可以用主服务器上的`pg_current_wal_lsn`和后备服务器上的`pg_last_wal_receive_lsn`来检索。后备服务器的最后 WAL 接收位置也被显示在 WAL 接收者进程的进程状态中,即使用`ps`命令显示的状态。

你可以通过pg_stat_replication视图检索 WAL 发送者进程的列表。 `pg_current_wal_lsn`与`sent_lsn`域之间的巨大差异表示主服务器承受着巨大的负载,而`sent_lsn`和后备服务器上`pg_last_wal_receive_lsn`之间的差异可能表示网络延迟或者后备服务器正承受着巨大的负载。

在一台热后备上,WAL接收者进程的状态可以通过pg_stat_wal_receiver`视图检索到。 `pg_last_wal_replay_lsn`和该视图的`flushed_lsn`的差别表示WAL的接收速度大于它被重放的速度。

4.2.6. 复制槽

复制槽提供了一种自动化的方法来确保主控机在所有的后备机收到 WAL 段 之前不会移除它们,并且主控机也不会移除可能导致恢复冲突的行,即使后备机断开也是如此。

作为复制槽的替代,也可以使用wal_keep_size阻止移除旧的 WAL 段,或者使用archive_command把段保存在一个归档中。 不过,这些方法常常会导致保留的 WAL 段比需要的更多,而复制槽只保留已知所需要的段。 另一方面,复制槽可以保留很多的WAL段以至于它们填满了分配给`pg_wal`的空间; max_slot_wal_keep_size限制复制槽所保留的WAL文件的大小。

类似地,hot_standby_feedback和 vacuum_defer_cleanup_age保护了相关行不被 vacuum 移除,但是前者在后备机断开期间无法提供保护,而后者则需要被设置为一个很高 的值已提供足够的保护。复制槽克服了这些缺点。

4.2.6.1. 查询和操纵复制槽

每个复制槽都有一个名字,名字可以包含小写字母、数字和下划线字符。

已有的复制槽和它们的状态可以在 pg_replication_slots视图中看到。

槽可以通过流复制协议 或者 SQL 函数创建并且移除。

4.2.6.2. 配置实例

你可以这样创建一个复制槽:

postgres=# SELECT * FROM pg_create_physical_replication_slot('node_a_slot');
  slot_name  | lsn
-------------+-----
 node_a_slot |

postgres=# SELECT slot_name, slot_type, active FROM pg_replication_slots;
  slot_name  | slot_type | active
-------------+-----------+--------
 node_a_slot | physical  | f
(1 row)

要配置后备机使用这个槽,在后备机中应该配置`primary_slot_name`。这里是一个简单的例子:

primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
primary_slot_name = 'node_a_slot'

4.2.7. 级联复制

级联复制特性允许一台后备服务器接收复制连接并且把 WAL 记录流式传送给其他后备服务器,就像一个转发器一样。这可以被用来减小对于主控机的直接连接数并且使得站点间的带宽开销最小化。

一台同时扮演着接收者和发送者角色的后备服务器被称为一台级联后备服务器。“更直接”(通过更少的级联后备服务器)连接到主控机的后备服务器被称为上游服务器,而那些离得更远的后备服务器被称为下游服务器。级联复制并没有对下游服务器的数量或布置设定限制。

一台级联后备服务器不仅仅发送从主控机接收到的 WAL 记录,还要发送那些从归档中恢复的记录。因此即使某些上游连接中的复制连接被中断,只要还有新的 WAL 记录可用,下游的流复制都会继续下去。

级联复制目前是异步的。同步复制设置当前对级联复制无影响。

不管在什么样的级联布置中,热备反馈都会向上游传播。

如果一台上游后备服务器被提升为新的主控机,且下游服务器的`recovery_target_timeline`被设置成’latest'`(默认),下游服务器将继续从新的主控机得到流。

要使用级联复制,要建立级联后备服务器让它能够接受复制连接(即设置max_wal_senders和hot_standby,并且配置基于主机的认证)。你还将需要设置下游后备服务器中的`primary_conninfo`指向级联后备服务器。

4.2.8. 同步复制

PostgreSQL流复制默认是异步的。如果主服务器崩溃,则某些已被提交的事务可能还没有被复制到后备服务器,这会导致数据丢失。数据的丢失量与故障转移时的复制延迟成比例。

同步复制能够保证一个事务的所有修改都能被传送到一台或者多台同步后备服务器。这扩大了由一次事务提交所提供的标准持久化级别。在计算机科学理论中这种保护级别被称为 2-safe 复制。而当`synchronous_commit`被设置为`remote_write`时,则是 group-1-safe (group-safe 和 1-safe)。

在请求同步复制时,一个写事务的每次提交将一直等待,直到收到一个确认表明该提交在主服务器和后备服务器上都已经被写入到磁盘上的预写式日志中。数据会被丢失的唯一可能性是主服务器和后备服务器在同一时间都崩溃。这可以提供更高级别的持久性,尽管只有系统管理员要关系两台服务器的放置和管理。等待确认提高了用户对于修改不会丢失的信心,但是同时也不必要地增加了对请求事务的响应时间。最小等待时间是在主服务器和后备服务器之间的来回时间。

只读事务和事务回滚不需要等待后备服务器的回复。子事务提交也不需要等待后备服务器的响应,只有顶层提交才需要等待。长时间运行的动作(如数据载入或索引构建)不会等待最后的提交消息。所有两阶段提交动作要求提交等待,包括预备和提交。

同步后备可以是物理复制后备或者是逻辑复制订阅者。它还可以是任何其他物理或者逻辑WAL复制流的消费者,它懂得如何发送恰当的反馈消息。除内建的物理和逻辑复制系统之外,还包括`pg_receivewal`和`pg_recvlogical`之类的特殊程序,以及一些第三方复制系统和定制程序。同步复制支持的细节请查看相应的文档。

4.2.8.1. 基本配置

一旦流复制已经被配置,配置同步复制就只需要一个额外的配置步骤:synchronous_standby_names必须被设置为一个非空值。synchronous_commit`也必须被设置为`on,但由于这是默认值,通常不需要改变。这样的配置将导致每一次提交都等待确认消息,以保证后备服务器已经将提交记录写入到持久化存储中。`synchronous_commit`可以由个体用户设置,因此它可以在配置文件中配置、可以为特定用户或数据库配置或者由应用动态配置,这样可以在一种每事务基础上控制持久性保证。

在一个提交记录已经在主服务器上被写入到磁盘后,WAL 记录接着被发送到后备服务器。每次一批新的 WAL 数据被写入到磁盘后,后备服务器会发送回复消息,除非在后备服务器上`wal_receiver_status_interval`被设置为零。如果`synchronous_commit`被设置为`remote_apply`,当提交记录被重放时后备服务器会发送回应消息,这会让该事务变得可见。如果根据主服务器的`synchronous_standby_names`设置选中该后备服务器作为一个同步后备,将会根据来自该后备服务器和其他同步后备的回应消息来决定何时释放正在等待确认提交记录被收到的事务。这些参数允许管理员指定哪些后备服务器应该是同步后备。注意同步复制的配置主要在主控机上。命名的后备服务器必须直接连接到主控机,主控机对使用级联复制的下游后备服务器一无所知。

将`synchronous_commit`设置为`remote_write`将导致每次提交都等待后备服务器已经接收提交记录并将它写出到其自身所在的操作系统的确认,但并非等待数据都被刷出到后备服务器上的磁盘。这种设置提供了比`on`要弱一点的持久性保障:在一次操作系统崩溃事件中后备服务器可能丢失数据,尽管它不是一次IvorySQL崩溃。不过,在实际中它是一种有用的设置,因为它可以减少事务的响应时间。只有当主服务器和后备服务器都崩溃并且主服务器的数据库同时被损坏的情况下,数据丢失才会发生。

把`synchronous_commit`设置为`remote_apply`将导致每一次提交都会等待,直到当前的同步后备服务器报告说它们已经重放了该事务,这样就会使该事务对用户查询可见。在简单的情况下,这为带有因果一致性的负载均衡留出了余地。

如果请求一次快速关闭,用户将停止等待。不过,在使用异步复制时,在所有未解决的 WAL 记录被传输到当前连接的后备服务器之前,服务器将不会完全关闭。

4.2.8.2. 多个同步后备

同步复制支持一个或者更多个同步后备服务器,事务将会等待,直到所有同步后备服务器都确认收到了它们的数据为止。事务必须等待其回复的同步后备的数量由`synchronous_standby_names`指定。这个参数还指定一个后备服务器名称及方法(FIRST`和`ANY)的列表来从列出的后备中选取同步后备。

方法`FIRST`指定一种基于优先的同步复制并且让事务提交等待,直到它们的WAL记录被复制到基于优先级选中的所要求数量的同步后备上为止。在列表中出现较早的后备被给予较高的优先级,并且将被考虑为同步后备。其他在这个列表中位置靠后的后备服务器表示可能的同步后备。如果任何当前的同步后备由于任何原因断开连接,它将立刻被下一个最高优先级的后备所替代。

基于优先的多同步后备的`synchronous_standby_names`示例为:

synchronous_standby_names = 'FIRST 2 (s1, s2, s3)'

在这个例子中,如果有四个后备服务器`s1`、s2、`s3`和`s4`在运行,两个后备服务器`s1`和`s2`将被选中为同步后备,因为它们出现在后备服务器名称列表的前部。`s3`是一个潜在的同步后备,当`s1`或`s2`中的任何一个失效, 它就会取而代之。`s4`则是一个异步后备因为它的名字不在列表中。

方法`ANY`指定一种基于规定数量的同步复制并且让事务提交等待,直到它们的WAL记录至少被复制到列表中所要求数量的同步后备上为止。

`synchronous_standby_names`的基于规定数量的多同步后备的例子:

synchronous_standby_names = 'ANY 2 (s1, s2, s3)'

在这个例子中,如果有四台后备服务器`s1`、s2、`s3`以及`s4`正在运行,事务提交将会等待来自至少其中任意两台后备服务器的回复。`s4`是一台异步后备,因为它的名字不在该列表中。

后备服务器的同步状态可以使用`pg_stat_replication`视图查看。

4.2.8.3. 性能规划

同步复制通常要求仔细地规划和放置后备服务器来保证应用能令人满意地工作。等待并不利用系统资源,但是事务锁会持续保持直到传输被确认。其结果是,不小心使用同步复制将由于响应时间增加以及较高的争用率而降低数据库应用的性能。

IvorySQL允许应用开发者通过复制来指定所要求的持久性级别。这可以为整个系统指定,不过它也能够为特定的用户或连接指定,甚至还可以为单个事务指定。

例如,一个应用的载荷的组成可能是这样:10% 的改变是重要的客户详情,而 90% 的改变是不太重要的数据,即使它们丢失业务也比较容易容忍(例如用户间的聊天消息)。

通过在应用级别(在主服务器上)指定的同步复制选项,我们可以为大部分重要的改变提供同步复制,并且不会拖慢整体的载荷。应用级别选项是使高性能应用享受同步复制的一种重要和实用的工具。

你应该认为网络带宽必须比 WAL 数据的产生率高。

4.2.8.4. 高可用性规划

当`synchronous_commit`被设置为`on`、`remote_apply`或者`remote_write`时, `synchronous_standby_names`指定产生的事务提交要等待其回应的同步后备的数量和名称。如果任一同步后备崩溃,这类事务提交可能无法完成。

高可用的最佳方案是确保有所要求数量的同步后备。这可以通过使用`synchronous_standby_names`指定多个潜在后备服务器来实现。

在基于优先的同步复制中,出现在该列表前部的后备服务器将被用作同步后备。后面的后备服务器将在当前同步后备服务器失效时取而代之。

在基于规定数量的同步复制中,所有出现在该列表中的后备服务器都将被用作同步后备的候选。即使其中的一个失效,其他后备仍将继续担任候选同步后备的角色。

当一台后备服务器第一次附加到主服务器时,它将处于一种还没有正确同步的状态。这被描述为`追赶`模式。一旦后备服务器和主服务器之间的迟滞第一次变成零,我们就来到了实时的`流式`状态。在后备服务器被创建之后的很长一段时间内可能都是追赶模式。如果后备服务器被关闭,则追赶周期将被增加,增加量由后备服务器被关闭的时间长度决定。只有当后备服务器到达`流式`状态后,它才能成为一台同步后备。这种状态可以使用`pg_stat_replication`视图查看。

如果在提交正在等待确认时主服务器重启,那些正在等待的事务将在主数据库恢复时被标记为完全提交。没有办法确认所有后备服务器已经收到了在主服务器崩溃时所有还未处理的 WAL 数据。某些事务可能不会在后备服务器上显示为已提交,即使它们在主服务器上显示为已提交。我们提供的保证是:在 WAL 数据已经被所有后备服务器安全地收到之前,应用将不会收到一个事务成功提交的显式确认。

如果实在无法保持所要求数量的同步后备,那么应该减少`synchronous_standby_names`中指定的事务提交应该等待其回应的同步后备的数量(或者禁用),并且在主服务器上重载配置文件。

如果主服务器与剩下的后备服务器是隔离的,你应当故障转移到那些其他剩余后备服务器中的最佳候选者上。

如果在事务等待时你需要重建一台后备服务器,确保命令 pg_start_backup() 和 pg_stop_backup() 被运行在一个`synchronous_commit` = `off`的会话中,否则那些请求将永远等待后备服务器出现。

4.2.9. 在后备机上连续归档

当在一个后备机上使用连续归档时,有两种不同的情景:WAL 归档在主服务器 和后备机之间共享,或者后备机有自己的 WAL 归档。当后备机拥有其自身的 WAL 归档时,将`archive_mode`设置为 always,后备机将在收到每个 WAL 段时调用归档命令, 不管它是从归档恢复还是使用流复制恢复。共享归档可以类似地处理,但是 `archive_command`必须测试要被归档的文件是否 已经存在,以及现有的文件是否有相同的内容。这要求 `archive_command`中有更多处理,因为它必须当心 不要覆盖具有不同内容的已有文件,但是如果完全相同的文件被归档两次时 应返回成功。并且如果两个服务器尝试同时归档同一个文件,所有这些都必须 在没有竞争情况的前提下完成。

如果`archive_mode`被设置为`on`,归档器在恢复或者后备模式中无法启用。 如果后备服务器被提升,它将在被提升后开始归档,但是它将不会归档任何不是它自身产生的 WAL或时间线历史文件。 要在归档中得到完整的一系列 WAL 文件,你必须确保所有 WAL 在到达后备机之前都被归档。 对于基于文件的日志传输来说天然就是这样,因为后备机只能恢复在归档中找到的文件,而启用了流复制时则不是这样。 当一台服务器不在恢复模式中时,在`on`和`always`模式之间没有差别。

4.3. 故障转移

如果主服务器失效,则后备服务器应该开始故障转移过程。

如果后备服务器失效,则不会有故障转移发生。如果后备服务器可以被重启(即使晚一点),由于可重启恢复的优势,那么恢复处理也能被立即重启。如果后备服务器不能被重启,则一个全新的后备服务器实例应该被创建。

如果主服务器失效并且后备服务器成为了新的主服务器,那么接下来旧的主服务器重启后,你必须有一种机制来通知旧的主服务器不再成为主服务器。有些时候这被称为STONITH(Shoot The Other Node In The Head,关闭其他节点),这对于避免出现两个系统都认为它们是主服务器的情况非常必要,那种情况将导致混乱并且最终导致数据丢失。

很多故障转移系统仅使用两个系统,主系统和后备系统,它们由某种心跳机制连接来持续验证两者之间的连接性和主系统的可用性。也可能会使用第三个系统(称为目击者服务器)来防止某些不当故障转移的情况,但是除非非常小心地建立它并且经过了严格地测试,额外的复杂度可能会使该工作得不偿失。

IvorySQL并不提供在主服务器上标识失败并且通知后备数据库服务器所需的系统软件。现在已有很多这样的工具并且很好地与成功的故障转移所需的操作系统功能整合在一起,例如 IP 地址迁移。

一旦发生到后备服务器的故障转移,就只有单一的一台服务器在操作。这被称为一种退化状态。之前的后备服务器现在是主服务器,但之前的主服务器处于关闭并且可能一直保持关闭。要回到正常的操作,一个后备服务器必须被重建,要么在之前的主系统起来时使用它重建,要么使用第三台(可能是全新的)服务器来重建。在大型集簇上,pg_rewind功能可以被用来加速这个过程。一旦完成,主服务器和后备服务器可以被认为是互换了角色。某些人选择使用第三台服务器来为新的主服务器提供备份,直到新的后备服务器被重建,不过显然这会使得系统配置和操作处理更复杂。

因此,从主服务器切换到后备服务器可以很快,但是要求一些时间来重新准备故障转移集群。从主服务器到后备服务器的常规切换是有用的,因为它允许每个系统有常规的关闭时间来进行维护。这也可以作为一种对故障转移机制的测试,以保证在你需要它时它真地能够工作。我们推荐写一些管理过程来做这些事情。

要触发一台日志传送后备服务器的故障转移,运行`pg_ctl promote`,调用 pg_promote(),或者创建一个触发器文件,其文件名和路径由`promote_trigger_file`设置指定。 如果你正在规划使用`pg_ctl promote`或调用`pg_promote()`以进行故障转移,`promote_trigger_file`就不是必要的。 如果你正在建立只用于从主服务器分流只读查询而不是高可用性目的的报告服务器,你不需要提升它。

4.4. 热备

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

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

4.4.1. 用户概览

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

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

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

  • 查询访问: 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

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

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

用户可以通过`SHOW in_hot_standby`来检查hot standby会话是否是活跃的 (在服务器版本 14 之前该参数`in_hot_standby`不存在。对于更早版本的服务器,可行的替代方法是 SHOW transaction_read_only。) 此外, 还有一些函数允许用户访问有关备用服务器的信息。 它们允许您编写程序来识别数据库当前的状态。用于监控恢复进度, 或者您可以编写复杂的程序将数据库恢复到特定状态。

4.4.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_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`系统视图也包含汇总信息。

当 WAL 重放由于冲突而需要比`deadlock_timeout`更长时间时,用户可以控制是否打印日志消息。由参数 log_recovery_conflict_waits控制。

4.4.3. 管理员概览

如果`hot_standby`在`postgresql.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`设置一样长。

设置几个参数可确定用于跟踪事务ID、锁和预备事务的共享内存大小。备用服务器上的设置必须大于或等于主服务器上的设置,以确保在恢复过程中不会耗尽共享内存。例如,如果主数据库正在执行预备事务,而备用数据库没有获取共享内存来跟踪预备事务,则备用数据库将无法继续恢复,直到配置更改。受影响的参数是:

  • max_connections

  • max_prepared_transactions

  • max_locks_per_transaction

  • max_wal_senders

  • max_worker_processes

确保这不是问题的可靠方法是使备用数据库上的这些参数的值等于或大于主数据库上的值。因此,如果您想增加这些值,您应该先更改备用服务器上的设置,然后再更改主服务器上的设置。相反,如果要减小这些值,则应先更改主服务器上的设置,然后再更改备用服务器上的设置。请记住,当一个备用数据库被提升时,它会成为后续备用数据库所需参数设置的新基准。因此,最好在所有备用服务器上保持这些设置相同,这样在切换/故障转移期间就不会出现问题。

WAL 跟踪主节点上这些参数的变化。如果热备处理一个 WAL,表明主节点当前值大于备用数据库上的值,它将记录一个警告并中止恢复。例如:

WARNING:由于参数设置不足,无法进行热备
详细信息:max_connections = 80 的设置低于主服务器上的设置,其值为 100。
LOG:恢复已暂停
详细信息:如果恢复未暂停,服务器将关闭。
提示:您可以在进行必要的配置更改后重新启动服务器。

此时,您应该更改备库设置并重新启动实例以继续恢复。如果备库不是热备,不兼容的参数更改将立即将其关闭而不会暂停。因为这样继续开机没有意义。

管理员为max_standby_archive_delay和max_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_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 列上的递增。在恢复的开始 stats 文件会被删除,因此来自主服务器和后备服务器的 stats 将不同;这被认为是一种特性而不是缺陷。

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

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

4.4.4. 热备参数参考

在主服务器上,可以使用参数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就没有效果,然而当后备服务器变成主服务器时它将变得相关。

4.4.5. 警告

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

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

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

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

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