代表性面试主题

后端面试:如何安全使用 PostgreSQL 逻辑复制的行过滤与列列表?

后端困难
Offer.cc 编辑团队发布 更新

题干

一个 PostgreSQL 发布端要把部分租户和部分列复制到订阅端。请解释 row filter 与 column list 的语义、UPDATE 的边界转换、初始同步和分区表行为,并给出安全与运维方案。

题干与适用场景

一个 PostgreSQL 18 发布端要把部分租户和部分列复制到订阅端,订阅端用于报表和区域服务。请解释 row filter 与 column list 的语义、UPDATE 进入或离开过滤集合时的转换、初始同步、分区表行为,并设计安全与运维方案。

PostgreSQL 文档说明,row filter 在发布前判断行是否满足条件;column list 控制复制列但不是安全边界。面试重点是复制语义、数据一致性和变更治理,不能只回答“在 publication 上加 WHERE”。

面试官考察点

面试官会看你能否说明过滤表达式由复制连接角色执行、falseNULL 不复制、TRUNCATE 不受影响;能否解释 UPDATE 旧行和新行分别匹配时的 UPDATE/INSERT/DELETE 转换;能否处理 replica identity、初始同步、多个 publication 的 OR 语义、分区根和列列表演进;能否识别 column list 不能替代保密控制。

回答前需要澄清的问题

复制目标

确认订阅端需要哪些租户、操作类型和列,是否允许初始快照,是否需要双向写入,以及订阅端版本是否低于 15 或 18。

身份与过滤

确认表的 replica identity、过滤列是否会更新、分区是否使用根表发布,以及多个 publication 是否覆盖同一张表。

安全与恢复

确认敏感列是否必须完全不可见、复制连接角色权限、网络隔离、重建订阅和过滤规则误配时的回滚窗口。

30 秒回答框架

“row filter 决定哪些行的变更进入发布,falseNULL 会丢弃该变更;column list 只减少复制列,不能当作安全边界。UPDATE 要同时评估旧行和新行:未匹配到匹配变成 INSERT,匹配到未匹配变成 DELETE,双方匹配才是 UPDATE。过滤涉及 UPDATE/DELETE 时,表达式列和 column list 必须覆盖 replica identity。上线前我会冻结 publication 变更,验证初始同步、分区、多个过滤器 OR 合并和旧版本行为,并把敏感数据保护放在发布端权限与视图层。”

分步骤深入解答

第一步:定义发布不变量

为每张表记录允许的租户集合、操作类型、列集合、过滤表达式和订阅端版本。明确目标是性能裁剪、行为隔离还是安全保护;安全目标不能只依赖 column list。

第二步:设计 row filter

过滤表达式在发布前执行,使用 replication connection 对应角色;只允许文档规定的简单表达式。表达式为 falseNULL 时不复制,TRUNCATE 不受过滤影响。发布 UPDATE/DELETE 时,过滤列必须由 replica identity 覆盖;仅发布 INSERT 时可使用其他列。

第三步:推导 UPDATE 转换

更新前后都计算过滤条件。旧行和新行都匹配时发送 UPDATE;都不匹配时不发送;只有新行匹配时向订阅端发送 INSERT;只有旧行匹配时发送 DELETE。订阅端因此保持“当前满足过滤条件的行集合”,而不是简单复现所有 UPDATE。

第四步:限制 column list

column list 要包含 replica identity 需要的列,订阅表至少具备所有发布列。列表顺序不影响复制。没有列表表示未来新增列也会自动复制;显式列出全部列则不会自动包含后续新增列。多个 publication 对同表使用不同列列表可能导致订阅无法恢复,应在变更前检查。

sql
CREATE PUBLICATION tenant_eu
  FOR TABLE orders (order_id, tenant_id, status, total)
  WHERE (tenant_id = 'eu');

第五步:处理初始同步

初始同步会按 row filter 复制满足条件的既有行,按 column list 复制选定列。但旧于 15 的订阅端可能复制整表,旧于 18 的版本对 generated column 也有额外限制。切换前要检查版本矩阵和快照期间的写入积压。

第六步:处理分区与多个 publication

publish_via_partition_root=true 时使用根分区表的过滤器或列列表;默认 false 时按各分区定义处理。同一表在多个 publication 中的 row filter(同一 publish 操作)按 OR 合并,未过滤的 publication 会使其他过滤器失去裁剪效果。

第七步:建立运维护栏

把 publication 定义纳入迁移审查,记录过滤命中率、复制延迟、冲突、初始同步行数和规则版本。变更前在影子订阅验证旧行/新行边界;发现泄露或漏数时暂停订阅、修正 publication,再按一致性检查结果重建。

高质量示范回答

我会把 row filter 当作发布集合定义,把 column list 当作传输裁剪。过滤表达式由复制连接角色执行,false/NULLTRUNCATE 的语义要单独验证。UPDATE 必须比较旧行、新行,跨边界时转换为 INSERT 或 DELETE;涉及 UPDATE/DELETE 时保证过滤列和发布列覆盖 replica identity。初始同步、分区根和多个 publication 的 OR 合并都要纳入测试。敏感数据仍由发布端权限、视图或独立脱敏管道保护,column list 只作为性能与数据形状控制。

常见错误

  • 错误表现: 认为 column list 能阻止恶意订阅者读取未发布列。→ 失败原因: 官方文档明确它不是安全机制。→ 修正方法: 在发布端权限、视图或脱敏层实施保密控制。
  • 错误表现: 只按新行判断 UPDATE。→ 失败原因: 行可能离开或进入过滤集合。→ 修正方法: 评估旧/新两次并按四种结果转换。
  • 错误表现: 忽略 replica identity。→ 失败原因: UPDATE/DELETE 无法安全定位旧行。→ 修正方法: 检查身份列是否包含在过滤表达式和 column list 中。
  • 错误表现: 多个 publication 各自看起来很窄就认为总复制量很小。→ 失败原因: 同一 publish 操作的过滤器会 OR 合并。→ 修正方法: 计算合并后的集合并拒绝无过滤覆盖。

追问及应对

什么时候用 row filter,什么时候拆 publication?

租户或区域边界稳定且表达式简单时可用 row filter;若权限、生命周期和运维责任不同,拆成独立 publication 与订阅更容易审计。两者都要评估初始同步和规则变更成本。

新增列怎样避免意外复制?

使用显式 column list 并在 schema 变更审查中更新列表;不要把“显式列出当前全部列”和“未指定列表”混为一谈,后者会自动包含未来列。

如何验证 UPDATE 跨边界?

构造旧不匹配/新匹配、旧匹配/新不匹配、双方匹配和双方不匹配四组更新,分别核对订阅端 INSERT、DELETE、UPDATE 和无事件结果。

过滤能否替代多租户安全隔离?

不能。它是逻辑复制的数据选择机制;敏感数据隔离仍需要发布端最小权限、独立凭证、网络控制和必要的脱敏。

公开来源

同类题目