时间序列分片必须用 range 而非 hashed 索引,因需按时间范围(如按月)路由查询;分片键须为单调递增的 Date/ISODate 类型时间字段,且需预先建索引;冷热分离依赖 sh.addShardTag() 与 sh.addTagRange() 的严格顺序配合,边界值类型必须一致,查询条件类型也须匹配分片键。

时间序列分片必须用 hashed 索引?错,range 才是核心
时间跨度分片不是靠哈希打散,而是靠时间字段的有序性做范围切分。MongoDB 的 shardCollection 命令只接受 range 或 hashed 分片键,而时间序列场景下,range 是唯一能支撑“按月/按天路由”的方式——否则查 2024-06 的数据会扫全集群。
常见错误现象:sh.addShardTag("shard01", "hot") 配了标签但查询没走对应节点,本质是分片键没对齐时间范围。
- 分片键必须是时间字段(如
ts),且类型为Date或ISODate;NumberLong也可,但需确保单调递增、无重复 - 不要用
_id或复合键里混入非时间字段(比如{ts: 1, region: 1}),否则范围无法连续,sh.splitAt()会失效 - 建集合前必须先在分片键上建索引:
db.metrics.createIndex({ts: 1}),否则shardCollection报Cannot shard collection without an index on the shard key
冷热分离靠 sh.addShardTag() + sh.addTagRange(),但顺序不能反
标签本身不路由,它只是“描述能力”;真正生效的是 sh.addTagRange() 绑定的范围和标签之间的映射关系。如果先绑范围再加标签,或者标签名拼错,路由就静默失效——查冷数据仍发往热节点。
使用场景:把 ts <= ISODate("2024-01-01") 的 chunk 全部迁到 shard-cold,热数据留在 shard-hot。
- 必须先执行
sh.addShardTag("shard-cold", "cold"),再执行sh.addTagRange("db.metrics", {ts: MinKey}, {ts: ISODate("2024-01-01")}, "cold") - 范围边界值必须与分片键类型严格一致:用
MinKey/MaxKey,别手写null或new Date(0),否则报Range must be valid - 迁移不是即时的:
sh.status()里看到tags已配,但 chunk 还在原节点,得等 balancer 下次触发或手动sh.moveChunk()
sh.splitAt() 手动切分 chunk,但时间点选错会导致数据倾斜
自动分片靠插入触发,但时间序列写入集中在最新窗口(比如每秒写 2024-06-15T14:00:00Z),旧时间范围 chunk 一直不裂开,最终一个 chunk 几百 GB 卡死迁移。必须主动按时间点切分。
参数差异:sh.splitAt() 接收的是分片键值,不是时间字符串——传 "2024-01-01" 会当字符串比大小,和 Date 完全不兼容。
- 正确写法:
sh.splitAt("db.metrics", {ts: ISODate("2024-01-01T00:00:00Z")}) - 别在高峰期执行:每个
splitAt会锁住目标 chunk 对应的 primary shard 几秒,高频调用可能阻塞写入 - 切分后检查:
sh.status()中该范围是否已分裂,且新 chunk 的min/max边界是否符合预期(例如是否出现{ts: ISODate("2024-01-01T00:00:00Z")}到{ts: ISODate("2024-02-01T00:00:00Z")})
查询性能崩在 $gte/$lt 范围没对齐分片键边界
即使分片键是 ts,如果查询条件写成 {ts: {$gte: "2024-06-10", $lt: "2024-06-15"}}(字符串),MongoDB 无法做分片剪枝,会广播到所有 shard。必须类型一致、格式标准。
性能影响:一个本该只查 2 个 shard 的查询,变成扫全部 8 个 shard,延迟从 20ms 涨到 300ms+。
- 客户端传参必须转成
Date对象,Node.js 里用new Date("2024-06-10"),Python 用datetime.fromisoformat() - 聚合管道中也一样:
{$match: {ts: {$gte: {$dateFromString: { dateString: "$inputDate" }}}}}不如直接传Date,避免运行时转换开销 - 注意时区:所有
ISODate默认 UTC,如果业务时间是东八区,写入和查询都统一转成 UTC 存储,别存本地时间再查
最易被忽略的一点:balancer 开关状态和 tag range 生效无关,但它决定 chunk 是否真的迁移。哪怕 tag 配得完全正确,sh.getBalancerState() 返回 false,冷数据 chunk 就永远卡在热节点上。


















