id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week
id
title
type
domain
stack
tags
level
prerequisites
est_hours
related
week
scenario.data-sync
数据同步:业务库到搜索引擎 / 向量库 / 报表库,outbox 与 CDC、幂等、顺序、对账和重建
scenario
cross
data-pipeline
postgresql
mongodb
elasticsearch
vector-db
mq
数据同步
outbox
CDC
变更流
幂等
顺序
对账
全量重建
advanced
backend.data-pipeline.index
backend.mq.index
3
backend.elasticsearch.scenario.kb-search-sync
backend.data-pipeline.scenario.third-party-sync
backend.mongodb.scenario.conversation-store
backend.mq.reliability
数据同步:业务库到搜索引擎 / 向量库 / 报表库,outbox 与 CDC、幂等、顺序、对账和重建
业务背景与问题
业务数据在 PostgreSQL(订单、知识库文档)和 MongoDB(对话记录),需要同步到:ES(全文检索)、向量库(RAG)、报表库(报表 Agent)。现状是业务代码里“写完数据库顺手写 ES”:
写 ES 失败时数据库已提交,两边不一致;
同一条数据快速修改两次,ES 中留下的是旧版本;
删除没有同步,搜索出已下架的商品;
修改映射或换嵌入模型时不知道怎么重建。
约束
允许秒级到分钟级的延迟。
不能明显增加业务写入的耗时。
下游可以从源头完全重建(源头是唯一可信的数据)。
方案设计
捕获变更 (二选一或组合):
outbox 模式 :业务事务中同时写 outbox 表(事件类型、实体 id、版本号),后台进程投递到消息队列 → 保证“数据库提交 ⇔ 有事件”;
CDC :读取数据库日志(PostgreSQL 逻辑复制 / Debezium, MongoDB 变更流),业务代码零侵入。
消费与写入 :
消息只带 id 和版本,消费者回源读取最新数据再写下游(避免消息里的旧快照覆盖新数据);
下游文档 id = 业务主键,写入带外部版本号,旧版本被拒绝;
删除事件 → 删除下游文档(或标记删除);
同一实体的事件按实体 id 分区,保证顺序;
失败重试、死信、告警。
对账 :每天比较源头与下游的数量和版本(按分片哈希),差异自动修复。
全量重建 :新建索引 / 集合 → 全量导入(记录开始时间)→ 回放该时间之后的增量 → 校验 → 切换别名 → 保留旧索引回滚。
监控 :同步延迟(事件产生到写入下游的时间)、积压量、失败数、对账差异。
关键代码
outbox(同一事务中写业务数据和事件):
消费者:回源 + 外部版本号(Elasticsearch Python 客户端,示意):
基础组件的可运行示例:增量同步管道 、MongoDB 变更流 、ES 别名切换 。
取舍
决策
outbox
CDC
侵入性
需要改业务代码
业务零侵入
事件语义
业务事件,含义清晰
行级变更,需要转换
运维
简单
需要部署和维护 CDC 组件、处理日志保留
适合
中小团队、事件少
表多、多个下游
易错点
业务代码中双写(数据库 + ES),没有事务保证。
消息中带完整快照,乱序时旧快照覆盖新数据。
忘记同步删除和权限变更。
重建期间的增量没有回放,切换后丢数据。
没有同步延迟监控,下游落后几小时才发现。
面试怎么讲
“我们把‘写库顺手写 ES’改成了 outbox:业务事务里同时写 outbox 表,后台投递到队列,保证提交和事件一致。消费者只拿 id 回源读最新数据,用业务主键作为文档 id、版本号作为外部版本,乱序和重复都不会让旧数据覆盖新数据;删除也同步。每天按分片哈希对账;换映射或嵌入模型时新建索引全量导入、回放增量、校验后切别名。监控同步延迟和积压。”