Debezium PostgreSQL CDC:从 publication、slot 与 LSN 到可恢复变更流
一条停摆的连接器为什么能写满数据库磁盘
一次迁移演练进入增量追平阶段后,目标端不再出现新订单,源库业务却仍能正常提交。值班同学重启了 Kafka Connect,任务依旧失败;两小时后,源库 pg_wal 所在磁盘逼近满载。最危险的误判是“连接器已经停了,所以它不再消耗数据库资源”。事实恰好相反:只要逻辑复制槽还在,PostgreSQL 就可能为了这个尚未确认消费进度的槽保留 WAL。
排查不能只看 Connect 的 RUNNING/FAILED。先在源库读取真正的状态:
SELECT slot_name,
active,
restart_lsn,
confirmed_flush_lsn,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots
WHERE slot_name = 'cdc_lab_slot';active=false 说明当前没有客户端使用槽,不代表槽失效;restart_lsn 是 PostgreSQL 仍可能需要保留 WAL 的下界,confirmed_flush_lsn 是消费者已确认处理到的位置。当前 WAL LSN 与 restart_lsn 的差值持续增长,才是“这个槽正在把磁盘预算吃掉”的直接证据。删除槽会立刻解除保留责任,但也会切断原位续传能力,因此不能把 pg_drop_replication_slot 当作磁盘告警的第一反应。
Debezium 的 PostgreSQL 连接器把数据库事务日志转换成 Kafka Connect 记录。它默认是至少一次交付:崩溃窗口内已经写入 Kafka、但尚未持久化 offset 的记录可能在恢复后重放。架构上要同时守住三条线:PostgreSQL 的 slot/LSN、Connect 的 offset、下游按业务键或事件键收敛重复的能力。只盯其中一条,事故就会以“看起来续上了,实际漏了或重了”的方式出现。
publication、slot、LSN 和 offset 各自保存什么
publication 是逻辑复制的对象集合与动作过滤器,决定哪些表的 INSERT/UPDATE/DELETE/TRUNCATE 能进入发布流;它不保存消费进度。replication slot 是数据库端的消费身份与保留责任,防止消费者尚未确认的 WAL 被回收;一个槽只能被一个活跃消费者占用。LSN 是 WAL 中的位置,不是业务时间,也不是全局事件编号。Connect offset 则把连接器已经交给框架并确认的源位置持久化到 offset 存储中。
这四个对象的协作顺序如下:
Debezium 事件中的 source.lsn 帮助定位源位置,op 区分快照读取 r、新增 c、更新 u 和删除 d。但事件出现 source.lsn 不等于 Connect offset 已经持久化,也不等于下游已经提交。LSN 是 WAL 字节位置,不是逐事件加一的连续序号;事务、WAL 记录类型和未被 publication 捕获的写入都会让相邻业务事件的 LSN 出现数值间隔,因此不能用“LSN 不连续”判断丢数。恢复核对应把 Connect offset 中的源分区与 LSN、数据库中同一 slot 的状态、事件事务标识以及目标端业务键放到一起:offset 能在 slot 仍保留的 WAL 范围内恢复,并且故障窗口内各事务按业务键投影后的最终状态与源端一致,才构成可接管证据。
PostgreSQL 的逻辑解码按事务提交输出,长事务会让可回收位置长期不前进;大事务还可能在提交瞬间形成突发流量。tasks.max 调大也不能把一个 PostgreSQL connector 横向拆成多个读取任务,Debezium PostgreSQL Connector 文档明确该连接器始终使用单任务。要扩展吞吐,通常应先减少单库捕获面、拆分数据库或连接器,并核对跨连接器事务顺序是否仍满足业务约束。
用稳定发行线搭起本地验证链
下面的配置以 Debezium stable 文档当前对应的 3.6 发行线为基线,使用 PostgreSQL 原生 pgoutput。Debezium 连接器要求 Java 17 或更高版本;Debezium Server、Operator、Outbox 与 Quarkus 扩展要求 Java 21 或更高版本。项目采用 Apache License 2.0,仍要为 PostgreSQL JDBC 驱动、Kafka 客户端和企业附加插件分别生成依赖与许可证清单。pgoutput 无需安装第三方解码插件。Debezium 可以作为 Kafka Connect 插件、Debezium Server 或嵌入式 Engine 运行;需要 Connect 的 REST 管理、分布式 offset 与 Kafka topic 时,Kafka Connect 是最直接的入口。版本与运行时组合以官方发行概览为准,安装包和容器入口以官方安装说明为准,不要把 nightly、候选版或仓库主分支示例混入稳定环境。
本地实验需要 Docker、一个可用的 Kafka Connect 集群,以及能修改 PostgreSQL 实例参数的账号。先启动合成源库:
services:
postgres:
image: postgres:17
ports:
- "5432:5432"
environment:
POSTGRES_DB: cdc_lab
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${PG_ADMIN_PASSWORD:-local-admin-only}
command:
- postgres
- -c
- wal_level=logical
- -c
- max_replication_slots=8
- -c
- max_wal_senders=8
- -c
- max_slot_wal_keep_size=2GB
volumes:
- pg-cdc-data:/var/lib/postgresql/data
volumes:
pg-cdc-data:wal_level=logical 让 WAL 携带逻辑解码所需信息;max_replication_slots 与 max_wal_senders 是实例级容量,不应按“当前只建一个槽”卡死,因为备库、其他订阅和维护任务也可能占用。max_slot_wal_keep_size 是失控保护,不是无损承诺:槽需要的 WAL 超过上限并在检查点后被移除时,槽可能失去续传所需片段,最终只能重新建立基线。具体行为应同时核对 PostgreSQL 复制槽视图中的 wal_status、safe_wal_size 与实例实际版本。
若使用官方 Debezium Connect 容器,插件已在镜像中;若使用自建 Connect,把 PostgreSQL connector 插件解压到 worker 的 plugin.path 下,然后重启 worker,并用 GET http://localhost:8083/connector-plugins 确认 io.debezium.connector.postgresql.PostgresConnector 可见。只把 JAR 放进某个 task 容器而没有同步到全部 worker,会在再均衡后变成“原 worker 正常,新 worker 找不到类”的间歇故障。
最小权限不是给复制账号一个 superuser
生产连接器应使用专用登录角色。复制角色需要 LOGIN、REPLICATION、连接数据库以及读取被捕获表的权限。若让 Debezium 自动创建 publication,还需要数据库 CREATE 和满足 publication 所有权规则的权限,实际往往比团队愿意授予的范围更大。更容易审计的做法是由数据库管理员预先创建 publication,连接器只使用它:
CREATE ROLE cdc_reader LOGIN REPLICATION PASSWORD '<CDC_PASSWORD>';
GRANT CONNECT ON DATABASE cdc_lab TO cdc_reader;
\c cdc_lab
CREATE SCHEMA IF NOT EXISTS sales;
CREATE TABLE sales.orders (
id bigint PRIMARY KEY,
customer_ref text NOT NULL,
amount numeric(12,2) NOT NULL,
status text NOT NULL,
updated_at timestamptz NOT NULL DEFAULT clock_timestamp()
);
GRANT USAGE ON SCHEMA sales TO cdc_reader;
GRANT SELECT ON TABLE sales.orders TO cdc_reader;
CREATE TABLE public.cdc_heartbeat (
id integer PRIMARY KEY,
touched_at timestamptz NOT NULL
);
GRANT SELECT, INSERT, UPDATE ON TABLE public.cdc_heartbeat TO cdc_reader;
CREATE PUBLICATION cdc_lab_pub
FOR TABLE sales.orders, public.cdc_heartbeat;表的 SELECT 用于快照;REPLICATION 用于逻辑复制连接和槽。后续新增表不能只改 table.include.list,还要把表加入 publication,并授予 schema/table 读取权限。若缺任一环,常见表现分别是快照 permission denied for table、增量流没有新表事件,或 connector 创建 publication 失败。
网络侧在 pg_hba.conf 只放行 Connect 实际网段、实际数据库和专用角色。逻辑复制是连接到具体数据库,不应把这里写成物理复制使用的伪数据库 replication。假设 Connect 节点位于 10.42.16.0/24,可把下面规则放在更宽泛的 host ... all ... 规则之前:
# TYPE DATABASE USER ADDRESS METHOD
hostssl cdc_lab cdc_reader 10.42.16.0/24 scram-sha-256pg_hba.conf 自上而下使用第一条同时匹配连接类型、客户端地址、数据库和用户的记录;认证失败后不会继续尝试后面的规则。若前面已有匹配该网段的 reject 或其他认证规则,上面的专用规则永远不会生效。hostssl 还要求服务端已经启用 TLS;客户端应使用 verify-full 校验 CA 与主机名,而不是只把链路加密却忽略服务端身份。前面的 PostgreSQL 容器骨架没有生成测试证书,注册连接器前必须给实例挂载受信 CA 签发的服务端证书,并让 database.hostname 与证书 SAN 一致;否则 verify-full 按设计失败。不要为了绕过这个失败改写成 host all all 0.0.0.0/0 trust,那既扩大攻击面,也会让本地成功掩盖生产认证差异。凭证放入 Connect 支持的 ConfigProvider、编排平台 Secret 或外部密钥服务,不把明文 REST 请求留在 shell history、Git 和工单附件中。
注册连接器时让配置字段能回到故障现象
将以下 JSON 保存为临时文件后提交到本地 Connect REST;文件中的密码仍是占位符,实际环境应通过 ConfigProvider 引用:
{
"name": "cdc-lab-postgres",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"tasks.max": "1",
"database.hostname": "postgres",
"database.port": "5432",
"database.user": "cdc_reader",
"database.password": "<CDC_PASSWORD>",
"database.dbname": "cdc_lab",
"database.sslmode": "verify-full",
"topic.prefix": "lab.pg",
"plugin.name": "pgoutput",
"slot.name": "cdc_lab_slot",
"publication.name": "cdc_lab_pub",
"publication.autocreate.mode": "disabled",
"table.include.list": "sales.orders",
"snapshot.mode": "initial",
"heartbeat.interval.ms": "10000",
"heartbeat.action.query": "INSERT INTO public.cdc_heartbeat(id, touched_at) VALUES (1, clock_timestamp()) ON CONFLICT (id) DO UPDATE SET touched_at = EXCLUDED.touched_at",
"tombstones.on.delete": "true"
}
}topic.prefix 参与 topic 与 offset 身份,不能在恢复时随意改名;改名可能让连接器以新逻辑身份启动。slot.name 必须在同一 PostgreSQL 集群中唯一,并符合小写字母、数字和下划线规则。publication.autocreate.mode=disabled 表示 publication 生命周期由 DBA 管理,能够避免连接器因为捕获表达式变化而自动扩大对象面。snapshot.mode=initial 在没有既有 offset 时做一致性快照,有 offset 时从记录的 LSN 继续。
这里要区分两种 heartbeat。heartbeat.interval.ms 让 Debezium 定期向 __debezium-heartbeat.<topic.prefix> 发送 Kafka heartbeat 记录,并给连接器提交已读取 LSN 的机会;但在“同一 PostgreSQL 实例的其他数据库很忙、cdc_lab 很空闲”时,只有 Kafka heartbeat 不会在 cdc_lab 产生可解码 WAL。heartbeat.action.query 由连接器按相同节奏在 cdc_lab 执行一次幂等 upsert,public.cdc_heartbeat 又被加入 cdc_lab_pub,这次数据库变更才会经过当前 slot,使低流量数据库有机会推进确认位置。动作表可以继续被 table.include.list 排除,避免形成业务 topic,但不能从 publication 删除,也不能撤掉执行查询所需的表权限。
heartbeat 的正向证据不是“topic 每十秒多一条消息”,而是连续多个周期内 confirmed_flush_lsn 前移,pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) 不再单调增长,并且业务 topic 没有出现 heartbeat 表事件。把 heartbeat 表移出 publication 作为反例后,action query 仍会成功、Kafka heartbeat 也仍可能出现,但低流量数据库的 slot 水位不再因这张表推进;恢复 publication 后应重新观察到水位前移。这个实验能把“连接器活着”和“当前数据库正在帮助释放 WAL”分开。
提交与观察命令如下:
curl -fsS -X POST http://localhost:8083/connectors \
-H 'Content-Type: application/json' \
--data @cdc-lab-postgres.json
curl -fsS http://localhost:8083/connectors/cdc-lab-postgres/status
curl -fsS http://localhost:8083/connectors/cdc-lab-postgres/config预期状态中 connector 与 task 都是 RUNNING。HTTP 201 只证明配置对象被创建,不证明 task 已成功连接数据库。失败时先读 task 的 trace,再到 PostgreSQL 查槽和 publication;这比反复删除重建 connector 更能保留证据。
正向实验:证明快照与增量在同一水位接续
在启动连接器前插入基线行,再启动连接器并执行增删改:
INSERT INTO sales.orders(id, customer_ref, amount, status)
VALUES (1001, 'C-LAB-01', 88.50, 'CREATED');
UPDATE sales.orders
SET status = 'PAID', updated_at = clock_timestamp()
WHERE id = 1001;
INSERT INTO sales.orders(id, customer_ref, amount, status)
VALUES (1002, 'C-LAB-02', 19.90, 'CREATED');
DELETE FROM sales.orders WHERE id = 1002;在 Kafka 消费 lab.pg.sales.orders 时,预期先看到 id=1001 的快照事件 op=r,随后看到更新 op=u、新增 op=c 和删除 op=d;开启 tombstone 后,删除记录之后还会有同 key、value 为 null 的清理记录。快照期间发生的更新不能被快照旧值永久覆盖,连接器会在一致性快照与 WAL 起点之间建立接续关系。
验证不能停在事件数量。把每条事件的 key、op、source.lsn、source.snapshot、事务标识和 Kafka partition/offset 输出到临时审计表。对 id=1001,最终投影应是 PAID;对 id=1002,最终应不存在。若只按到达顺序盲目 upsert,却没有正确处理 delete/tombstone,行数可能“差不多”,业务状态仍然错误。
重启 connector 后再更新 id=1001。预期它从既有 offset 与同一 slot 继续,而不是重新发出整表 r。少量尾部事件可能重复,下游应按业务主键和可比较版本处理。把事件的 Kafka offset 当全局版本也不成立:不同 partition 之间不可直接比较,topic 重建后语义也会改变。
反向实验:故意制造权限缺口和 WAL 积压
第一组反例是把 publication.name 改为不存在的 cdc_missing_pub,同时保留 publication.autocreate.mode=disabled。预期 task 进入 FAILED,trace 指向 publication 不存在;数据库中不会凭空多出 publication。这个失败证明“配置成功提交”与“数据面可运行”是两件事,也证明 publication 由谁创建必须是显式责任。
第二组反例用来观察 WAL 保留。停止 connector 但保留 slot,然后持续更新一张被捕获表:
UPDATE sales.orders
SET amount = amount + 0.01, updated_at = clock_timestamp()
WHERE id = 1001;
SELECT slot_name, active, restart_lsn, confirmed_flush_lsn,
wal_status, safe_wal_size,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM pg_replication_slots
WHERE slot_name = 'cdc_lab_slot';预期 active=false,保留差值随写入增长;达到实例限制并经过检查点后,wal_status 可能从 reserved/extended 走向 unreserved/lost,字段是否存在及枚举值取决于 PostgreSQL 版本。看到 lost 后不能靠重启恢复缺失 WAL,应冻结下游切流,重新做受控快照或从另一条完整日志链恢复。不要为了制造现象在共享库执行大批量更新,本地实验也要给卷设置空间上限并在结束后清理。
slot.drop.on.stop=true 只适合测试。生产若启用它,正常滚动重启也会删除恢复锚点;Connect offset 还指向旧 LSN,而新槽从新位置开始,两边已经不是同一恢复协议。
snapshot 不是一次普通的 SELECT
默认 initial snapshot 会开启事务,按 snapshot.isolation.mode 建立读取视图,先记录 WAL 位置,再读取表结构与数据,成功后把完成标记写入 connector offset,最后从先前记录的位置转入流式阶段。连接器若在完成标记持久化前中断,重启会重新开始快照;因此大表快照的失败成本不仅是时间,还包括源库重复扫描、网络出口、Kafka 写入和下游重复收敛。snapshot.fetch.size 控制每批读取的最大行数,调大通常减少往返,却提高连接器堆、网络突发和单批失败代价;它不是并行度开关。
snapshot.mode=no_data 只从已有 offset 或新建 slot 的位置开始,不能补出此前的表数据;只有目标已经有可信基线且 WAL 起点能与基线严格对齐时才可用。when_needed 会在没有 offset 或 offset 指向的 LSN 已不可用时尝试快照,它提高自动恢复能力,却可能在事故期间突然触发全库读取。always 每次启动都做快照,适合某些故障恢复策略,不适合把普通滚动发布变成反复全量。模式语义以稳定版 connector 的 snapshot.mode 表为准。
增量快照通过 signal 机制按块补读表,适合新增捕获表或修复局部基线,但它仍会和在线变更交错,需要下游按 key 合并。不要把“分块”误解为没有源库代价:块查询会消耗连接、缓存和 IO,主键分布不均会导致尾块拖长。上线前应在副本或同规格压测环境测量每块耗时、源库读放大、WAL 增长和 Kafka 峰值,再决定 chunk 与并发策略。
主库切换时,普通 slot 不会自动跟着地址漂移
连接器连到代理或虚拟地址,只解决“怎样找到新主库”,不解决“新主库有没有相同 slot 状态”。普通逻辑复制槽只存在于原主库;直接提升备库后,连接器可能在新主库自动创建同名新槽,但新槽与 Connect offset 未必仍属于同一条可恢复日志链,最坏结果是没有明显报错却出现窗口缺口。
PostgreSQL 17 起提供 failover logical slot 的自动同步机制。Debezium 侧要设置 slot.failover=true;数据库侧还要给主备流复制建立物理复制槽,在备库配置 primary_slot_name、hot_standby_feedback=on、有效的 primary_conninfo 与 sync_replication_slots=on,并把这个物理复制槽名加入主库的 synchronized_standby_slots。这里不能填 cdc_lab_slot:它是待同步的逻辑槽,不是约束主库 WAL 发送进度的物理槽。最小关系如下,参数修改方式与是否需要重启应按目标 PostgreSQL 版本执行:
# primary
synchronized_standby_slots = 'standby_a_physical'
# standby
primary_slot_name = 'standby_a_physical'
hot_standby_feedback = on
sync_replication_slots = on随后在备库确认逻辑槽已同步:
SELECT slot_name, slot_type, active, restart_lsn,
confirmed_flush_lsn, failover, synced
FROM pg_replication_slots
WHERE slot_name = 'cdc_lab_slot';切换门禁不是“新主库可连接”,而是候选备库上的逻辑槽 failover=true 且 synced=true,物理复制槽持续推进,复制延迟和 WAL 可用窗口满足恢复预算。同步是异步发生的,刚创建 failover slot 或备库刚恢复时不能立即提升。具体参数、版本要求与同步行为应对照 PostgreSQL 逻辑复制故障切换、PostgreSQL 复制参数和Debezium failover slot 配置。托管数据库是否暴露这些参数、代理是否保持目标数据库一致、故障切换后 DNS 缓存多久刷新,都要在目标服务上单独验证。
若版本或平台不能同步 failover slot,就必须把停写、保存 Connect offset、记录旧主 slot 状态、确认备库追平、提升、重建槽、决定补快照或接受窗口损失写进切换剧本。不要要求“每条事件 LSN 数值连续”:LSN 本来就不是业务事件序号。切换门禁应验证恢复所用 offset 的源分区仍对应同一数据库身份、所需 WAL 可从目标 slot 读取,并用故障窗口内的事务标识和业务键对账证明没有漏提交、重复已收敛;无法建立这条证据链时,不应自动放行业务读目标端。
项目接入要把重复、删除和 Schema 变化写进契约
业务接入时,Kafka topic 只是传输层。消费者至少要明确 key 从哪些列生成,主键变化如何表达,delete 与 tombstone 谁处理,缺失字段是 null 还是未携带,TOAST 大字段未变化时如何解释,以及 Schema Registry 的兼容策略。PostgreSQL 表没有合适主键时,UPDATE/DELETE 的旧值能力受 replica identity 影响;把所有表设为 REPLICA IDENTITY FULL 会增加 WAL 和事件体积,不能当成零成本修复。
下游写关系库时可使用“主键 upsert + 源版本比较 + delete 幂等”;写对象存储时通常需要追加原始事件并由后续作业压实;触发外部副作用时必须增加 Inbox 或去重表,不能因为 Connect 支持 Kafka 事务就宣称业务端到端绝对一次。Debezium 的 exactly-once 说明要求 worker 与 connector 配置共同满足条件,并明确列出 Kafka 事务相关风险。即使 source 到 Kafka 达到一次写入,Kafka 到第三方 API 的副作用仍由消费者负责。
Schema 变更应先走 expand-contract:先新增可空列或兼容结构,确认 CDC 与消费者识别,再回填和切换,最后删除旧列。publication 的表集合、connector include list、读取授权和消费者 schema 必须在同一变更单中联动。只让 DBA 执行 DDL 而不通知 CDC owner,通常会把故障推迟到下一次 task 重启或下游反序列化。
容量、监控与敏感数据要一起预算
容量模型至少包含源库快照读 IO、WAL 产生速率、slot 最长不可用时间、Kafka 峰值、Connect 堆与队列、网络出口以及下游追平吞吐。一个简单但有效的预算是:可容忍停机时长 × 高峰 WAL 速率 × 安全系数 不得超过 WAL 卷与保留上限的可用空间;恢复吞吐必须长期高于高峰新增速率,否则积压永远追不完。
监控要把 Connect task 状态、最后事件时间、源 LSN、confirmed_flush_lsn、restart_lsn、保留字节、Kafka producer 错误和下游 lag 放在同一视图。只告警 task=FAILED 会漏掉“任务 RUNNING 但 publication 没包含新表”;只告警磁盘会把定位推迟到事故末端。阈值应从业务恢复时间目标和实测 WAL 速率反推,不使用脱离负载的万能数值。
CDC 事件可能包含姓名、地址、令牌散列和已删除数据。column.exclude.list 或字段转换能减少进入 Kafka 的敏感面,但不能替代源端最小授权、topic ACL、传输加密、静态加密和保留期。Connect 日志不得打印完整配置;故障工单只附经过脱敏的 key、LSN 和错误码。复制账号、Connect REST 管理权限、Kafka topic 读写权限应分离,避免一个凭证同时拥有源库全读与消息集群管理权。
清理、回滚和长期治理
正常退出遵循“停写或冻结捕获面 -> 等待 lag 清零 -> 记录最终 offset、slot 状态与下游水位 -> 停 connector -> 确认不再需要回放 -> 删除 publication/slot -> 撤权并回收账号与 topic”的顺序。先删 slot 再验证下游,会把最后的回滚证据一并删除。执行清理前记录:
SELECT pg_current_wal_lsn() AS current_lsn;
SELECT slot_name, active, restart_lsn, confirmed_flush_lsn
FROM pg_replication_slots
WHERE slot_name = 'cdc_lab_slot';
-- 只有退出审批完成后才执行:
SELECT pg_drop_replication_slot('cdc_lab_slot');
DROP PUBLICATION cdc_lab_pub;
REVOKE SELECT, INSERT, UPDATE ON TABLE public.cdc_heartbeat FROM cdc_reader;
REVOKE SELECT ON TABLE sales.orders FROM cdc_reader;
REVOKE USAGE ON SCHEMA sales FROM cdc_reader;
REVOKE CONNECT ON DATABASE cdc_lab FROM cdc_reader;
DROP OWNED BY cdc_reader RESTRICT;
DROP ROLE cdc_reader;DROP ROLE 会拒绝删除仍被任何数据库对象引用的角色。上面的显式 REVOKE 便于审计具体撤回了什么,随后在 cdc_lab 执行的 DROP OWNED ... RESTRICT 用来清除该库遗漏的授权;它也会删除该角色在当前数据库拥有的对象,因此执行前必须先查询所有权并把需要保留的对象 REASSIGN OWNED 给接管角色。角色和它的依赖是集群级的,而 DROP OWNED 只处理当前数据库:若 cdc_reader 曾获授其他数据库或其中对象的权限,必须连接到每个相关数据库分别撤权并执行 DROP OWNED,最后才能 DROP ROLE。不要用 CASCADE 掩盖未盘点的依赖。
若迁移切流后需要回切,先确认新系统写入是否已经反向同步或明确舍弃;Debezium 单向捕获不能自动恢复目标端独有写入。回滚 connector 配置时保留原 topic.prefix、slot 和 offset 身份,除非变更方案明确要求重新做基线。删除 offset、改 slot 名或重建 publication 都属于数据恢复动作,不是普通配置回滚。
团队应为每个连接器登记源库 owner、CDC owner、下游 owner、publication、slot、topic prefix、敏感级别、WAL 预算、允许停机时间、快照策略、切换策略和退出日期。配置进入版本库但凭证外置,变更必须同时审查捕获面、容量、权限与下游兼容性。最终可接管业务的证据不是一张“RUNNING”截图,而是一条能从源事务、LSN、Kafka offset 一直对到目标业务状态,并能在重启和主库切换后继续成立的审计链。
