KingbaseES V8R6 流复制冲突分类以及对应解决方案
背景
据实施人员反馈发现如下报错:
FATAL: terminating connection due to conflict with recovery
DETAIL: User query might have needed to see row versions that must be removed.
HINT: In a moment you should be able to reconnect to the database and repeat your command.
很明显报错说明发生了查询冲突现象,下面来讲解并分析一下流复制冲突以及如何避免。
提前说明:一切避免冲突的手段都可能导致主节点的垃圾回收做无用功, 费劲IO和CPU却不回收垃圾.
什么是复制冲突?
每当恢复过程无法将 WAL 信息从主服务器应用到备用服务器时,就会发生复制冲突,因为主库的更改会造成备库查询中断。
复制冲突类型
快照复制冲突
这是最常见的复制冲突。基于mvcc特性。
如果主库VACUUM 处理表并删除死元组,则可能发生快照冲突。此删除将在备用服务器上replay。现在备用数据库上的查询可能在主数据库上的 VACUUM 之前开始(它有一个较旧的快照),所以它仍然可以看到应该删除的元组。这构成了快照冲突。
锁复制冲突
备用服务器上的查询在它们正在读取的表上使用ACCESS SHARE锁。因此,必须在备用数据库上重放主数据库上的任何 ACCESS EXCLUSIVE 锁(与 ACCESS SHARE 冲突),例如 DROP TABLE、TRUNCATE 和许多 ALTER TABLE 等语句。如果备用数据库应该在查询使用的表上重放这样的锁,我们就会发生锁冲突。
buffer pin复制冲突
减少对 VACUUM 需求的一种方法是使用 HOT 更新。然后在主节点上的任何查询访问具有仅死堆的页面并且可以获得排他锁将修剪HOT链。(查询修改时页面,缩短热链。加轻量级锁)数据库总是持有这样的页面锁定时间很短,因此与主节点上的处理没有冲突。页面锁定还有其他原因,但这可能是最常见的原因。
When the standby server should replay such an exclusive page lock and a query is using the page (“has the page pinned”), you get a buffer pin replication conflict. Pages can be pinned for a while, for example during a sequential scan of a table on the outer side of a nested loop join. (当从库上有nest loop join, 并且外表是全表扫描, 而且刚好这个外表有prune HOT chains的wal replay时, 这个replay可能长时间等待).
HOT chain pruning can of course also lead to snapshot replication conflicts. (HOT chain pruning也会导致snapshot 冲突)
罕见的复制冲突
- 死锁复制冲突:在使用从主数据库重放 WAL 所需的共享缓冲区时对备用块的查询。数据库会立即取消这样的查询。
- 表空间复制冲突:一个表空间在备用服务器上的 temp_tablespaces 中,并且一个查询在那里有临时文件。当主数据库发生 DROP TABLESPACE 时,我们会遇到冲突。在这种情况下,数据库会取消备用数据库上的所有查询。
- 数据库复制冲突:如果备用数据库在数据库上有活动会话,则 DROP DATABASE 的复制会导致冲突。在这种情况下,数据库会终止与备用数据库的所有连接。
监控复制冲突
统计视图 pg_stat_database_conflicts 包含自上次统计重置以来发生的所有复制冲突的详细说明。必须在备用服务器而不是主服务器上查看该视图,因为那是发生复制冲突的地方。
请注意,此视图不会显示发生的所有复制冲突,它仅显示导致在备用数据库上取消查询的那些。
避免流复制冲突
避免所有冲突
显然,如果备库上没有查询,就不会有复制冲突。因此,可以在备库上设置 hot_standby=off,则不会发生冲突问题。
但是,只有备库专门用于高可用性才可以使用上面的配置,如果用于读写分离场景,这样做就违背初衷了。
避免快照冲突
减少此类冲突的方法是防止主库删除备库上仍然可见的死元组。可以通过以下两个参数实现:
- 将主库上的 hot_standby_feedback 设置为on。它阻止
VACUUM
移除最近死亡的元组,就有可能导致主库上的表膨胀。默认情况下,没有启用该设置。 - 将主库上 vacuum_defer_cleanup_age 参数设置为大于0的值。VACUUM将不会立即清除死元组,除非超过了 vacuum_defer_cleanup_age 指定的事务数据的旧值。还不如 hot_standby_feedback 那么具体明确,还可能导致表膨胀。
hot_standby_feedback = on 将消除大多数快照复制冲突,但不一定消除buffer pin冲突。
避免锁冲突
避免锁冲突的明显措施是不发出对表使用ACCESS EXCLUSIVE锁的语句。例如:DROP TABLE、TRUNCATE、LOCK、DROP INDEX、DROP TRIGGER、ALTER TABLE。
但是有一种ACCESS排他锁是无法通过这种方式避免的:来自VACUUM截断的锁。当VACUUM完成了对表的处理,并且表末尾的页变为空时,它会尝试在表上获得一个短的ACCESS EXCLUSIVE锁。如果成功,它将截断空页并立即释放锁。虽然这类锁不会中断主库上的处理,但它们可能导致备库上的复制冲突
避免VACUUM截断的方法
- 从KingbaseESV8R6开始,可以针对单个表的禁用此功能
ALTER TABLE test SET(vacuum_truncate = off);
- 将主库上的old_snapshot_threshold设置为-1以外的值。这会禁用VACUUM截断,这是未被记录的副作用。
避免buffer pin冲突
没有很好的方法可以避免这些冲突。也许可以减少 HOT 更新的数量,但这会损害主节点的性能。
解决复制延迟的代价
当hot_standby_feedback=on。
如果备库出现了LONG QUERY,或者Repeatable Read的长事务,并且主库对备库还需要或正查询的数据执行了更新并产生了垃圾时,主库会保留这部分垃圾版本(与vacuum_defer_cleanup_age参数效果类似)。
代价1,主库对应对象膨胀,因为垃圾版本要延迟若干个事务后才能被回收。
代价2,重复扫描垃圾版本,重复耗费垃圾回收进程的CPU和IO资源。(n_dead_tup会一直处于超过垃圾回收阈值的状态,从而autovacuum 不断唤醒worker进行回收动作)。
当主库的 autovacuum_naptime=很小,同时autovacuum_vacuum_scale_factor=很小时,尤为明显。
代价3,如果期间发生大量垃圾,垃圾版本可能会在事务到达并解禁后,爆炸性的被回收,产生大量的WAL日志,从而造成WAL的IO峰值。
当设置参数max_standby_archive_delay, max_standby_streaming_delay时,
代价,如果备库的QUERY与APPLY(恢复进程)冲突,那么备库的apply wal会出现延迟,也许从备库读到的是很久以前的数据,导致备库的数据过于陈旧。
请注意,如果备用服务器在应用 WAL 方面落后,则您无需在故障转移期间丢失更多数据——WAL 信息仍会流式传输到备用服务器并写入 sys_wal。但是备用数据库需要更长的时间才能追平,因此有故障转移时间会增加的风险。
结论
对于读写分离的集群模式架构,想解决复制冲突,
则必须调整 hot_standby_feedback、max_standby_streaming_delay 和 Vacuum_truncate 存储参数,以尽可能少地取消查询,同时避免过度的表膨胀和长时间的复制延迟。
解决流复制会造成表膨胀,所谓鱼与熊掌不可兼得。如果可以和业务协调,尽量不要把备库的查询和主库的DDL语句放同一时间段执行。