PostgreSQL 13、14中逻辑复制/解码改进
最近写了一篇关于Patroni如何解决PG集群中逻辑复制槽故障转移问题的博客:
事实上,没有什么比这个问题更能伤害逻辑复制了。即使在写本文时,也可以看到目前没有用户试图解决这个问题。感谢Patroni社区以出色的方式解决了这个问题:无需补丁、无需扩展,完全无创的解决方案。
随着最大的缺陷消失,我们预计会有越来越多的用户开始研究或重新考虑逻辑复制,尤其是那些由于实际困难而放弃它的用户。我想让他们知道PG13和14等版本中,还有更多与逻辑复制/解码相关的令人兴奋的新功能。在进入新特性前,让我们看看旧版本中逻辑复制的其他问题。
内存使用和磁盘使用
PG过去只为内存中每个事务保留4096次更改(max_changes_in_memory)。如果有一个非常长的事务,其余的更改将作为溢出文件溢出到磁盘。这里有2个重要含义。1)如果每次变化很大,若有子事务,内存消耗很容易跑到几个GB。这甚至会影响主机的稳定性和OOM启动的机会。2)如果更改非常小,若有太多的小更改,并事务很长,就会溢出到磁盘,造成IO开销。
大量复制延迟和CPU负载
许多用户几乎经常抱怨他们不断看到巨大的复制延迟。仔细检查显示WAL sender进程正在消耗大量CPU。单核饱和是最常见的情况。很多时候,更进一步分析显示存在长时间运行的事务或大量数据加载并导致溢出文件的生成。系统正忙于检查溢出文件并准备提交顺序,需要将其发送到逻辑副本。
同样,我们见证了一些用户选择逻辑复制以减少主节点负载的案例。但是WAL sender在逻辑解码期间的复杂性抹杀了所有潜在的收益。这些问题对PG社区来说并不陌生。事实上,关于问题及修复的讨论大约在PG10发布同时开始。好消息是,这些在最近发展中得到了解决。
PG13中的改进
内存和磁盘使用问题在PG13已经基本解决。当添加这了这些改动后,max_changes_in_memory(4096)不再使用了。相反,PG13会跟踪所有事务的总内存使用量和单个事务的内存使用量。引入了一个新参数logical_decoding_work_mem。仅当超过限制时,缓冲区才会溢出到磁盘。并且只有消耗最多内存的最大事务才会成为溢出到磁盘的受害者。这更加智能,减少了不必要的磁盘溢出。
参考:
ReorderBufferCheckMemoryLimit (src/backend/replication/logical/reorderbuffer.c)
PG14中的改进
logical_decoding_work_mem已满时溢出到磁盘是一种想法。但是如何将改动直接传输给订阅者而不是溢出到磁盘,这是PG14中的主要改进。由于我们处理的是正在运行的事务,所以这并不是说说那么简单。逻辑复制的整体逻辑和特性必须经历巨大变化。但是PG14引入了将reorderbuffer流式传输到订阅者而不是先溢出到磁盘的选项。显然,流式传输正在运行的事务这个新功能需要复制协议的改进。新日志信息格式例如“Stream Start”、“Stream Stop”、“Stream Commit”、“Stream Abort”等添加到了复制协议中。参考:
https://www.postgresql.org/docs/14/protocol-logicalrep-message-formats.html
输出插件接口也需要相应改进。这也是PG14中的改进,参考提交45fdc9738b了解更多详细信息,并参阅PostgreSQL文档。
当超过logical_decoding_work_mem时,使用流。这并不意味着buffer不会溢出到磁盘。如果流不可用,保留使用溢出到磁盘的选项。如果当前可用的信息不足以解码,就会发生这种情况。
提交7259736a6e5b7c7588fff9578370736a6648acbb总结了重大改进:
1)当达到logical_decoding_work_mem内存限制后,并不是将事务序列化到磁盘,而是使用内存部分的改动并调用流API方法(commit:45fdc9738b)。但是,有时如果我们有不完整的toast或者预测插入,会溢出到磁盘,因为无法生成完整的元组和流,一旦获得完整的元组旧会流式传输包括序列化更改在内的事务。
2)由于立即在WAL中进行了分配(将 subxact 与顶级 xact 相关联),并且在每个命令结束时记录了失效信息,我们可以进行这种增量处理。由提交0bead9af48和c55040ccd0添加。
3)现在可以流式正在运行的事务,当输出插件查询catalog(系统和用户自定义)时,并发的abort可能会造成故障。通过这样处理这个故障:系统表扫描方法API返回ERRCODE_TRANSACTION_ROLLBACK给后端服务或者解码特定未提交事务的WAL Sender。接收到这个错误码的解码逻辑终止当前事务的解码,并继续解码其他事务。
如何配置
必要的功能仅在PG14中使用。客户端需要在streaming开启的情况下初始化复制连接。为次,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)
监控改进
监控方面主要由2个改进。
监控初始数据COPY
PG14允许用户使用新的监控试图pg_stat_progress_copy来监控COPY命令的进度。当有人设置逻辑复制时,这是一个很大的增值。下面是一个select * from pg_stat_progress_copy ;输出示例,以及PUBLISHER 端使用\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)
监控逻辑复制
可以通过PG14提供的新视图监控逻辑复制:PUBLISHER 端pg_stat_replication_slot。即使我们不适应新的流式传输功能,这也很有用,因为生成溢出文件的可能性更高。
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();重置与特定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();
结论
本文,鼓励过去由于逻辑复制缺陷而放弃逻辑复制的用户重新考虑使用它。因为PG13和14和Patroni解决了大部分困难。冗长的批量事务会对逻辑复制造成严重影响,之前版本非常严重,随着改进,很大程度上得到了缓解,预计将大大减少发布端的负载。
然而,并不意味着这是完美的。社区和开发人员知道更多需要改进的地方,尤其是订阅端的改进。启动即将到来的版本中会有这样的变化。
原文
https://www.percona.com/blog/logical-replication-decoding-improvements-in-postgresql-13-and-14/