Code前端首页关于Code前端联系我们

PostgreSQL 15:如何改进逻辑复制中的通信?

terry 2年前 (2023-09-26) 阅读数 53 #数据库

解释 PostgreSQL 15 中引入的改进,以解决逻辑复制通信中的以下两个问题:

  • 如果未根据订阅过滤器发布事务中的所有 DML,则发送空事务的情况。这会浪费CPU/内存/网络等资源。
  • 对于大型交易,如果 walreceiver 忙于处理交易中未发布的 DML,则 walsender 可能无法长时间与 walreceiver 通信。即使 walsender 按预期工作,也可能会导致意外的超时错误。

逻辑复制通信

在进一步讨论之前,我想简单介绍一下逻辑复制通信的两个概念:通信消息类型和过滤。

通信消息类型

在逻辑复制中,walsender 向 walreceiver 发送的消息有两种类型:

  • 维护消息
    该消息用于告诉 walreceiver walsender 正在按预期工作。
    用户可以使用 wal_receiver_timeout 参数设置 walreceiver 的 GUC 超时(默认为 60 秒)。如果在wal_receiver_timeout内,如果walreceiver没有收到来自walsender的任何消息,它将退出并出现超时错误。
  • 逻辑复制日志消息
    逻辑复制日志消息有多种类型,但我们在本博客中仅关注其中两种:
    • DML消息,包括INSERT、UPDATE、DELETE和TRUNCATE
    • 定义开始消息和事务结束(例如 BEGIN 和 COMMIT 消息)。
      您可以在 PostgreSQL 网站上查看逻辑复制协议的完整列表。

过滤

您可能没有意识到这一点,但并非事务中的所有 DML 都会发送到 walreceiver。这是因为在创建发布时可以为表、行或操作类型指定过滤器。因此,如果满足过滤条件,某些DML将不会被发送。

改进概述

现在我将向您展示如何改进/修复本文开头提到的两个问题。

空事务改进

如果一个事务中的所有DML在解码过程中都被过滤掉,那么我们称该事务为空事务。在 PostgreSQL 15 之前,标准逻辑解码插件 pgoutput(PostgreSQL 默认使用的逻辑复制插件)将每个事务发送到 walreceiver。即使对于空事务,虽然没有发送与 DML 相关的消息,但仍然会发送定义事务开始和结束的消息。构建/传输这些空事务会浪费 CPU 周期和网络带宽。我们可以在下面看到这个过程
PostgreSQL 15:如何改进逻辑复制中的通信?

为了解决这个问题,walsender延迟了 BEGIN 消息,直到发送第一条 DML 消息。如果解码结束时没有发送 BEGIN 消息,则也不会发送 COMMIT 消息。详细的开发信息可以在 GitHub 上找到。

对于非空交易,消息发送时间也会发生变化。我们举个例子来看看这个变化——假设我们有如下事务T1:

postgres=# BEGIN;
BEGIN
postgres=*# INSERT INTO tab_not_publish VALUES (1); -- This DML will be filtered out
INSERT 0 1
postgres=*# INSERT INTO tab_publish VALUES (1); -- This DML will be published
INSERT 0 1
postgres=*# COMMIT;
COMMIT

发送BEGIN消息和COMMIT消息的时序如下图所示。
PostgreSQL 15:如何改进逻辑复制中的通信?

正如我们在上面看到的,只有 tab_publish 表的 INSERT 消息可以从 walsender 发送到 walreceiver。
PostgreSQL 15:如何改进逻辑复制中的通信?
如上所述,如果修改后过滤掉事务中的所有 DML,则不会发送 BEGIN 和 COMMIT 消息。因此,walsender可以跳过发送空交易。

但是在同步逻辑复制中,PostgreSQL 15之前,为了确认数据已经同步,当walreceiver收到空事务的COMMIT消息时,会同步本地数据,并向walsender发送反馈消息。确认数据已同步。

walsender 只有在收到 walreceiver 的反馈后才会继续,否则 walsender 会阻止客户端后端进行交易。因此,在 PostgreSQL 15 中,在同步逻辑复制中跳过空事务后,walsender 会向 walreceiver 发送 keep 消息并请求反馈。然后walreceiver同步数据并根据这些消息发送walsender反馈。这样就可以避免同步逻辑复制中事务响应延迟的情况。下图显示了同步逻辑复制中通信的变化。
PostgreSQL 15:如何改进逻辑复制中的通信?

PostgreSQL 15:如何改进逻辑复制中的通信?

修复意外超时错误

在 PostgreSQL 15 之前,如果一个事务有很多连续的 DML 没有发布,会导致 walsender 长时间无法与 walreceiver 通信,因为 walsender 会很忙解码这些未发布的 DML。 。在这种情况下,即使 walsender 按预期工作,它也会导致 walreceiver 收到意外的超时错误,因为 walreceiver 在指定的超时时间内不会收到来自 walsender 的任何消息。

在 PostgreSQL 15 中,为了避免此错误,walsender 定期与 walreceiver 进行通信。因此,当walsender处理一定阈值的DML(无论这些DML是否已经发布)时,他会在必要时尝试发送walreceiver消息来保持通信。此开发的详细信息可在 GitHub 上找到。

假设我们有事务T2,T2中有很多DML,但没有一个会被发布。如下图所示,当T2交易被处理时,walsender和walreceiver之间的通信会发生变化。
PostgreSQL 15:如何改进逻辑复制中的通信?

PostgreSQL 15:如何改进逻辑复制中的通信?

如上所示,我们在PostgreSQL核心中将阈值设置为100(经过性能测试,确认这个阈值可以解决这个超时错误,并且不会降低2的性能)。这样,当 walsender 按预期工作时,walreceiver 就不会出现意外的超时错误。

性能影响

最后分享零事务改进的性能测试结果。

如上所述,改进对空事务的处理后,walsender不再向walreceiver发送仅包含BEGIN和COMMIT消息的空事务。这可以减少网络流量并提高性能。在我的测试中,我发现在对空事务的解码进行改进后,walsender 在异步逻辑复制中传输的字节数减少了 97 个字节;在同步逻辑复制中,即使walsender再次发送维护消息,但总传输量仍然减少了79字节。接下来我们将通过测试结果来看看网络带宽消耗方面的性能提升。

很明显,walsender发送的交易中空交易的比例影响了测试结果,因此我们测试了五种不同比例的空交易。另外,改进后,在同步逻辑复制中,如果walsender跳过空事务,则会额外发送一条keep消息,因此同步逻辑复制和异步逻辑复制都进行了测试。如下所述,异步逻辑复制和同步逻辑复制的性能都得到了改进。 (x轴代表传输交易中空交易的占比,y轴代表walsender向walreceiver传输的数据总量,以字节为单位。)

PostgreSQL 15:如何改进逻辑复制中的通信?

PostgreSQL 15:如何改进逻辑复制中的通信?

从测试结果可以明显看出,占比越高空交易的数量,改善就越大。最棒的。当空交易比例为25%、50%、75%和100%时,网络流量减少约8%、22%、40%和84%。当没有空事务时,性能也不会下降。

面向未来

在这篇文章中,我通过跳过空事务并修复意外超时错误来解释逻辑复制的性能和功能改进。然而,跳过空交易的机制仍然存在一些局限性。

例如,两阶段确认不会跳过空交易。这是因为,如果walsender在准备交易和确认准备交易之间重启,我们无法清楚地知道确认时准备交易是否被跳过。由于同样的问题,正在进行的事务的流式传输(由 subscribe_parameter 的“streaming”参数设置)没有得到改进。很快我们可能会尝试以更好的方式改进上述两种交易类型中空交易的处理。

此外,为了提高将持续交易流应用到walreceiver的效率,我们团队在walreceiver 3中共享了交易并行应用的补丁,并正在社区中积极讨论以持续改进。

原标题:PostgreSQL 15 如何改进逻辑复制中的通信
原作者:Wei Wang

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门