1前言
周末在家看了几篇关于逻辑复制的PPT,硬核干货,值得分享(来自pgconf富士通的逻辑复制分享)之前曾写过《关于逻辑复制的方方面面》,侧重于基础原理,而此篇文章则聚焦于新版本的特性与内核原理。
2正文
逻辑复制的应用场景很多,比如最常见的大版本升级、数据清洗、跨平台复制等。


整体架构如下所示,后续我会逐步分解每一步的流程

主要涉及到的进程有logical replication launcher、walsender、apply worker、table sync worker。
walsender:使用OutputPlugins进行逻辑解码,然后将解码后的数据发送给订阅端 logical replication launcher:类似于autovacuum launcher,守护进程,进行fork logical replication worker,发布端和订阅端都有该进程 logical replication worker:工作进程,接收WALsender发送过来的数据
3logical replication launcher

The logical replication worker launcher uses the background worker infrastructure to start the logical replication workers for every enabled subscription.
所以logical replication worker也会受到max_worker_processes参数的限制。logical replication launcher会周期性地检查是否有新的订阅端

当有新的订阅端连接上来(created)或者启用了(enabled),logical replication launcher便会创建一个apply worker,这便是逻辑复制的第一步流程。

apply worker会遍历所有待同步的表列表,然后生成table sync worker以同步表,每个待同步的表会由一个单独的sync worker负责,table sync worker的个数取决于max_sync_workers_per_subscription。同步完成之后,便会将pg_subscription_rel中的状态置为ready,已就绪

当然你可以选择在创建订阅的时候配置copy_data=off,是否复制发布端中表中已有数据,这便是逻辑复制的第二步流程。

可以看到,table sync worker通过walsender,使用pgoutput plugin copy复制数据,所以拷贝的速度是有保证的。这便是逻辑复制的第三步流程。
4walsender

walsender就是干活的进程,负责解析WAL获取数据,这里提到了reorderbufferqueue,我在之前的文章中也提过,walsender会借助ReorderBuffer结构体来保存这些信息,同时用ReorderBufferTXN标识每一个事务(xid和TXN进行映射),在事务提交时,借助decode plugin对元组进行逻辑解码,比如自带的test_decoding、decoder_raw等等,按插件自己定制解析后输出逻辑解析成品,然后同一个ReorderBufferTXN中的操作会全部发送给订阅端。
The reorderbufferqueue collects individual pieces of transactions in the order they are written to the WAL. When a transaction is completed, it will reassemble the transaction and call the output plugin with the changes.
当一个事务内的变更过大,超过了logical_decoding_work_mem,便会溢出到磁盘,也就是我们经常看到的spill文件。对于大事务,逻辑复制可谓是恨得牙痒痒,从13开始就在不断地进行优化(我会在下一篇文章剖析逻辑复制的演进,拭目以待)

如果配置了流式订阅,那么数据会实时流式传输到订阅端,发布端事务提交后,订阅端然后应用回放,统计数据会存储在pg_stat_replication_slots视图中,这便是逻辑复制的第七、八步流程。

当订阅端碰到错误时,会退出。然后replication launcher会周期性地检查,并重启worker进程

当然不同的错误会有不同的行为表现,比如主键冲突、检查约束冲突等

在PostgreSQL15提供了一个很方便的特性,允许skip lsn,

或者使用disable_on_error,这样就不会不断反复重启,浪费资源,等到用户自行处理好冲突之后,再启用即可。

5synchronous_commit
另外逻辑复制也有同步复制的说法,同样也是取决于synchronous_commit参数,发布端需要配置synchronous_standby_names参数

异步的流程如下

同步的流程如下

可以看到,差异在于是否需要等到订阅端回放完毕并且反馈给walsender

另外在15版本中提供了row filter选项,即允许发布部分数据,那么假如存在多个相同策略的时候,订阅端的行为是怎样的呢?答案是OR,如下??
The expressions get OR 'ed, and rows satisfying any of the expressions are replicated.
ALL TABLES publication and TABLES IN SCHEMA publication take precedence and the publish treat as if there are no row filters.


所以要格外注意当存在多个publication的时候,策略的行为差异。

除了row filter,新版本还支持指定列的传输,这样可以减少带宽消耗、隐藏关键字段提升安全性等,不过要注意的是
There's currently no support for subscriptions comprising several publications where the same table has been published with different column lists.
Specifying a column list when the publication also publishes FOR TABLES IN SCHEMA is not supported.


6小结
以上便是逻辑复制在14、15版本中的新特性行为,由于逻辑复制可能是一对多、多对多、多对一的形式,所以类似row filter/specific columns这类策略的交并集,就要格外小心。

另外,此篇文章对于另外一个关键点只是一笔带过——逻辑复制针对大事务的处理方式的演进,从13版本以前的max_changes_in_memory(4096)再到13以后的logical_decoding_work_mem,再到14的streaming replication,再到16订阅端的并行apply,都是为了优化逻辑复制场景下,大事务导致的巨大延迟,这一点我会在下一篇文章详细剖析,师母已呆。
由于公众号还没有留言的功能,我懒得迁移了,各位想与我交流或者加交流群的,欢迎来撩:_xiongcc