21 深入专题:逻辑复制与数据同步

21 深入专题:逻辑复制与数据同步

逻辑解码、发布订阅、复制槽与异构数据同步方案。

PG 什么时候必须用 HugePages?

当 shared_buffers 很大(如 ≥8GB)、连接数多、且 relcache/catcache/plancache 等热工作集大时,使用 HugePages(大页)可显著降低 TLB miss,避免大量 4KB 小页带来的页表遍历开销。大页把页表项从千万级降到万级,减少内存访问延迟和缺页中断,尤其适合大 shared_buffers、多连接、多对象的高并发场景。启用需要配置 vm.nr_hugepages 并让 PG 进程有权限用大页,否则启动会失败。

PG 同步逻辑复制有哪些『巨坑』?

坑主要在语义和时机上:一是 failover=true 的槽如果备库没及时同步,切主后订阅端可能缺数据;二是 hot_standby_feedback 会把备库的 catalog_xmin 反馈给主库,主库清理 catalog 死元组被阻塞,可能造成系统表膨胀;三是槽同步依赖物理备库与主库的 LSN 追赶,网络抖动会导致槽同步延迟。使用前要确认 sync_replication_slots 与物理复制配置一致,并监控 catalog_xmin 前进情况,否则高可用反而引入新故障。

PG14 如何解决大事务逻辑复制的延迟问题?

PG14 引入 streaming 流式复制:对超过 logical_decoding_work_mem 的大事务,逻辑解码边产生边发送变更,而不是等事务提交才一次性落盘 spill 文件再发送,从而大幅缩短大事务的端到端延迟。之前大事务要等提交后才能解码发送,导致长时间无数据流动、订阅端延迟剧增。通过流式接口(pg_logical streaming API)把变更增量流式吐出。

PG15 发布支持发布整个 schema 里的所有表吗?

支持。PG15 允许发布某个 schema 下的所有表(Allow publishing the tables of schema),类似之前 FOR ALL TABLES,可针对指定 schema 一键发布。配合后续 PG19 的 EXCEPT TABLE 黑名单,发布配置越来越灵活:既可按 schema 发布,又可排除个别表,让逻辑复制的表集合管理更接近生产需要,减少逐表 CREATE PUBLICATION 的维护成本。

PG15 逻辑复制对 2PC(两阶段提交)事务做了什么支持?

PG15 支持逻辑解码和订阅回放 2PC 两阶段提交事务:PREPARE TRANSACTION 的变更先被解码输出,等 COMMIT PREPARED 再确认应用,保证分布式场景下事务原子性在逻辑复制流里正确表达。此前逻辑复制无法正确处理 2PC,会导致事务边界错乱。开启后订阅端能正确重放跨节点协调的两阶段事务,配套 publication/subscription 的 two_phase 相关选项。

PG16 为什么允许 standby 上做逻辑解码?

PG16 允许在物理 standby(从库)上建立逻辑复制槽并进行逻辑解码,让订阅端可以直接从从库消费,把逻辑复制的解码负载从主库转移出去,减轻主库压力。这需要 standby 开启 wal_level=logical 且保留足够 WAL,主库把逻辑解码所需信息随 WAL 同步过来。这样主库专注写,从库承担 CDC/订阅读取,是读写分离在复制侧的延伸。

PG16 对大事务逻辑复制做了什么并行优化?

PG16 支持订阅端并行回放大事务:设置 subscription 参数 streaming=‘parallel’ 后,apply worker 会把大事务拆分给多个并行 apply worker 同时回放,突破单线程回放的瓶颈。使用并行回放要注意 max_parallel_apply_workers_per_subscription 等参数,且并行回放下事务内依赖顺序仍需保证。PG18 起把默认行为改为 parallel,进一步释放大事务复制性能。

PG18 对大事务逻辑复制性能做了什么改进?

PG18 把大事务逻辑复制的默认行为升级为并行应用(parallel apply),并把流式解码与并行回放结合,减少大事务在订阅端的延迟。同时继续优化逻辑解码内存管理和 spill,让超过 logical_decoding_work_mem 的大事务复制更平滑。整体方向是让大事务从『提交后一次性解码』演进为『流式+并行』,把复制延迟压到接近实时的水平。

PG19 发布端 EXCEPT TABLE 黑名单是干什么的?

EXCEPT TABLE 允许发布端用 CREATE PUBLICATION … FOR ALL TABLES EXCEPT (表名) 的方式,把『除指定表外的所有表』纳入发布,本质是黑名单机制。此前 FOR ALL TABLES 只能全量发布,没法排除个别不需要复制的表(如临时表、日志表)。EXCEPT TABLE 让全库发布的配置更灵活,新增表自动纳入发布的同时又能排除例外,是逻辑复制易用性的一大改进。

PG19 在逻辑复制可靠性上又改进了什么?

PG19 再次提高逻辑复制可靠性,重点是给 pg_sync_replication_slots() 增加 retry 重试逻辑,在同步逻辑复制槽时遇到瞬时失败可自动重试,减少因网络抖动导致的槽同步失败。同时持续完善同步逻辑复制槽的故障转移语义,让主备切换后逻辑订阅的接续更可靠。整体趋势是把同步逻辑复制槽打磨成生产可用的高可用组件。

PG19 的 foreign server 独立订阅(CREATE SUBSCRIPTION … SERVER)是什么?

PG19 宣布支持在 CREATE SUBSCRIPTION 时用 SERVER 指定一个已定义的 foreign server,把订阅的连接信息与 foreign server 解耦。好处是连接参数(主机、端口、认证)集中在一个 foreign server 对象里管理,多个订阅可复用同一 server 定义,改连接信息只需改 server 一处。这让逻辑复制的连接管理更规范,也为跨服务器统一配置铺路。

PostgreSQL 逻辑复制的基本架构和核心对象是什么?

逻辑复制基于『发布(publication)+ 订阅(subscription)+ 复制槽(replication slot)+ 逻辑解码(pgoutput)』四件套。发布端用 CREATE PUBLICATION 声明要复制的表,订阅端用 CREATE SUBSCRIPTION 指定连接和发布,内核通过逻辑解码把 WAL 里的变更还原成逻辑行(INSERT/UPDATE/DELETE),经 pgoutput 协议发送,订阅端 walsender/apply worker 回放。复制槽保证订阅端断线期间 WAL 不被回收,是逻辑复制的基石。

WalMiner 是什么,和官方逻辑解码有什么区别?

WalMiner 是一个 WAL 日志解析 SQL 工具,能直接把 PostgreSQL 的 WAL 记录解析成对应的 SQL 语句(redo),并支持生成逆向 SQL(undo),用于误操作找回、审计和数据分析。它不要求 wal_level=logical,而是直接解析物理 WAL,对历史 WAL 也能回放;而官方逻辑解码(pgoutput)依赖 logical 级别 WAL 且只能从解码开始点向后。WalMiner 3.0 起更名并增强,4.0 预览版进一步优化了解析能力与精确性。

WalMiner 精确解析依赖什么条件,undo 靠什么实现?

WalMiner 要精确还原某行数据的修改,需要依赖 checkpoint 之后的 full page write(FPW)拿到整页数据作为基准,再叠加后续 WAL 记录重建行内容;如果解析起点早于 checkpoint、没有 FPW 则可能无法精确还原。undo(逆向 SQL)是根据 WAL 里记录的新旧值对,生成反向的 UPDATE/DELETE 把数据改回去。因此保留足够的 WAL 和合理的 checkpoint 间隔是 WalMiner 能精确找回数据的前提。

pg_logical_emit_message 函数是干什么用的?

pg_logical_emit_message(transactional, prefix, content) 可以向 WAL 写入自定义的逻辑解码消息,返回 pg_lsn。用途是传递控制消息、审计信息、DDL 等不落地到 table 的定制内容,直接写 WAL 且不依赖 pg_catalog 元数据结构。它是 pgstream 实现 DDL 复制、以及其他逻辑解码应用传递旁路消息的基础,比把消息塞进临时表再解码更简洁高效。

pglogical 2.x 相比内置逻辑复制有什么优势?

pglogical 是 2ndQuadrant 开发的成熟逻辑复制插件,早期内置逻辑复制(PG10 前没有)时它就是主流方案。pglogical 2.x 支持行过滤、列过滤、跨版本复制、级联订阅、冲突检测等内置逻辑复制缺失的高级能力,部署也比手工配置 publication/subscription 更灵活。随着 PG10+ 内置逻辑复制成熟,很多场景可用内置方案替代,但 pglogical 的过滤和冲突处理能力仍优于内置。

pgstream 如何解决 PG 逻辑复制不支持 DDL 的问题?

PG 原生逻辑复制只复制 DML,不支持 DDL。pgstream 是开源方案,通过事件触发器捕获 DDL,配合 pg_logical_emit_message 把 DDL 作为消息写入 WAL 并随逻辑复制流发送,订阅端接收后在回放事务前先执行对应 DDL,从而实现 DDL 的复制。pgstream v1.0 已把这条『DDL 复制』链路做通,弥补了 PG 逻辑复制长期被吐槽的短板。

同步逻辑复制槽(sync replication slot)是什么,为什么要用?

同步逻辑复制槽把主库的逻辑复制槽同步到物理 standby,配合 failover=true 和 hot_standby_feedback,实现主备切换后逻辑订阅『不断流』——standby 升级为主后,原订阅端能直接从新主继续消费,不丢数据。关键参数包括 failover=true(槽支持故障转移)、sync_replication_slots=on(主库把槽同步到备库)、synchronized_standby_slots(限制同步范围)。PG 还提供 pg_sync_replication_slots() 函数,PG19 给它加了 retry 逻辑提高可靠性。

复制槽的关键 LSN 字段 restart_lsn/confirmed_flush_lsn/catalog_xmin 分别代表什么?

restart_lsn 是最早可能需要重新解码的 WAL 位置,主库保留 WAL 至少要保留到它,否则订阅端追不上就要重建。confirmed_flush_lsn 是订阅端已确认持久化的位置,用于衡量复制延迟和判断 standby 同步进度。catalog_xmin 是复制槽持有的最老 catalog 版本号,主库清理系统表死元组时不能早于它——如果订阅端长期不消费,catalog_xmin 长期不前进,会导致主库 pg_catalog 膨胀、vacuum 失效。

如何用内置逻辑复制做 minimal downtime 大版本升级?

方案是在旧版本实例上建发布(含目标表),新版本实例建订阅做初始数据同步,增量持续通过逻辑复制追赶;数据基本同步后,短暂停写、切应用连接、确认槽追上最后 LSN、再删订阅完成切换。相比直接 pg_upgrade 停机升级,逻辑复制方案可以把停机时间压缩到切换瞬间(秒级到分钟级),代价是需要先建逻辑复制链路、且对 DDL 敏感、要求有主键。

无主键表怎么做逻辑复制?

PG 逻辑复制默认按主键定位行,无主键表无法可靠更新/删除。早期可用隐藏列(如 oid 或 replica identity)作为行标识,PG 通过设置 REPLICA IDENTITY FULL(用整行做标识,代价高)或 USING INDEX 指定唯一索引来解决。最推荐的做法是给表加主键或至少一个非空唯一索引,否则 REPLICA IDENTITY FULL 会复制整行旧值、性能差,且无法精确表达部分列更新。

正确配置 Debezium 捕获 PG 逻辑增量要注意什么?

Debezium 的 PostgreSQL connector 基于逻辑解码,要求 wal_level=logical、安装 decoderbufs 或 wal2json 等输出插件,并给连接账号足够的复制权限。生产上要避免 Debezium 2.2~3.0 之间的已知 bug(如快照/类型映射问题),建议用修复后的版本。还要监控复制槽不消费导致的 WAL 膨胀,为每个捕获任务建独立槽,保证 max_wal_size 合理,否则 Debezium 停摆会把主库 WAL 撑爆。

触发器在逻辑复制的订阅端会不会被执行?

默认会。逻辑复制回放变更时,订阅端表上的触发器会照常触发,这与物理流复制(从库不重放触发器)不同。这既有用(可以在订阅端做审计、转换)也有坑(可能重复触发业务逻辑)。如果不想触发,可以用 ALTER TABLE … DISABLE TRIGGER 或让触发器判断 session_replication_role,PG 在回放时把 session_replication_role 设为 replica,触发器可据此跳过。

逻辑复制如何防止双向复制打环(数据回环)?

通过 origin 机制防环:每个变更记录其来源 origin,逻辑解码输出时带上 origin,回放端用 origin=none 或跳过自身 origin 的变更,从而避免 A→B→A 的无限循环。在配置双向/多主逻辑复制时,订阅端要正确设置 origin,让从 A 复制到 B 的数据再被 B 发布时,A 能识别并忽略,否则会产生打环和重复应用。

逻辑复制开始时还没结束的事务会不会丢失?

不会丢失,但要注意时序:逻辑复制的复制槽在创建时记录一个初始 LSN,从该 LSN 之后提交的事务才会被解码。如果在创建订阅/槽时某个事务尚未提交,它的变更尚未写入 WAL 对应位置,只有当它后续提交并落在解码范围才会被发送。核心是逻辑解码只输出『已提交』事务,未提交的事务不会出现在解码流里,因此不会丢已提交数据,但『快照一致性』和初始数据同步要分开处理。

逻辑解码的内存参数 logical_decoding_work_mem 和 spill 是什么?

logical_decoding_work_mem 是逻辑解码器在做事务内变更排序时使用的内存上限,默认 64MB。当单个事务的解码数据超过该上限时,会把部分变更 spill(溢出)到磁盘,避免内存暴涨,但会带来 IO 开销。PG16 引入 logical_decoding_mode=buffered/immediate 控制是否允许缓冲溢出,buffered 会尽量缓冲、超出才落盘,immediate 则即时输出不缓冲。控制好该参数和溢出行为对大批量变更复制的性能影响很大。