PG-处理流复制冲突

流复制冲突如何产生

流复制冲突发生在备库允许只读模式的情况下,而主库在不断产生wal日志(redo data)的过程中,传输这些wal日志到备库后,apply wal时可能会与备库的query产生冲突。注意流复制冲突不会发生在主库上。

简单点来说,就是备库查询(备库query)与恢复(备库apply)冲突。

每当恢复进程无法在备库apply从主库传递过来的 wal 时,就发生流复制冲突

冲突类型

常见类型

快照复制冲突

如果VACUUM处理一张表并删除了死亡元组(dead tuple),就有可能发生快照冲突。该删除将在备库上重放(replay),备库上的查询可能已经在主库上的VACUUM操作命令之前启动(它有一个旧的快照),备库上的查询操作仍然可以看到应该删除的元组。这就会产生快照冲突。

主库上回收的dead tuple的WAL传递到备库,同时,备库当前的一致性查询需要这些记录。

锁复制冲突

备库上正在查询的表上获得了ACCESS SHARE锁,而此刻必须回放主库WAL上的任何获取ACCESS EXCLUSIVE锁的操作(如:DROP TABLE,TRUNCATE和大多数ALTER TABLE等),就会发生锁冲突。

例如:主库truncate a表, 备库查询a表

Buffer pin复制冲突

如果访问了一个只有死堆的元组的页面,并且可以获得该页面上的排他锁,就会删除该HOT链表。

任何在主库上访问仅包含死堆元组的页面并且获取到了该页面的(exclusive)排它锁的查询都会删除HOT链,PostgreSQL总是在很短的时间内持有这种页面锁,因此,不会与主库上的vacuum操作发生冲突。但是,当备库在回放此类排它页面锁的wal数据同时有查询正在访问该页面数据,则会发生 buffer pin 复制冲突。

Hot prun(剪枝)也会造成快照复制冲突

延申知识点

PG 实现的MVCC多版本读,所有的update操作都是通过插入一个新版本行同时将旧版本的行标识未失效的方式实现的。数据行的各个版本从老到新形成一个单向链表结构,即update chain

大量的数据更新行为会导致索引和数据行空间膨胀问题。在PostgreSQL 8.3开始引入HOT技术优化update效率低下的问题。

HOT(堆内元组) 更新

HOT(Heap-Only Tuple) 更新:在update更新时,不在index上新增条目(entry),而是通过数据块中老记录指向同块中的新纪录方式,来减少IO读写操作。仅当更新的行新版本和旧版本位于同一个数据块,需要足够的空间存储产生的新版本行

罕见复制冲突类型

死锁复制冲突

在备库上查询使用到了回放WAL所需的共享缓冲区。PostgreSQL将立刻取消此类操作

表空间复制冲突

当主库删除表空间时,备库上的一个大查询也在该表空间写临时文件,会发生冲突。在这种情况下,PostgreSQL会取消备库上的所有查询。

数据库复制冲突

主库执行删除数据库,而备库刚好也有活动会话在连接该数据库时,导致冲突。在这种情况下,PostgreSQL会终止备用数据库上的的所有连接。

备库如何解决复制冲突

PG中的参数max_standby_streaming_delay决定了当wal重放遇到复制冲突时行为。此参数定义了当遇到复制冲突,WAL 应用中的最大允许延迟,如果冲突的查询在max_standby_streaming_delay指定的时间之后仍然在运行,PostgreSQL将强行取消查询。此时会给出如下信息:

ERROR:  canceling statement due to conflict with recoveryDETAIL:  User query might have needed to see row versions that must be removed.1.

max_standby_streaming_delay默认值是30秒,0表示立即取消查询,没有重放延迟。-1表示pg从不取消查询,允许任意长时间的重放延迟。

监控流复制冲突

pg_stat_database_conflicts 视图包含自上次统计重置以来发生的所有复制冲突的详细信息。

在备库上查询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截断的方法

  1. 从PostgreSQL v12开始,可以针对单个表的禁用此功能

    ALTER TABLE tb1 SET(vacuum_truncate = off);
    
  2. 将主库上的old_snapshot_threshold设置为-1以外的值。这会禁用VACUUM截断,这是未被记录的副作用。

原文地址:https://www.cnblogs.com/binliubiao/p/15345694.html