—— 新增系统表 pg_publication_sync 与用户接口扩展
1. 设计目标
本设计的目标不是实现完整 DDL 自动复制链路,而是先补齐以下两项基础能力:
新增系统 catalog 表
pg_publication_sync
用于在内核中持久化 publication 维度的 DDL 同步控制信息。扩展 publication / subscription 用户接口
在CREATE/ALTER PUBLICATION与CREATE/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_publicationpg_subscriptionpg_publication_rel
而不是:
- 运行时 message queue
- 审计流水表
- DDL 事件日志表
2.2 用户接口先行
本阶段首先冻结用户接口和 catalog 结构,确保:
- SQL 语法明确
- 参数语义明确
- catalog 模型稳定
- 后续捕获与应用逻辑可以在此基础上继续扩展
2.3 与现有逻辑复制模型兼容
本设计沿用现有逻辑复制控制面风格:
- publication 保存“发布侧配置”
- subscription 保存“订阅侧配置”
- 新增
pg_publication_sync保存“DDL 同步维度的附加控制信息”
尽量避免把 DDL 设计成与现有 publication 体系完全独立的一套机制。
3. 总体方案
整体上引入三类元数据:
pg_publication.pubddl
表示该 publication 开启了哪些 DDL 类型。pg_subscription.subddl
表示该 subscription 允许接收哪些 DDL 类型。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 表”,那么你前面那套字段:
lsnxidddl_seqnomessage_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 |
text 或 jsonb |
预留扩展字段 |
4.4 字段说明
4.4.1 oid
系统 catalog 行 OID,保持与 pg_publication、pg_subscription 等系统表一致风格。
4.4.2 pfsyncpubid
引用 pg_publication.oid,表示该条同步配置属于哪个 publication。
这是该表最核心的归属字段。
4.4.3 pfsynckind
表示这条配置对应的同步对象维度。建议用单字符编码,例如:
r:relation/tablen:namespace/schemap: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
预留扩展字段。可选:
textjsonb
如果追求 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_sync 与 pg_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:
tableindexsequencetriggerviewruleschemafunctiontypedomainextension
6.1.4 解析规则
在 gram.y 中扩展 publication option 解析:
- 读取
ddl字符串 - 按逗号拆分
- trim 空白
- 标准化大小写
- 校验 token
- 转换为 bitmask
- 存入
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.hsrc/include/catalog/pg_publication_sync.dat
8.2 索引建议
至少需要:
- 主键 OID 索引
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_publication与pg_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. 本设计的收益
收敛到“系统表 + 接口”之后,这个方案的收益很明确:
先冻结 SQL 接口
后续实现不会反复改语法。先冻结 catalog 模型
后续 DDL 捕获与 apply 逻辑有稳定元数据基础。与现有 publication 架构一致
pg_publication_sync类似pg_publication_rel的扩展控制表,便于理解与维护。避免把配置模型和运行时事件模型混在一起
不再让pg_publication_sync承担消息流水职责,语义更清晰。
14. 建议的最终结论
基于你现在收敛后的目标,建议最终采用下面这套思路:
14.1 保留 pubddl/subddl
pg_publication.pubddlpg_subscription.subddl
作为用户接口参数落盘位置。
14.2 重定义 pg_publication_sync
把它定义成:
publication 的 DDL 同步控制 catalog 表
而不是 DDL 事件日志表。
14.3 字段设计采用“配置型字段”
不要再用:
lsnxidddl_seqnomessage_data
这类运行时消息字段。
而应该使用:
pubidkindnspidrelidobjidddl maskenabled
这类 catalog 控制字段。