暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PGD:理解冲突及其解决机制

新智锦绣 2025-07-25
110

点击蓝字关注我们



背景


PGD 用户通常会在集群中使用 write-leader(写领导者),这是由 Raft 选举出的节点,负责向数据库写入。即使为每个区域或子组都配置了写领导者,每个写领导者也只会写入不同的schema或不同的表。当崩溃或网络分区导致写领导者从一个节点切换到另一个节点时,就可能发生冲突。因此,冲突本不应是常见现象。如果频繁发生冲突,说明 PGD 的使用方式存在问题。

冲突有默认的解决机制,也可以进行修改。PGD 文档中已提供了冲突的简明完整总结。冲突处理包含两个方面:

  • 检测(Detection)

  • 解决(Resolution)

本文的目标用例子把最常见的几种冲突讲清楚:它们怎么被检测到、默认怎么解决。自定义解决方式请参考官方文档。(https://www.enterprisedb.com/docs/pgd/latest/reference/conflicts/)


冲突类型


insert_exists


场景

两个节点都想插入“同一行”。只有这一行存在唯一约束时,系统才知道它们是“同一行”。

  • 有唯一约束 → 保留时间戳较新的那条插入。

  • 没有唯一约束 → 不冲突,两条都插进去。

时间戳来自“最初提交这条事务的那个节点”。 如果时间差很大且节点时钟不同步,可能出现“新数据被旧数据覆盖”,但所有节点都会得到同一个结果,不会分叉。


update_origin_change


这是最常见的冲突,并不表示存在问题。但理解它可能带来的影响非常重要。

定义如下:一个传入的更新正在修改一个由不同节点最后更改的行。源节点的变更意味着该行之前的版本是由不同的源节点添加的。这可能是冲突的指示。

这种情况会出现在具有一个或多个唯一约束(可能包括主键)的表上。写领导者的变更可能导致这种冲突。以下示例展示了这种情况:

  • node1 和 node2 几乎同时拿到事务 T1、T2。

  • node2 先提交 T2 → 本地余额变成 1200。

  • node1 恢复后把 T1 同步过来 → 余额 1100。

系统发现:本地这条记录上次是 node2 写的,现在却要被 node1 覆盖 → 触发冲突。

是不是真冲突?

PGD 会猜:如果 T1 在 node1 提交前,已经见过 node2 的 1200 版本,那就不是冲突;否则就算冲突。

猜不准就按冲突处理:时间戳大的赢。于是 1200 留下,1100 丢失——update lost。

所有节点都会选 1200,所以不会分叉。

时钟跑偏会怎样?

如果 node1 的时钟比 node2 快,就会出现“来自未来”的事务先到,导致两边各自保留不同值 → 数据分叉


update_missing


场景 1:更新和插入交叉

  1. node1 插入记录 A 后崩溃,还没来得及同步。

  2. node2 成为新写领导者,把 A 改成 A1。

  3. node3 先收到 A1 更新没收到 A 插入 → 找不到行 → 触发 update_missing。

    默认策略:apply_remote,直接按 A1 做 upsert。

  4. 随后 node3 收到迟到的 A 插入 → 又触发 insert_exists,用时间戳再比一次。

  5. 如果时钟不准,可能出现“旧插入覆盖新更新” → 分叉。

场景 2:更新和删除交叉

  1. node1 删除记录 A。

  2. node2 并发更新 A → 本地成功(因为还没收到删除)。

  3. node1 收到更新 → 发现行已删:

    如果能找到删除标记 → 记为 update_recently_deleted,skip跳过。

    如果删除已被 vacuum 清理 → 找不到行 → 变成 update_missing,直接 apply_remote 重新插入。

  4. node2 收到删除 → 本地把 A 删掉。

    → 结果 node1 有 A,node2 没有 → 分叉。

场景 3:表上没有唯一约束,使用 REPLICA IDENTITY FULL

- 两行同时被不同节点更新,下游拿到旧/新两个版本。
- 找不到旧版本 → 记为 update_missing,两条更新都会被应用 → 行会重复。


delete_recently_updated


这与 update_recently_deleted 冲突类似,删除时间戳比更新旧 → 直接跳过删除,保留更新。


update_pkey_exists


当存在主键更新,且正在设置的新主键在副本上已存在时,会发生此冲突。当两个这样的更新在两个节点上交叉时,可能会发生这种情况。在这种情况下,选择最后写入者胜出(last-writer-wins)策略,时间戳较新的更新胜出。因此保留时间戳较新的更新。


delete_missing


当两个节点同时删除一行时,会出现此冲突。当事务复制时找不到该行。解决方法为 skip,即忽略此情况。


multiple_unique_conflicts


这是由于插入与更新冲突,且与主键以外的索引发生冲突导致的。如果只是主键冲突,则冲突归类为 update_pkey_exists。如果两个更新冲突,则冲突归类为 update_origin_change。这种冲突的挑战在于,冲突语句并未引用同一行,这与其他所有冲突类型不同。因此,无法像其他冲突类型那样做出丢弃一个或另一个的决定。解决方法相当棘手,因此写操作会报错,并提供有关冲突的信息,以便用户手动解决。考虑以下示例。


如何冲突避免


由于时钟偏差导致的冲突可以通过以下技术之一避免:

  • 不允许节点应用来自另一个节点的未来提交。这可能以牺牲一些性能为代价。本质上,我们会让具有未来时间戳的事务等待,直到它被应用。以下设置可以实现这一点:拒绝来自未来的提交

    ALTER SYSTEM SET bdr.maximum_clock_skew = 0;
    ALTER SYSTEM SET bdr.maximum_clock_skew_action = 'wait';
    SELECT pg_reload_conf();
    • 数据分歧通常只会在时钟偏差大于写领导者变更所需时间时发生,因为两次更新的提交时间戳至少会相差这么多。通过监控 PGD 节点上的时钟偏差,可以确保不会发生这种情况。

    • 将行版本冲突检测与 REPLICA IDENTITY FULL 结合使用可以避免这种情况。这样,时钟偏差不会导致数据分歧,冲突检测也不会出现误报。解决方法继续使用最后更新胜出策略。因此可能会出现更新丢失。

    • 此外,遵循以下准则有助于减少 PGD 中的冲突。

      • 业务幂等

      • 单点写入

      • 合理切主策略

      • 及时清理但别急着 vacuum 被删记录(留墓碑)


    冲突与解决列表汇总



    关于公司

    感谢您关注新智锦绣科技(北京)有限公司!作为 Elastic 的 Elite 合作伙伴及 EnterpriseDB 在国内的唯一代理和服务合作伙伴,我们始终致力于技术创新和优质服务,帮助企业客户实现数据平台的高效构建与智能化管理。无论您是关注 Elastic 生态系统,还是需要 EnterpriseDB 的支持,我们都将为您提供专业的技术支持和量身定制的解决方案。


    欢迎关注我们,获取更多技术资讯和数字化转型方案,共创美好未来!

    Elastic 微信群

    EDB 微信群


    发现“分享”“赞”了吗,戳我看看吧


    文章转载自新智锦绣,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论