TTL索引在分片集群中常不生效,因其清理仅在主分片触发,若分片键不含或无关时间字段,过期文档无法被定位扫描;须将TTL字段纳入分片键(如{tenantId:1,createdAt:1}),禁用哈希分片,expireAfterSeconds≥3600,并确保节点时钟同步≤300ms。

为什么 TTL 索引在分片集群里经常“不生效”
因为 TTL index 的清理任务只在主分片(primary shard)上由本地 mongod 定期触发,而分片键(shard key)若不包含时间字段或与 expireAfterSeconds 字段无关,MongoDB 无法保证过期文档实际落在能被扫描到的分片上——结果就是文档长期滞留,db.collection.getIndexes() 显示索引存在,但 db.collection.find({ createdAt: { $lt: ... } }) 还能查出大量“本该过期”的数据。
常见错误现象:db.collection.stats().indexCount 正常,但 db.collection.countDocuments({ createdAt: { $lt: ISODate("...") } }) 返回非零值;日志里看不到 Removing expired documents 记录。
- 必须确保 TTL 字段(如
createdAt)是分片键的一部分,或与分片键有强相关性(例如复合分片键{ tenantId: 1, createdAt: 1 }) - 不能用哈希分片键(
{ _id: "hashed" })配 TTL,哈希打散后时间局部性完全丢失,TTL 扫描效率趋近于全量广播 -
expireAfterSeconds值建议 ≥ 3600(1 小时),太小会导致清理线程频繁唤醒却扫不到新过期文档,徒增 CPU 波动
时间范围分片(Time-Based Sharding)怎么建才不翻车
时间范围分片本质是手动按时间切分集合,靠应用层路由 + 分片标签(shard tags)控制数据落点,和自动分片逻辑正交。它不依赖 MongoDB 内置分片算法,所以能规避 TTL 的分布盲区,但代价是运维复杂度上升。
使用场景:日志、事件流、监控指标等写入高度时间有序、读多写少、且保留周期明确(如“只存 90 天”)的数据。
- 先创建按时间前缀命名的集合,如
events_202404、events_202405,每个集合单独shardCollection - 对每个集合设置唯一分片键,例如
{ ts: 1, _id: 1 },并绑定 tag 到对应分片:sh.addShardTag("shard01", "events_202404") - 禁止跨集合查询:不要用
db.events_*模糊匹配,聚合时必须显式$unionWith或应用层合并,否则路由失败
TTL 和时间范围分片能一起用吗?怎么配
可以,但只能单向嵌套:在每个时间分片集合(如 events_202404)内部建 TTL index,作为兜底机制,防应用路由异常导致旧数据误写入新分片。
参数差异关键点:expireAfterSeconds 必须严格 ≤ 当前分片覆盖的时间窗口长度。比如 events_202404 存 2024-04-01 到 2024-04-30 的数据,那它的 TTL 最大设为 2592000(30 天),不能设 31 天,否则 4 月 30 日写入的文档可能撑到 5 月 1 日才删,而那时它已不属于该分片管理范围。
- 建索引命令示例:
db.events_202404.createIndex({ ts: 1 }, { expireAfterSeconds: 2592000 }) - 删除过期分片前,务必先
db.events_202403.drop(),再移除对应 shard tag,否则残留 tag 会干扰后续路由 - 注意
drop操作在分片集群中是异步的,可通过db.printShardingStatus()观察dropped: true状态确认完成
最容易被忽略的时钟与权限坑
分片节点间系统时钟不同步超过 300ms,TTL 清理就会错乱:早写的文档被晚删,或者未到时间就被误删。而 sh.setBalancerState(false) 后忘记恢复,会导致时间分片集合的 chunk 迁移停滞,冷数据永远卡在热节点上。
- 所有 mongos 和 mongod 节点必须跑
chronyd或ntpd,且driftfile更新间隔 ≤ 60s - 执行
sh.stopBalancer()前,先sh.getBalancerState()确认是 true;维护完立刻sh.startBalancer(),别靠“之后再弄” - 给运维账号加最小权限:
clusterManager角色管分片操作,dbAdmin角色管单集合 TTL,别直接给root
真正麻烦的从来不是语法对不对,而是时间字段在分片键里的位置、各节点时钟差了多少毫秒、以及那个被注释掉的 sh.startBalancer() 调用有没有人记得取消注释。










