alibaba / alibaba/MongoShake

全量同步时序集合适配不足:强制 _id 排序导致源端大量落盘,且目标端未按时序属性创建

Open
#993 0 comments 0 reactions 1 assignee Claimed by @zhongli-james View on GitHub
bug
Dominant language
Go
Stars
1.8k
Forks
466
PR merge metrics
No merged PRs in 30d

Description

**背景**

关联 issue #992。用户全量同步大体量时序集合(单表逻辑数据 ~226GB、压缩后 ~50GB)时,源端出现 `(OutOfDiskSpace) PlanExecutor error during aggregation :: Available space ... less than the required minimum of 524288000 bytes`,并伴随大量 `E11000 duplicate key error ... index:_id_`。排查后确认磁盘触底是被 MongoShake 的全量读行为放大,而 dup则指向目标集合未被建成时序集合。当前全量链路把时序集合当普通集合处理,存在多处适配不足。

**根因(均已在代码确认)**

- **强制 _id 排序**。collector/docsyncer/doc_reader.go:393 对所有集合无条件 `findOptions.SetSort({_id:1})`。时序集合(逻辑视图)没有 _id索引,服务端会把 find 改写为对底层 `system.buckets` 的解包聚合,再对解包后的全部测量值做阻塞排序,从而触发 `allowDiskUse`落盘。逻辑数据越大、`full_sync.reader.collection_parallel` 越高,并发排序落盘越严重,容易跌破 MongoDB 500MB 空闲底线。TS 分支目前只`SetHint(nil)` 并跳过 `noCursorTimeout`(#897/#784),未去掉 sort。
- **全量不按时序属性创建目标集合**。collector/docsyncer/ 全量写入直接对逻辑名 `BulkWrite`,没有任何携带 timeseries:{...} 的`createCollection`。对于在 oplog 窗口之前就已存在的时序集合(`sync_mode=all`),没有 create DDL 可回放,目标端首次 insert会自动建成普通集合,丢失时序属性,并因此带上 _id_ 唯一索引。
- **dup 兜底对时序集合无效**。collector/docsyncer/doc_executor.go:244-282 在 `insert_on_dup_update=true` 时把重复 insert 转成按 _id 的
`UpdateOne($set)`。时序集合不支持按 _id 的任意更新,该兜底对真时序目标不可靠。

**影响**

- 大体量时序集合全量时源端临时磁盘暴涨,可能触发 OutOfDiskSpace,进而 panic + hypervisor 重启循环(见 #992)。
- 目标端时序集合可能被降级为普通集合;重启重跑时产生大量 E11000 告警,且兜底修复在真时序目标上不成立。

**建议方案(分阶段)**

Phase 1 —— 快速止血,低风险:
- 全量读时序集合时不设置 _id 排序(改为自然顺序扫描)。全量同步不依赖 _id 有序,自然顺序即可消除服务端阻塞排序与落盘。
- 时序集合强制` full_sync.reader.parallel_thread=1`(splitVector 依赖索引,TS 无 _id 索引不适用并发分片读)。
- 在文档/日志中提示:大体量时序全量对源端读节点(注意 secondaryPreferred 命中的是从节点)有临时空间要求。

Phase 2 —— 正确性适配,后续跟进:
- 全量开始前,若目标端不存在,按源端 timeseries选项(`timeField/metaField/granularity/expireAfterSeconds`)创建时序集合,避免被降级为普通集合。
- 评估对时序集合改为直接读写 `system.buckets.*`(bucket 天然有 _id,可廉价扫描/排序,写入保留原 bucket 结构),与增量链路已有的system.buckets 处理保持一致,实现忠实复制。
- 时序目标集合不使用按 _id 的 `insert_on_dup_update` 兜底;改为「保证目标干净 + 单趟不中断」或基于 bucket 幂等的策略。

**验证建议**

- 大体量时序集合全量:对比开启/关闭 _id 排序时源端读节点 dbPath/_tmp 落盘峰值与耗时。
- 预存时序集合 `sync_mode=all`:确认目标端被建成时序集合(db.getCollectionInfos 含 timeseries),且无 _id_ 索引、无 E11000。
- 崩溃重启重跑:确认时序集合重放不因 dup 兜底失败而 panic。

相关:#992、#897、#784

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.