原文地址:https://www.percona.com/blog/logical-replication-decoding-improvements-in-postgresql-13-and-14/
原文作者:Jobin Augustine
我最近写了一篇关于 Patroni 如何解决 PostgreSQL 集群中的逻辑复制槽故障转移问题的博客。事实上,没有什么比这个问题更能伤害逻辑复制了。甚至在我写这篇文章的时候,我也可以看到没有 Patroni 的客户/用户正在努力解决这个问题。感谢 Patroni 社区以最出色的方式解决了这个问题:不需要给PostgreSQL 打补丁,不需要扩展!完全无创的解决方案。
随着最大的障碍/威慑消失,我们预计越来越多的用户开始研究或重新考虑逻辑复制,尤其是那些由于实际困难而放弃它的用户。我想让他们知道,在 PostgreSQL 13 和 14 等新版本中,还有许多与逻辑复制/解码(decoding )相关的令人兴奋的新功能。
在进入新特性之前,让我们看看 PostgreSQL 旧版本中逻辑复制的其他问题。
内存使用和磁盘使用 - Memory Usage and Disk Usage
如果有一个非常长的事务,其余的更改将作为溢出文件溢出到磁盘。这有两个重要的含义。首先,如果每次更改真的很大,并且如果有子事务,那么内存消耗很容易达到几 GB。这甚至会影响主机的稳定性和 OOM 启动的机会。另一方面,如果更改非常小,如果有太多的小更改,如果事务很长,就会溢出到磁盘,造成 IO 开销。
大量复制延迟和 CPU 负载 - Massive Replication Delays and CPU Load
单核饱和是最常见的情况。很多时候,更进一步的调查显示存在长时间运行的事务或大量数据加载并导致溢出文件的生成。系统正忙于检查溢出文件并准备提交顺序,需要将其发送到逻辑副本。
同样,我们见证了一些用户选择逻辑复制以减少主节点负载的案例。但是 WAL 发送器在逻辑解码过程中的复杂性(CPU 和 IO 使用)抹杀了所有潜在的收益。
些问题对于 PostgreSQL 社区来说并不陌生。事实上,讨论大约在 PostgreSQL 10 发布的同时开始,关于这些问题及其修复。好消息是所有这些都在最近的发展中得到解决。
我想从用户社区向那些为开发这些出色的解决方案贡献时间和精力的人表示感谢。即,Tomas Vondra、Amit Kapila、Dilip Kumar、Masahiko Sawada、Vignesh C,还有更多的人提供了非常宝贵的意见,例如 Peter Eisentraut、Masahiko Sawada 和 Andres Freund。
PostgreSQL 13 中的改进 - Improvements in PostgreSQL 13
内存和磁盘使用问题在 PostgreSQL 13 中基本解决。现在在添加更改时不使用 max_changes_in_memory (4096)。相反,会跟踪所有事务的总内存使用量和单个事务的内存使用量。引入了一个新参数logical_decoding_work_mem。仅当超过此限制时,缓冲区才会溢出到磁盘,并且只有消耗最多内存的最大事务才会成为溢出到磁盘的受害者。这更智能,也减少了不必要的磁盘溢出。
参考:ReorderBufferCheckMemoryLimit (src/backend/replication/logical/reorderbuffer.c)
PostgreSQL 14 中的改进 - Improvements in PostgreSQL 14
当logical_decoding_work_mem 已满时溢出到磁盘是一种想法。但是如何将更改直接传输给订阅者而不是溢出到磁盘。这是 PostgreSQL 14 中的主要更改/改进。但这并不像说的那么容易,因为我们正在处理正在进行的事务。逻辑复制的整体逻辑和功能必须经历巨大的变化。但是,是的,PostgreSQL 14 引入了将重新排序缓冲区流式传输到订阅者而不是先溢出到磁盘的选项。显然,流式传输正在进行的事务的这一新功能需要改进复制协议。复制协议中添加了新的消息格式,如“Stream Start”、“Stream Stop”、“Stream Commit”和“Stream Abort”等。请参考 PostgreSQL 文档:https://www.postgresql.org/docs/14/protocol-logicalrep-message-formats.html for more details.
输出插件界面也需要相应的更改。这也是对 PG 14 的改进。请参阅提交 45fdc9738b了解更多详细信息,并参阅 PostgreSQL 文档。
当超过 logical_decoding_work_mem 时考虑流式传输。这并不意味着缓冲区永远不会溢出到磁盘。如果无法进行流式传输,则仍然可以选择溢出到磁盘。如果当前可用的信息不足以解码,就会发生这种情况。
提交 7259736a6e5b7c7588fff9578370736a6648acbb 总结了重大改进。
我们不是在内存中达到logical_decoding_work_mem 限制后将事务序列化到磁盘,而是使用内存中的更改并调用提交 45fdc9738b 添加的流API 方法。但是,有时如果我们有不完整的 toast 或推测插入,我们会溢出到磁盘,因为我们无法生成完整的元组和流。而且,一旦我们获得完整的元组,我们就会流式传输包括序列化更改在内的事务。
由于立即在 WAL 中进行了分配(将 subxact 与顶级 xact 相关联),并且由于在每个命令结束时记录了失效消息,我们可以进行这种增量处理。这些功能分别由提交 0bead9af48 和 c55040ccd0 添加。
现在我们可以流式传输正在进行的事务,当输出插件查询目录(系统和用户定义的)时,并发中止可能会导致失败。我们通过从系统表扫描 API 返回 ERRCODE_TRANSACTION_ROLLBACK sqlerrcode 到后端或解码特定未提交事务的 WALSender 来处理此类故障。接收到这样一个 sqlerrcode 的解码逻辑中止当前事务的解码并继续解码其他事务。
如何设置 - How to Setup
必要的功能仅在 PostgreSQL 14 中可用。客户端需要在“流式传输”开启的情况下启动复制连接。为了促进这一点,CREATE SUBSCRIPTION 采用了一个新的输入参数“streaming”,默认情况下它是关闭的。下面是一个例子:
CREATE SUBSCRIPTION sub1 CONNECTION 'host=pg0 port=5432 dbname=postgres user=postgres password=xxxx' PUBLICATION tap_pub WITH (streaming = on);
请记下新参数 streaming =on,它指定是否应为此订阅启用正在进行的事务的流式传输。
或者,可以修改当前订阅以启用流式传输。
ALTER SUBSCRIPTION sub1 SET(STREAMING = ON)
监控改进
在监控方面有两个主要改进。
监控初始数据副本 - Monitoring the Initial Data Copy
PostgreSQL 14 允许用户使用新的监控视图 pg_stat_progress_copy 监控 COPY 命令的进度。当有人设置逻辑复制时,这是一个很大的增值。有关详细信息,请参阅文档。
以下是示例输出: select * from pg_stat_progress_copy ;从 发布人 端使用 psql 的 \watch
Wed 23 Feb 2022 07:01:46 AM UTC (every 1s)
pid | datid | datname | relid | command | type | bytes_processed | bytes_total | tuples_processed | tuples_excluded
------+-------+----------+-------+---------+------+-----------------+-------------+------------------+-----------------
2034 | 16401 | postgres | 16390 | COPY TO | PIPE | 932960052 | 0 | 9540522 | 0
(1 row)
Wed 23 Feb 2022 07:01:47 AM UTC (every 1s)
pid | datid | datname | relid | command | type | bytes_processed | bytes_total | tuples_processed | tuples_excluded
------+-------+----------+-------+---------+------+-----------------+-------------+------------------+-----------------
2034 | 16401 | postgres | 16390 | COPY TO | PIPE | 976060287 | 0 | 9979509 | 0
(1 row)
因为我们知道表中有多少元组,所以我们不难理解它的进展程度。
订阅方也可以进行类似的监控:
Wed 23 Feb 2022 07:01:46 AM UTC (every 1s)
pid | datid | datname | relid | command | type | bytes_processed | bytes_total | tuples_processed | tuples_excluded
------+-------+----------+-------+-----------+----------+-----------------+-------------+------------------+-----------------
1204 | 14486 | postgres | 16385 | COPY FROM | CALLBACK | 912168274 | 0 | 9328360 | 0
(1 row)
Wed 23 Feb 2022 07:01:47 AM UTC (every 1s)
pid | datid | datname | relid | command | type | bytes_processed | bytes_total | tuples_processed | tuples_excluded
------+-------+----------+-------+-----------+----------+-----------------+-------------+------------------+-----------------
1204 | 14486 | postgres | 16385 | COPY FROM | CALLBACK | 948074690 | 0 | 9694752 | 0
(1 row)
监控逻辑复制
可以通过 PostgreSQL 14 提供的新视图监控逻辑复制:PUBLISHER 端的 pg_stat_replication_slots。 (名称类似于 pg_replication_slots )。但这种观点是一个很大的进步。
即使我们没有使用新的流式传输功能,这也很有用,因为生成溢出文件的可能性更高。
postgres=# select * from pg_stat_replication_slots ;
slot_name | spill_txns | spill_count | spill_bytes | stream_txns | stream_count | stream_bytes | total_txns | total_bytes | stats_reset
-----------+------------+-------------+-------------+-------------+--------------+--------------+------------+-------------+-------------
sub | 1 | 34 | 2250000000 | 0 | 0 | 0 | 2701 | 1766040 |
(1 row)
正如我们在上面的案例中看到的,有一个庞大的事务,导致了大量的溢出文件。
可以使用函数 pg_stat_reset_replication_slot() 重置与特定插槽相关的统计信息;
postgres=# select pg_stat_reset_replication_slot('sub');
pg_stat_reset_replication_slot
--------------------------------
(1 row)
postgres=# select * from pg_stat_replication_slots ;
slot_name | spill_txns | spill_count | spill_bytes | stream_txns | stream_count | stream_bytes | total_txns | total_bytes | stats_reset
-----------+------------+-------------+-------------+-------------+--------------+--------------+------------+-------------+-------------------------------
sub | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 2022-02-23 15:39:08.472519+00
(1 row)
启用流式传输后,我们可以获得正在进行的交易流式传输的详细信息:
Wed 23 Feb 2022 03:58:53 PM UTC (every 2s)
slot_name | spill_txns | spill_count | spill_bytes | stream_txns | stream_count | stream_bytes | total_txns | total_bytes | stats_reset
-----------+------------+-------------+-------------+-------------+--------------+--------------+------------+-------------+-----------------------------
sub | 1 | 9 | 603980550 | 0 | 29 | 1914455250 | 242 | 1914488162 | 2022-02-23 15:55:46.8994+00
(1 row)
建议调整 logical_decoding_work_mem 的值(默认为 64MB)来设置我们可以为(每个)walsender 进程花费的最大内存量。使用它,我们可以避免大量溢出到磁盘,同时避免过多的内存使用。
例如:
postgres=# ALTER SYSTEM SET logical_decoding_work_mem = '512MB';
ALTER SYSTEM
postgres=# select pg_reload_conf();
结论
在这篇博文中,我想鼓励过去由于逻辑复制的缺点而放弃逻辑复制的 PostgreSQL 用户重新考虑它,因为 PostgreSQL 13 和 14 以及 Patroni 解决了大部分困难。众所周知,冗长的批量事务会对逻辑复制造成严重问题。在以前的版本中效果非常严重,但随着新的改进,它在很大程度上得到了缓解,预计将大大减少发布者方面的负载。
然而,这并不意味着它是完美的。社区和开发人员知道更多需要改进的地方,尤其是订阅方面的改进。我们应该期待在即将到来的版本中会有这样的变化。
作者
Jobin Augustine 是 PostgreSQL 专家和开源倡导者,在 PostgreSQL、Oracle 和其他数据库技术方面拥有超过 19 年的顾问、架构师、管理员、作家和培训师的工作经验。他一直是开源社区的积极参与者,他的主要关注领域是数据库性能和优化。他是各种开源项目的贡献者,并且是一位活跃的博主,喜欢用 C++ 和 Python 编写代码。 Jobin 拥有计算机应用硕士学位,并于 2018 年加入 Percona,担任高级支持工程师。在加入 Percona 之前,他曾在 OpenSCG 担任架构师 2 年,并且是 BigSQL 核心团队的一员,这是一个完整的 PostgreSQL 发行版产品。在 OpenSCG 工作之前,Jobin 在戴尔担任数据库高级顾问 10 年和 TCS/CMC 5 年。




