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

[译] 在 PostgreSQL数据库中使用 ALTER SUBSCRIPTION SKIP 解决逻辑复制冲突

原创 黎青峰 2022-06-28
453

原文地址:Addressing logical replication conflicts using ALTER SUBSCRIPTION SKIP
原文作者:Takamichi Osumi

在这篇博文中,我通过解释如何使用 ALTER SUBSCRIPTION SKIP 命令,来结束对 PostgreSQL 中处理逻辑复制冲突的新机制的讨论。

在我之前的博文中,我写过关于逻辑复制冲突的文章,并介绍了一种机制来避免它 - pg_replication_origin_advance

此功能具有一组特定于冲突处理的内部保护措施和检查。因此,我们没有在冲突中跳过非失败交易的风险。

在这篇文章中,我将解释另一种处理冲突的方法,使用 ALTER SUBSCRIPTION SKIP 命令。

在我们开始之前

我之前的博客文章已经描述了逻辑复制冲突是什么,它是如何发生的,以及社区开发的PostgreSQL 15 新特性对于解决它有多么有用。与此同时,针对处理冲突进行了单独的讨论。OSS 社区积极追求这个项目的完成,我们在PostgreSQL代码库中成功构建了 ALTER SUBSCRIPTION SKIP 特性1 。正如文档所阐明的,此功能可以跳过应用远程事务的所有更改。这听起来可能类似于 pg_replication_origin_advance 的功能。因此,在本博客中,我想重点关注 ALTER SUBSCRIPTION SKIP 以公开差异。

在那篇文章中,我解释了更多背景和相关的新功能。如果您还没有,不妨先看看这篇文章。我将 ALTER SUBSCRIPTION SKIP 应用到与上一篇文章中用于 pg_replication_origin_advance 的场景类似的场景。

免责声明:请注意,该作品尚未发布,社区可以决定更改其设计或完全还原它。

ALTER SUBSCRIPTION SKIP 命令
此命令的目的是跳过在订户上的指定 LSN 处完成的冲突事务。用户可以通过 ‘lsn’ skip_option 指定完成 LSN 以指示失败的事务。应用工作者从发布者接收更改并判断是否应该在节点上跳过这些更改。对于这个命令输入,我们可以直接使用错误上下文的完成 LSN。因此,不需要在服务器日志上编辑导出的 LSN,这与 pg_replication_origin_advance 的参数不同。从这个意义上说,使用 ALTER SUBSCRIPTION SKIP 更简单。我们将在下面看一个演示。

通过跳过事务观察冲突和解决方案(ALTER SUBSCRIPTION SKIP 命令)

  • 1在发布者端,创建一个表和一个发布。
postgres=# CREATE TABLE tab (id integer);
CREATE TABLE
postgres=# INSERT INTO tab VALUES (5);
INSERT 0 1
postgres=# CREATE PUBLICATION mypub FOR TABLE tab;
CREATE PUBLICATION

我们现在有一个用于初始表同步的记录。

  • 2在订阅者端,创建一个具有唯一约束和订阅的表。

我们创建了一个启用了 disable_on_error 的订阅。此订阅定义将导致在后台进行初始表同步,这将成功而没有任何问题。

postgres=# CREATE TABLE tab (id integer UNIQUE);
CREATE TABLE
postgres=# CREATE SUBSCRIPTION mysub CONNECTION '…' PUBLICATION mypub WITH (disable_on_error = true);
NOTICE: created replication slot "mysub" on publisher
CREATE SUBSCRIPTION

结果,值 5 将被插入到订阅者中。

  • 3在发布端,表同步后连续执行三个事务。
postgres=# BEGIN; -- Txn1
BEGIN
postgres=*# INSERT INTO tab VALUES (1);
INSERT 0 1
postgres=*# COMMIT;
COMMIT
postgres=# BEGIN; -- Txn2
BEGIN
postgres=*# INSERT INTO tab VALUES (generate_series(2, 8));
INSERT 0 7
postgres=*# COMMIT;
COMMIT
postgres=# BEGIN; -- Txn3
BEGIN
postgres=*# INSERT INTO tab VALUES (9);
INSERT 0 1
postgres=*# COMMIT;
COMMIT
postgres=# SELECT * FROM tab;
id
----
5
1
2
3
4
5
6
7
8
9
(10 rows)

在发布者上,我们可以成功执行这些事务。但是,Txn2 包含重复数据,该数据在初始表同步时已插入。因此,在订阅者上,这违反了表的唯一约束并引发错误。

  • 4在订阅方,检查当前复制状态。
postgres=# SELECT subname, subenabled FROM pg_subscription;
 subname | subenabled
---------+------------
 mysub   | f
(1 row)

postgres=# SELECT * FROM tab;
id
----
5
1
(2 rows)

现在,我们将看看订阅者的当前状态。根据 disable_on_error 选项2的行为,订阅被错误自动禁用。只有 Txn1(以及由初始表同步复制的数据)已在订阅服务器上复制。这里我们看不到 Txn2 和 Txn3 的结果,因为有冲突。Txn3 只有在我们解决冲突后才会重播。

  • 5 在订阅者的日志中,我们看到了此冲突的错误消息和 disable_on_error 选项的日志。
ERROR: duplicate key value violates unique constraint "tab_id_key"
DETAIL: Key (id)=(5) already exists.
CONTEXT: processing remote data for replication origin "pg_16389" during "INSERT" for replication target relation "public.tab" in transaction 730 finished at 0/1566D10
LOG: logical replication subscription "mysub" has been disabled due to an error

错误:重复键值违反唯一约束“tab_id_key”
详细信息:键 (id)=(5) 已存在。上下文:在事务 730 中的复制目标关系“public.tab”的“INSERT”期间
处理复制源“ pg_16389 ”的远程数据完成于0/1566D10
日志:由于错误,逻辑复制订阅“mysub”已被禁用

在订阅者的服务器日志中,我们可以获得 ALTER SUBSCRIPTION SKIP 命令参数的完成 LSN。我将利用这个完成 LSN 来跳过 Txn2,如下所示。

  • 6在订阅者端,执行 ALTER SUBSCRIPTION SKIP 命令并启用订阅。
postgres=# ALTER SUBSCRIPTION mysub SKIP (lsn = '0/1566D10');
ALTER SUBSCRIPTION
postgres=# SELECT subname, subskiplsn, subenabled FROM pg_subscription;
 subname | subskiplsn | subenabled
---------+------------+------------
 mysub   |  0/1566D10 | f
(1 row)

postgres=# ALTER SUBSCRIPTION mysub ENABLE;
ALTER SUBSCRIPTION
postgres=# SELECT * FROM tab;
 id
----
 5
 1
 9
(3 rows)

postgres=# SELECT subname, subskiplsn, subenabled FROM pg_subscription;
 subname | subskiplsn | subenabled
---------+------------+------------
 mysub   |  0/0   | t
(1 row)

在这里,我设置了跳过 LSN 并重新激活了订阅。立即,我们看到 Txn3 的复制值,没有 Txn2 的值。我们跳过了整个交易。与此同时,在我们成功跳过更改后,subskiplsn 已被清除。

  • 7在订阅者日志中,我们可以看到命令的成功完成日志消息。
LOG: start skipping logical replication transaction finished at 0/1566D10
CONTEXT: processing remote data for replication origin "pg_16389" during "BEGIN" in transaction 730 finished at 0/1566D10
LOG: done skipping logical replication transaction finished at 0/1566D10
CONTEXT: processing remote data for replication origin "pg_16389" during "COMMIT" in transaction 730 finished at 0/1566D10

我们也可以从服务器日志中看到成功的日志。

ALTER SUBSCRIPTION SKIP 的高级保护措施

此功能具有一组特定于冲突处理的内部保护措施和检查。因此,我们没有在冲突中跳过非失败交易的风险。

将此与我在上一篇博文中解释的滥用 pg_replication_origin_advance 的示例进行比较,以进行比较。在那里,我谈到了这个功能可以跳过不相关的成功交易的一个方面。但是,此 ALTER SUBSCRIPTION SKIP 功能不会发生这种情况。我们通过下面的一些内部检查来实现这一点。

首先,指定的 LSN 必须大于原始 LSN,原始 LSN 指向已在订阅者上复制数据的位置。

此外,此功能要求参数的完成 LSN 与订阅者从发布者获得的第一个事务的完成 LSN 之间的精确匹配。否则(启用订阅后),PostgreSQL 将尝试再次应用失败的事务并立即导致相同的冲突。这在 PostgreSQL 15 中不会发生,因为错误消息的改进,现在包括完成 LSN。由于正确的完成 LSN 可用,我们只需要指定它。

此外,当我们设置一个没有冲突的跳过 LSN 并成功应用事务时,跳过 LSN 将被清除并显示警告消息。这是有道理的,因为传入的事务被证明是没有问题的,并且用户可能错误地设置了 LSN。当发生新的冲突时,可以在另一个适当的时间再次设置跳过LSN,并且用户决定采用该措施。

如果您对内部工作的详细信息感兴趣,可以查看提交日志3。

总结

为了处理逻辑复制冲突,社区投入了大量时间和精力来创建这种新的便捷方法。很高兴我们可以选择享受更简单、更安全的方法。

此外,已经提出了一些想法来从社区的各个方面增强此功能。例如,有一个想法是在 ALTER SUBSCRIPTION SKIP 命令中添加其他类型的 skip_option,例如指示要跳过的关系或命令(INSERT、DELETE、…)。另一个提议的想法是将与错误相关的信息(例如完成的 LSN)存储到系统目录中,或者将订阅者跳过的数据记录在服务器日志或表中的某个位置。

所有这些增强听起来都很有吸引力,我期待着我们将在未来的 PostgreSQL 版本中拥有什么样的新特性。

本文参考:

  1. https://www.postgresql.org/docs/devel/sql-altersubscription.html
  2. https://www.postgresql.org/docs/devel/sql-createsubscription.html
  3. https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=208c5d65bbd60e33e272964578cb74182ac726a8
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论