mongoosastic双写需手动处理失败回滚:es操作须try/catch捕获异常,记录失败_id及原因,推入redis延迟重试队列;它仅监听新写入事件,不支持历史数据补全,且无法保证原子性,必须接受最终一致性。

用 mongoosastic 实现双写,但必须处理失败回滚
直接在 Node.js 业务代码里同步写入 ES 是最轻量的增量同步方式,mongoosastic 就是为此设计的:它监听 Mongoose 的 save、remove 等事件,自动触发 ES 的索引/删除操作。
但它不解决“只写成功一半”的问题——比如 MongoDB 写入成功,ES 写入因网络超时或 mapping 冲突失败,数据就永久不一致了。你得自己加补偿逻辑:
- 所有 ES 操作必须包裹在
try/catch中,并记录失败文档的_id和错误原因(如"version_conflict_engine_exception") - 维护一个本地重试队列(可用 Redis List),失败后推入并设置延迟重试(如 1s、5s、30s)
- 避免在请求链路中阻塞等待 ES 结果;异步写 + 最终一致性才是合理预期
- 注意:它不监听已有 oplog 或 change stream,所以无法补全历史变更,仅适用于新写入路径
用 monstache 监听 change stream,绕过 Node.js 层
如果业务已上线、MongoDB 是 Replica Set、且需要真正 CDC 级别的增量同步(含历史更新/删除),别在 Node.js 里硬扛——直接用 monstache 这类独立进程更可靠。
它通过 MongoDB 的 change stream(4.0+)或 oplog(旧版)捕获变更,与你的 Node.js 应用完全解耦:
- 配置里指定
mongo-url和elasticsearch-urls,再用direct-read-namespaces列出要同步的集合(如["mydb.products"]) - 启用
resume后,它会自动保存resume token到文件或 MongoDB,断连重启后从断点继续 - 字段映射和清洗靠 JavaScript 中间件(
[[script]]配置项),但注意:不支持箭头函数、不能用async/await,只能用function声明 - ES 7.10+ 默认禁用
type,确保 monstache 版本 ≥ rel6,否则会报"type is deprecated"
用 node-mongodb-es-connector 自定义轮询,适合低频场景
如果你的 MongoDB 不是副本集(没法用 change stream)、又不想引入额外服务,可考虑基于时间戳或自增字段的轮询同步,node-mongodb-es-connector 提供了这种模式的骨架。
它本质是定时查 MongoDB 的 updated_at 字段,把变动推到 ES:
- 必须保证集合里有单调递增的字段(如
updated_at: ISODate或seq: NumberLong),否则会漏数据 - 轮询间隔不能太短(30s),否则达不到“近实时”
- ES bulk 写入需控制
size(建议 ≤ 1000 文档/批)和bytes(建议 ≤ 10MB/批),避免超时或 OOM - 它不处理删除事件——除非你在 MongoDB 里用软删(
is_deleted: true),并在脚本里显式过滤掉
同步前必须确认的三件事
无论选哪种方式,以下检查跳过就会卡住:
- MongoDB 必须是
Replica Set,单机standalone模式下change stream直接报错"Change Stream not supported" - ES 索引的
mappings要提前建好,尤其涉及date、geo_point、nested等类型;否则字段被动态 mapping 成text后,全文检索就失效了 - Node.js 项目若用
mongoose,确保 schema 里timestamps: true已开启,否则createdAt/updatedAt字段为空,轮询或双写都拿不到时间锚点











