副本集本身不导致时序聚合精度丢失,但同步延迟、Oplog截断、时区混用和bucket物理隔离会间接破坏聚合结果完整性;根本解法是聚合只在Primary执行或物化结果后由Secondary查询。

副本集本身不导致时序聚合精度丢失,但同步延迟、Oplog截断、时区混用和bucket物理隔离这四点会间接破坏你聚合结果的完整性。
为什么 db.sensors.aggregate() 在Secondary上跑结果不准
副本集成员之间不是实时强一致的。Secondary 上执行聚合时,它看到的数据视图可能滞后于 Primary —— 尤其当 oplogSize 小或写入洪峰出现时,optimeDate 落后几分钟很常见。这意味着你查到的“最新一小时”数据,其实漏掉了最后几十秒补录的迟到文档。
- 永远不要在 Secondary 上跑依赖“最新时间窗口”的聚合(比如
$match: { ts: { $gte: { $dateSubtract: ... } } }) - 如果必须读 Secondary,请显式加
readConcern: "majority"并确认writeConcern: "majority"已启用,否则可能读到未提交中间态 - 检查
rs.printSecondaryReplicationInfo()中的oplogEnd: Date和oldestEntry: Date差值,若超过 5 分钟,说明 Oplog 窗口已成瓶颈
timeField 值被同步延迟“拖进旧 bucket”,导致 $dateTrunc 分组错位
MongoDB 时序集合底层按 timeField 值将文档归入固定时间范围的 bucket(如每 10 分钟一个)。但 Secondary 同步是异步拉取 Oplog 的,当它把一条 ts: ISODate("2026-09-07T12:00:00Z") 的文档写入时,Primary 可能早已推进到 12:10 bucket。此时 Secondary 会把它塞进自己本地已存在的 12:00 bucket,而非新建——但该 bucket 在 Primary 上可能已被压缩或部分清理。
- 这种错位不会报错,但会导致
$dateTrunc: { date: "$ts", unit: "minute" }在不同节点返回不同分组数 - 验证方式:在 Primary 和 Secondary 分别执行
db.sensors.find({ ts: ISODate("2026-09-07T12:00:30Z") }).explain("executionStats"),对比nReturned和executionTimeMillis - 根本解法:聚合只在 Primary 执行;若需负载分担,把聚合结果物化到独立集合,再由 Secondary 查询该结果集
TTL 清理与同步节奏不一致,删掉刚补录的迟到数据
假设你建了 TTL 索引 { ts: 1 } + expireAfterSeconds: 86400,本意是保留 24 小时原始数据。但 Secondary 同步慢,它在 2026-09-07T13:00:00Z 才写入一条 ts: 2026-09-06T12:00:00Z 的补录数据,而 TTL 清理线程已在 13:00 检查到该文档 ts + 86400 < now,直接标记删除。
- 现象:补录成功,但几秒后就查不到;
db.sensors.countDocuments({ ts: ISODate("2026-09-06T12:00:00Z") })在 Primary 返回 1,在 Secondary 返回 0 - 规避方法:TTL 索引必须用
{ _id: 1, ts: 1 }或其他业务字段复合,避免单靠ts触发误删;或者干脆禁用 TTL,改用应用层定时归档 - 注意:
expireAfterSeconds是基于文档中ts字段值计算的,和写入时间无关——这点和很多人直觉相反
真正容易被忽略的是:时序集合的 bucket 机制和副本集同步机制在设计上就是解耦的。你无法通过调大 oplogSize 或缩短心跳间隔来“对齐”二者,只能接受“聚合逻辑必须与数据落盘位置强绑定”这个事实——要么全走 Primary,要么把聚合结果导出为稳定快照。

















