PostgreSQL 逻辑复制支持系统表同步详细设计文档


难度 中等

—— 新增系统表 pg_publication_sync 与用户接口扩展


1. 设计目标

本设计的目标不是实现完整 DDL 自动复制链路,而是先补齐以下两项基础能力:

  1. 新增系统 catalog 表 pg_publication_sync
    用于在内核中持久化 publication 维度的 DDL 同步控制信息。

  2. 扩展 publication / subscription 用户接口
    CREATE/ALTER PUBLICATIONCREATE/ALTER SUBSCRIPTION 中增加 ddl 参数,使用户能够声明 DDL 同步能力范围。

本设计仅覆盖:

  • 系统表定义
  • catalog 结构
  • 用户语法
  • 参数解析与校验
  • catalog 存储模型
  • 与现有 pg_publication / pg_subscription 的关系

本设计不包含

  • DDL 捕获机制
  • DDL WAL 编码机制
  • logical decoding 如何发送 DDL
  • apply worker 如何执行 DDL
  • DDL 冲突处理
  • 初始 schema 同步

2. 设计原则

2.1 pg_publication_sync 的定位

pg_publication_sync 应被设计为一张类似 pg_publication 的系统 catalog 表,而不是运行时消息队列表。

它的定位是:

  • 系统表
  • pg_catalog 下的 catalog
  • 保存 publication 相关的 DDL 同步元信息
  • 由内核维护
  • 不作为普通用户表参与逻辑复制对象集管理
  • 不承载逐条 DDL 事件日志

因此它的角色更接近:

  • pg_publication
  • pg_subscription
  • pg_publication_rel

而不是:

  • 运行时 message queue
  • 审计流水表
  • DDL 事件日志表

2.2 用户接口先行

本阶段首先冻结用户接口和 catalog 结构,确保:

  • SQL 语法明确
  • 参数语义明确
  • catalog 模型稳定
  • 后续捕获与应用逻辑可以在此基础上继续扩展

2.3 与现有逻辑复制模型兼容

本设计沿用现有逻辑复制控制面风格:

  • publication 保存“发布侧配置”
  • subscription 保存“订阅侧配置”
  • 新增 pg_publication_sync 保存“DDL 同步维度的附加控制信息”

尽量避免把 DDL 设计成与现有 publication 体系完全独立的一套机制。


3. 总体方案

整体上引入三类元数据:

  1. pg_publication.pubddl
    表示该 publication 开启了哪些 DDL 类型。

  2. pg_subscription.subddl
    表示该 subscription 允许接收哪些 DDL 类型。

  3. pg_publication_sync
    保存 publication 维度的 DDL 同步附加配置,用于描述:

    • 哪个 publication 参与 DDL 同步
    • DDL 同步对象范围
    • DDL 类型集合
    • 与对象标识、归属范围相关的控制信息

因此,pg_publication_sync 不是“替代 pg_publication”,而是:

pg_publication 在 DDL 同步维度上的补充 catalog。


4. 系统表 pg_publication_sync 设计


4.1 表用途

pg_publication_sync 用于保存 publication 的 DDL 同步控制元数据。

它主要解决的问题是:

  • pg_publication 只描述 publication 本身的通用属性

  • pg_publication_rel 只描述 publication 与 table 的映射关系

  • 对于 DDL 同步,还需要一张单独的 catalog,来表达:

    • publication 在 DDL 维度上的配置
    • publication 对象范围与 DDL 类型之间的映射
    • 后续扩展不同对象类别时的统一元数据载体

因此:

pg_publication_sync 是一张publication 的 DDL 同步控制表


4.2 表属性

建议该表具备如下属性:

  • 系统 catalog
  • 位于 pg_catalog
  • 参与 initdb 初始化
  • 仅内核维护
  • 普通用户不可直接 DML
  • 可由超级用户只读查询用于排障
  • 不属于普通 publication object
  • 不允许用户通过 FOR TABLE 直接操作它

4.3 建议字段设计

既然现在表定位改为“类似 pg_publication 的 catalog 表”,那么你前面那套字段:

  • lsn
  • xid
  • ddl_seqno
  • message_data

这类明显偏运行时事件消息的字段就不适合继续保留在 pg_publication_sync 里了。

这里应该改成配置型字段

建议字段如下:

列号 字段 类型 说明
1 oid oid catalog 行 OID
2 pfsyncpubid oid 关联 pg_publication.oid
3 pfsynckind char 同步对象类型
4 pfsyncnspid oid 关联 schema 的 OID,可为空
5 pfsyncrelid oid 关联 table/relation 的 OID,可为空
6 pfsyncobjid oid 关联对象 OID,可为空
7 pfsyncsubid int4 子类型或细分类别
8 pfsyncenabled bool 是否启用
9 pfsyncddl int4 DDL 类型 bitmask
10 pfsyncextra textjsonb 预留扩展字段

4.4 字段说明

4.4.1 oid

系统 catalog 行 OID,保持与 pg_publicationpg_subscription 等系统表一致风格。


4.4.2 pfsyncpubid

引用 pg_publication.oid,表示该条同步配置属于哪个 publication。

这是该表最核心的归属字段。


4.4.3 pfsynckind

表示这条配置对应的同步对象维度。建议用单字符编码,例如:

  • r:relation/table
  • n:namespace/schema
  • p:publication 级默认项
  • o:generic object

这个字段的作用是区分一条 pg_publication_sync 记录到底描述的是:

  • publication 级全局 DDL 同步配置
  • 某个 schema 范围的 DDL 同步配置
  • 某个 relation 范围的 DDL 同步配置
  • 某个特定对象的 DDL 同步配置

这样后续扩展会比较平滑。


4.4.4 pfsyncnspid

当同步规则绑定到 schema 时,记录 schema OID。

典型对应:

  • FOR TABLES IN SCHEMA ...
  • 后续 schema 级 DDL 对象归属控制

无 schema 语义时可为 InvalidOid


4.4.5 pfsyncrelid

当同步规则绑定到 relation/table 时,记录 relation OID。

典型对应:

  • FOR TABLE ...
  • 表附属对象(index/trigger/rule/sequence)的归属控制

不是表范围时可为 InvalidOid


4.4.6 pfsyncobjid

预留给非 relation 普通对象使用,例如:

  • function
  • type
  • domain
  • extension
  • view

这样设计是为了给未来的非表对象 DDL 同步留下 catalog 表达能力。

若当前实现只先承载 publication/schema/relation 范围,也可以允许该字段为空。


4.4.7 pfsyncsubid

用于表达对象子类或附加类型。例如:

  • index / trigger / rule / sequence 作为 relation 附属对象的子类区分
  • function 重载签名的辅助区分
  • 后续对象细粒度同步控制

当前阶段若暂不需要精细区分,也可以保留字段但先不启用复杂语义。


4.4.8 pfsyncenabled

表示该同步项是否启用。

这样可以支持后续:

  • 逻辑禁用某条规则而不删除行
  • ALTER PUBLICATION ... SET (...) 时平滑更新
  • 调试和升级兼容

4.4.9 pfsyncddl

保存 DDL 类型集合,建议为 bitmask。

pg_publication.pubddl 的区别是:

  • pubddl:publication 全局能力声明
  • pfsyncddl:某条具体同步规则上实际生效的 DDL 类型集合

例如:

  • publication 全局声明支持 table,index,trigger,view,function
  • 某个 table 范围同步项仅允许 table,index,trigger

则具体记录体现在 pfsyncddl 上。


4.4.10 pfsyncextra

预留扩展字段。可选:

  • text
  • jsonb

如果追求 catalog 简洁,建议先用 text 预留;
如果预期很快需要结构化扩展,建议直接用 jsonb

考虑 PostgreSQL catalog 一贯风格,系统表中直接用 jsonb 不是不可以,但会显得偏重。
若当前仅做接口与表设计,建议先用 text 更稳妥。


4.5 推荐建模方式

我建议把 pg_publication_sync 设计成:

pg_publication 的 DDL 同步扩展表

类似于:

  • pg_publication:publication 主表
  • pg_publication_rel:publication 与 relation 的关联表
  • pg_publication_namespace:publication 与 namespace 的关联表
  • pg_publication_sync:publication 与 DDL 同步控制项的关联表

这样结构最清晰,也最符合 PostgreSQL 现有 catalog 风格。


5. 与现有 catalog 的关系


5.1 pg_publication 扩展

pg_publication 中新增字段:

pubddl int4

表示 publication 级别声明允许同步的 DDL 类型集合。

示例:

  • 0:不启用 DDL 同步
  • PUBDDL_TABLE | PUBDDL_INDEX:启用 table/index DDL 同步
  • 更大集合:启用更多对象类型

pubddl 的作用偏向全局总开关 + 能力声明


5.2 pg_subscription 扩展

pg_subscription 中新增字段:

subddl int4

表示 subscription 接受哪些 DDL 类型。

其作用是:

  • 用户接口持久化
  • 建立订阅时做兼容校验
  • 后续运行时作为本地约束边界

5.3 pg_publication_syncpg_publication_rel 的关系

两者不是替代关系,而是分工不同:

  • pg_publication_rel:描述 DML 复制对象范围
  • pg_publication_sync:描述 DDL 同步控制范围

例如:

  • 一张表被加入 publication 的 DML 集合,不代表它的 DDL 一定同步
  • 一条 DDL 同步规则可以引用与 relation 相关的对象范围,但其语义不同于 DML 复制成员关系

因此需要独立建模。


6. 用户接口设计


6.1 CREATE PUBLICATION 扩展

在 publication 参数中增加:

WITH (ddl = 'table,index,sequence,trigger,view,rule,schema,function,type,domain,extension')

6.1.1 参数语义

ddl 表示该 publication 声明允许同步的 DDL 类型集合。

该参数本质上是 publication 的DDL 能力开关


6.1.2 示例

CREATE PUBLICATION pub1
FOR TABLE public.t1
WITH (ddl = 'table,index,trigger');

CREATE PUBLICATION pub2
FOR TABLES IN SCHEMA public
WITH (ddl = 'table,index,view,function,type');

CREATE PUBLICATION pub3
FOR ALL TABLES
WITH (ddl = 'table,index,sequence,trigger,view,rule,schema,function,type,domain,extension');

6.1.3 支持 token

建议一次性支持以下 token:

  • table
  • index
  • sequence
  • trigger
  • view
  • rule
  • schema
  • function
  • type
  • domain
  • extension

6.1.4 解析规则

gram.y 中扩展 publication option 解析:

  1. 读取 ddl 字符串
  2. 按逗号拆分
  3. trim 空白
  4. 标准化大小写
  5. 校验 token
  6. 转换为 bitmask
  7. 存入 pg_publication.pubddl

6.2 ALTER PUBLICATION 扩展

支持:

ALTER PUBLICATION pub1 SET (ddl = 'table,index,trigger');

语义:

  • 更新 pg_publication.pubddl
  • 如有必要,同步调整 pg_publication_sync 中的对应规则项

由于本阶段只讨论接口和系统表,文档里只需定义:

ALTER PUBLICATION 修改 ddl 后,系统 catalog 中 publication 级 DDL 配置必须保持一致。

不需要展开具体同步更新算法。


6.3 CREATE SUBSCRIPTION 扩展

增加:

WITH (ddl = 'table,index,sequence,trigger,view,rule,schema,function,type,domain,extension')

6.3.1 参数语义

ddl 表示该 subscription 允许接收的 DDL 类型集合。

它是订阅端的能力声明与约束条件


6.3.2 示例

CREATE SUBSCRIPTION sub1
CONNECTION '...'
PUBLICATION pub1
WITH (ddl = 'table,index,trigger');

CREATE SUBSCRIPTION sub2
CONNECTION '...'
PUBLICATION pub1, pub2
WITH (ddl = 'table,index,sequence,view,function');

6.3.3 校验规则

在创建 subscription 时,检查:

subddl ⊆ OR(pubddl of all selected publications)

也就是订阅端请求的 DDL 能力不能超出发布端可提供能力。

这里只做接口层校验定义,不展开远端拉取与实现细节。


6.4 ALTER SUBSCRIPTION 扩展

支持:

ALTER SUBSCRIPTION sub1 SET (ddl = 'table,index,view,function');

语义:

  • 更新 pg_subscription.subddl
  • 后续接收 DDL 时按新集合解释

本阶段无需展开运行时行为。


7. ddl 参数的内部表示


7.1 bitmask 定义

建议统一定义:

#define PUBDDL_TABLE      (1 << 0)
#define PUBDDL_INDEX      (1 << 1)
#define PUBDDL_SEQUENCE   (1 << 2)
#define PUBDDL_TRIGGER    (1 << 3)
#define PUBDDL_VIEW       (1 << 4)
#define PUBDDL_RULE       (1 << 5)
#define PUBDDL_SCHEMA     (1 << 6)
#define PUBDDL_FUNCTION   (1 << 7)
#define PUBDDL_TYPE       (1 << 8)
#define PUBDDL_DOMAIN     (1 << 9)
#define PUBDDL_EXTENSION  (1 << 10)

publication 与 subscription 复用同一套定义。


7.2 解析辅助函数

建议增加统一函数:

  • parse_publication_ddl_option(const char *value)
  • parse_subscription_ddl_option(const char *value)
  • ddl_mask_to_string(int mask)

用于:

  • SQL option 解析
  • 错误消息输出
  • catalog 回显

8. pg_publication_sync 的 catalog 建模建议


8.1 文件

新增:

  • src/include/catalog/pg_publication_sync.h
  • src/include/catalog/pg_publication_sync.dat

8.2 索引建议

至少需要:

  1. 主键 OID 索引
  2. pfsyncpubid 上索引

若后续频繁按 relation/schema 查找,可增加:

  • (pfsyncpubid, pfsyncrelid)
  • (pfsyncpubid, pfsyncnspid)

当前阶段文档可先给出推荐,不必过度展开。


8.3 唯一性约束建议

建议至少保证同一 publication 下,同一同步对象范围不会重复定义。

可抽象为:

(pfsyncpubid, pfsynckind, pfsyncnspid, pfsyncrelid, pfsyncobjid, pfsyncsubid)

唯一。

这样可以避免同一个 publication 下生成重复控制项。


9. 推荐的语义关系

可以把三层关系理解为:

9.1 pg_publication.pubddl

回答问题:

这个 publication 总体上支持哪些 DDL 类型?


9.2 pg_subscription.subddl

回答问题:

这个 subscription 总体上接受哪些 DDL 类型?


9.3 pg_publication_sync

回答问题:

这个 publication 的 DDL 同步规则具体落在哪些范围/对象上?


10. 建议的 SQL 语义说明

为了让接口更清晰,建议在文档中明确:

10.1 ddl 是能力声明,不是逐条事件记录

用户执行:

CREATE PUBLICATION pub1
FOR TABLE public.t1
WITH (ddl = 'table,index');

表达的是:

  • pub1 开启了 table/index 类别的 DDL 同步能力
  • 该能力与 public.t1 这个 publication 范围相关联
  • 系统会把相关元数据存入 pg_publicationpg_publication_sync

而不是:

  • 立即生成任何 DDL 事件
  • 或者记录运行时消息

10.2 pg_publication_sync 是配置表,不是运行时流水表

用户查询该表,看到的应该是:

  • publication 级控制项
  • 某个 table/schema/object 范围对应的 DDL 配置

而不是历史发生过的每条 DDL。


11. 权限与可见性设计

建议:

  • 普通用户不可直接 DML pg_publication_sync
  • 超级用户可读
  • publication owner / subscription owner 是否可读可根据现有系统表可见性策略决定
  • 任何用户 SQL 对其 INSERT/UPDATE/DELETE 应报错或被拦截

这与 pg_publication 作为系统控制表的定位一致。


12. 示例


12.1 创建 publication

CREATE PUBLICATION pub_sales
FOR TABLE public.orders
WITH (ddl = 'table,index,trigger');

含义:

  • publication pub_sales 开启 DDL 同步能力
  • 支持类型:table/index/trigger
  • 系统在 pg_publication.pubddl 中记录总体能力
  • pg_publication_sync 中记录与 public.orders 对应的 DDL 同步控制项

12.2 创建 schema 范围 publication

CREATE PUBLICATION pub_app
FOR TABLES IN SCHEMA app
WITH (ddl = 'table,index,sequence,view,function,type');

含义:

  • publication 在 schema app 范围启用这些 DDL 类型
  • pg_publication_sync 中记录 schema 级同步控制项

12.3 创建 subscription

CREATE SUBSCRIPTION sub_app
CONNECTION 'host=... dbname=... user=...'
PUBLICATION pub_app
WITH (ddl = 'table,index,view,function');

含义:

  • 本地订阅端只接收 table/index/view/function 类别的 DDL
  • pg_subscription.subddl 保存此配置
  • 创建时要求这些类型不超出发布端 pubddl 能力范围

13. 本设计的收益

收敛到“系统表 + 接口”之后,这个方案的收益很明确:

  1. 先冻结 SQL 接口
    后续实现不会反复改语法。

  2. 先冻结 catalog 模型
    后续 DDL 捕获与 apply 逻辑有稳定元数据基础。

  3. 与现有 publication 架构一致
    pg_publication_sync 类似 pg_publication_rel 的扩展控制表,便于理解与维护。

  4. 避免把配置模型和运行时事件模型混在一起
    不再让 pg_publication_sync 承担消息流水职责,语义更清晰。


14. 建议的最终结论

基于你现在收敛后的目标,建议最终采用下面这套思路:

14.1 保留 pubddl/subddl

  • pg_publication.pubddl
  • pg_subscription.subddl

作为用户接口参数落盘位置。

14.2 重定义 pg_publication_sync

把它定义成:

publication 的 DDL 同步控制 catalog 表

而不是 DDL 事件日志表。

14.3 字段设计采用“配置型字段”

不要再用:

  • lsn
  • xid
  • ddl_seqno
  • message_data

这类运行时消息字段。

而应该使用:

  • pubid
  • kind
  • nspid
  • relid
  • objid
  • ddl mask
  • enabled

这类 catalog 控制字段。


文章作者: growdu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 growdu !
  目录
分类导航
随笔2 AI27 算法1 计算机基础13 博客搭建7 ChatGPT2 集群63 计算机通信1 数据库34 数据库深入80 DPDK26 Docker11 Elasticsearch4 编辑工具4 FAQ1 Go Web1 hometown2 编程语言16 网络9 OPC1 Linux38 openGauss4 页面12 PostgreSQL54 程序员自我修养1 协议11 成长之路1 stock1 存储5 工具20 VPP18 视频作品1 Vue13 Web1 代码示例11 数据库15 BenchmarkSQL1 PostgreSQL 源码修炼之路14
最热文章
1
13 逻辑复制深入
数据库深入🔥 1570
2
0 Postgresql存储、索引及系统优化、主备切换
PostgreSQL🔥 1495
3
一文读懂openguass dcf网络模块
集群🔥 1420
4
逻辑复制源码分析
数据库深入🔥 1327
5
PostgreSQL 分区表:从一行 `PARTITION BY` 到路由热路径的全链路拆解
数据库🔥 1094
6
applyparallelworker.c 之 LA 端源码深度解析:Leader Apply Worker 的指挥中枢
数据库深入🔥 1082
7
PostgreSQL Background Worker 全解:从 `RegisterBackgroundWorker` 到逻辑复制 4 类 worker 的全生命周期
数据库🔥 1078
8
PostgreSQL的后台进程walsender分析 - 关系型数据库 - 亿速云
PostgreSQL🔥 1033
9
PostgreSQL 逻辑复制的监控:六张视图 + 一组可执行 SQL,把 publisher/subscriber 的速率与健康度彻底看透
数据库🔥 1032
10
PostgreSQL 逻辑复制支持 DDL 之后:DDL 与 DML 的时序难题(重点:分区表)
数据库🔥 999
11
reorderbuffer.c 源码深度解析:PostgreSQL 逻辑复制的"事务重组引擎
数据库深入🔥 953
12
PostgreSQL 内核开发:读取一张表的 9 步标准流程与缓存全景
数据库🔥 938
13
从 `postgres` 二进制到生产级守护 —— PostgreSQL 最外层模块与启动全流程拆解
数据库🔥 936
14
支持逻辑复制同步 DDL 适配 SQL Server 方案
数据库深入🔥 934
15
PostgreSQL 逻辑复制的 ReorderBuffer 与事务机制:从一行 WAL 到一致性变更流的全链路绑定
数据库🔥 913
16
DDL同步架构(美化版)
数据库深入🔥 908
17
PostgreSQL Latch 机制详解:从一行 SetLatch 到 epoll 的内核之旅
数据库🔥 871
18
pgbench 源码全解:一个 C 文件如何撑起 PostgreSQL 官方压测工具
数据库🔥 860
19
PostgreSQL libpq 机制与缓冲区详解
数据库🔥 850
20
PostgreSQL 逻辑复制 spill 文件深度剖析:从 `xid-*.spill` 到 TPC-C 的增长方程
数据库🔥 845