TTL索引在分片集群中由各分片主节点独立运行TTLMonitor线程,每60秒扫描并清理本分片过期文档,不跨分片调度;删除通过oplog同步至Secondary,不释放磁盘空间,且依赖分片键与TTL字段协同设计以避免热点和毛刺。

TTL索引在分片集群中只在各分片的主节点上运行
分片集群里没有全局TTL清理线程,每个分片(shard)的mongod进程各自启动独立的TTLMonitor线程,每60秒扫描本分片上的TTL索引并执行删除。这意味着:
- 过期文档只会在它所在的分片上被清理,不会跨分片移动或统一调度
- 如果分片键和TTL字段不一致(比如分片键是
userId,TTL字段是createdAt),数据分布与过期节奏完全解耦——某分片可能堆积大量未过期日志,另一分片却早已清空 - 副本集内,只有该分片的Primary执行删除,Secondary通过oplog同步,不参与判断逻辑
分片键必须包含TTL字段才能让过期行为“可预测”
如果你希望历史数据按时间窗口均匀分布在各分片、且清理节奏可控,最稳妥的做法是把TTL字段作为分片键的一部分(通常是后缀)。例如:
- 用
{region: 1, createdAt: 1}作分片键,再对createdAt建TTL索引:这样相同时间范围的文档大概率落在同一分片,清理压力分散且可预期 - 避免用哈希分片键(如
{createdAt: "hashed"}):哈希打散后,同一时间窗口的文档被随机打到所有分片,TTL删除变成全集群毛刺源 - 范围分片更适配TTL场景,但需注意分片边界不能卡在高频写入的时间点上(比如按天切分,但每天00:00集中写入会引发单分片热点)
expireAfterSeconds=0 的绝对时间模式在分片下更可靠
相对时间模式(如expireAfterSeconds: 3600)依赖文档插入时的createdAt值,而这个值可能因应用层时钟偏差、批量导入时间戳错乱等问题失准。在分片集群中,更推荐用绝对时间字段+expireAfterSeconds: 0:
- 写入时显式计算并设置
expireAt字段,例如new Date(Date.now() + 3600 * 1000) - 建索引:
db.logs.createIndex({"expireAt": 1}, {expireAfterSeconds: 0}) - 好处是过期时间完全由服务端控制,不受写入时刻影响;分片键哪怕不含
expireAt,只要查询能命中对应分片,清理依然有效
真正容易被忽略的是删除操作对分片元数据和迁移的影响
TTL删除不是静默的:它产生真实delete操作,会写入oplog、触发分片间chunk迁移判定、甚至影响balancer决策。尤其当某分片批量删掉数百万文档后:
- 该分片的chunk大小骤降,balancer可能立刻发起迁移,把其他分片的chunk搬过来“填坑”,引发连锁IO压力
- 删除本身不释放磁盘空间(WiredTiger默认延迟回收),但会抬高cache压力,间接拖慢其他读写
- 监控要盯紧
serverStatus().metrics.ttl.passes和deletedDocuments,如果passes突增但deletedDocuments几乎为0,说明大量文档不满足TTL条件(比如expireAt字段缺失或类型错误),得查数据写入链路











