【GaussDB】在逻辑复制中剔除指定用户的事务

发布于:2025-08-31 ⋅ 阅读:(26) ⋅ 点赞:(0)

【GaussDB】在逻辑复制中剔除指定用户的事务

1. 需求背景

在逻辑复制中,期望对源端指定用户的所有操作不复制到目标端。然而WAL日志中没有用户信息可用于过滤,因此考虑结合审计日志来实现这一需求。

2. 解决方案

2.1 配置审计日志

首先需要开启事务号记录和指定用户的全量审计:

--开启记录事务号 
gs_guc reload -c "audit_xid_info=1"

--配置指定用户开启全量审计
gs_guc reload -c "full_audit_users='ogadmin'"

2.2 查询指定用户的事务号

通过以下SQL查询指定用户的所有事务号:

--查询指定用户的所有事务号
with t as (
select case when substr(detail_info,1,3)='xid' 
            and substr(detail_info,4,3)<>'=NA'  
           then regexp_replace(detail_info,'xid=(\d+),.*','\1') end as transactionid, 
       * from gs_query_audit('19000101','30000101') where username='ogadmin' )
       select * from t where transactionid is not null;

查询结果示例:

transactionid time type result userid username database client_conninfo object_name detail_info node_name thread_id local_port remote_port
928506 2025-06-26 13:34:22.000 +0800 ddl_table ok 16728 ogadmin postgres gsql@[local] t7 xid=928506, create table t7(a int); primary 140316575266560@804231262475203 7456 null
928810 2025-06-26 13:40:41.000 +0800 ddl_schema ok 16728 ogadmin postgres gsql@[local] s2 xid=928810, create schema s2; primary 140316575266560@804231641079130 7456 null
929001 2025-06-26 13:44:46.000 +0800 dml_action ok 16728 ogadmin postgres gsql@[local] t7 xid=929001, insert into t7 values (1); primary 140316575266560@804231886663356 7456 null

2.3 审计日志事务号提取问题分析

GaussDB的ADM_AUDIT_TRAIL视图中已尝试截取出事务号,但该视图的SQL存在问题:

CASE
    WHEN "position"(a.detail_info, 'xid = NA'::text) = 1 AND "position"(a.detail_info, 'xid'::text) = 1 THEN NULL::text
    ELSE substr(a.detail_info, "position"(a.detail_info, '='::text) + 1, "position"(a.detail_info, ','::text) - "position"(a.detail_info, '='::text) - 1)
END AS transactionid

主要问题:

  1. 实际日志中是xid=NA而非xid = NA,导致第一段条件永远无法匹配
  2. 当detail_info中存在其他=号时(如配置guc参数),会错误截取非事务号内容(如'bind_procedure_searchpath

因此需要自行从原始detail_info中截取事务号。

3. 方案实施挑战

3.1 关键问题分析

实施此方案需要解决以下核心问题:

  1. 审计日志完整性:如何确保审计日志记录是完整的?
  2. 逻辑嵌入:如何将剔除事务的逻辑嵌入到原有的逻辑复制程序中去?
  3. 操作顺序:如何确保各项操作的先后顺序?
  4. 资源消耗:如何减少重复查询审计日志的资源消耗(可否直接基于操作系统的存储接口去监控审计日志文件的变化)?

4. 替代方案:基于事务标签的过滤机制

PostgreSQL在双向复制中,通过在事务上标记名称来区分源端事务,防止循环复制,GaussDB同样具有此能力:

gaussdb 已移除pg_recvlogical二进制程序,仅保留了相关接口,为测试方便,这里实际是使用MogDB测试的
# 服务端
--创建一个复制槽
pg_recvlogical -d postgres -S test_slot --create

--创建复制源标签
select pg_replication_origin_create('maintain_node');

# 采集端
--开启流式解码
pg_recvlogical -d postgres -S test_slot --start -v -f -

## sql执行客户端
--执行一些SQL,采集端可以捕获到数据
 
--绑定当前会话标签
select pg_replication_origin_session_setup ('maintain_node');

---执行一些SQL,采集端没有捕获到数据

--解绑当前会话标签
select pg_replication_origin_session_reset ();

--执行一些SQL,采集端可以捕获到数据

# 环境清理
pg_recvlogical -d postgres -S test_slot --drop
select pg_replication_origin_drop('maintain_node');

原理:逻辑复制输出插件(如pgoutput、pg_recvlogical或test_decoding)默认会忽略携带复制源标签的事务,因为这些事务被视为"已同步过的数据",避免重复复制。

可以通过创建登录事件触发器,针对指定用户的登录自动打上复制源标签,从而实现指定的用户事务不复制。

5. 推荐方案:历史表归档策略

我个人不认为数据复制软件是用来处理这种归档操作的。比较通用的方式是:

  1. 源库对需要归档的表,在当前库建立历史表
  2. 归档时将需要归档的数据插入到历史表,然后删除当前表已归档的数据
  3. 对于历史表的修改操作(一般只有运维delete或truncate),配置同步规则,不进行数据删除的同步
  4. 可新建一个schema存放历史表,便于在复制软件中进行规则配置
  5. 复制软件针对历史表进行增量复制,对于实时表进行全量复制(其实这是ETL该干的事了)

虽然插入历史表会产生额外IO,可能使数据归档操作时间翻倍,但相比剔除事务不同步的方式,历史表方式更加安全,避免剔除事务时遗漏某些关键事务。

6.总结

本文探讨了在GaussDB中实现指定用户操作不复制到目标端的三种方案:

  1. 基于审计日志的事务过滤
    审计日志方案通过提取事务号实现过滤,但面临审计日志完整性、逻辑嵌入复杂性、操作顺序保证和资源消耗等挑战。虽然技术上可行,但实施复杂度较高,且存在事务遗漏风险。
  2. 基于逻辑复制标签的过滤
    基于逻辑复制标签实现过滤,技术上可行,但打标签这个附加操作需要在执行sql前执行(除非使用触发器,但触发器属于高风险操作,不建议使用),如果漏执行,将会存在错误覆盖目标库的风险。
  3. 基于历史表的归档策略
    历史表归档方案通过在源库建立历史表存储归档数据,配置复制规则排除历史表的删除操作,虽然会增加IO开销,但实现简单、安全性高,避免了事务过滤可能带来的风险。

推荐实践:采用历史表归档策略,新建独立schema存放历史表,便于复制规则配置。这种方法既满足了数据归档需求,又保证了复制数据的安全性和一致性,是更可靠的长期解决方案。


网站公告

今日签到

点亮在社区的每一天
去签到