如何优雅的使用MongoDB TTL索引

  • Post author:
  • Post category:其他

奇技 · 指南

使用过MongoDB的都知道MongoDB有个TTL索引,但是在使用中绝大部分都是直接设置expireAfterSeconds来让数据过期,这种方式是否有什么弊端?以及是否有别的方法去让MongoDB更”优雅”的帮我们处理掉不想要的数据?带着这个疑问,开始正文。

1

基础概念

在开始正文之前,先介绍一下什么是TTL索引给一些新上手MongoDB的朋友。官方文档概念如下

总结就是这东西首先是个单字段的索引,其次它可以帮助你自动删除集合中的数据。是不是觉得很高大上,再也不用手工写脚本删除数据了,别急,工欲善其事必先利其器,在使用之前需要先了解下它的工作原理。

2

工作原理

测试数据的lastModifiedDate是ISO时间,非本地时间
use test13db
db.eventlog.save({lastModifiedDate:ISODate("2021-05-16T14:55:22.338Z")})
db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 5 } )

如果在日常中,使用上面的方法创建了一个TTL索引,它是怎么工作的?

在主节点(副本集或分片副本集)/单实例会有一个后台进程来对比当前DB的ISO时间是否等于lastModifiedDate字段的值的时间加上expireAfterSeconds时间,如果是则TTL索引进程开始工作,从索引中读取到对应的索引条目,然后从集合中删除对应文档。

上面创建的索引的expireAfterSeconds是5秒,那么是否TTL索引是每5秒就会被唤醒一次呢? 并不是,实际上TTL索引是每60秒被唤醒一次,唤醒后开始进行工作。这里有一个因素是决定删除速度的,就是当前MongoDB实例的负载,如果负载很高的话,待删除数据可能会存在超过索引字段值的时间加上expireAfterSeconds(如果非60整数倍,则等待下一个60秒)。

3

其它的创建方法

这里还有一个方法:

db.log_events.createIndex( { "expireAt": 1 }, { expireAfterSeconds: 0 } )

expireAfterSeconds这个设置成0,难道要插入后就立刻过期?当然不是,这种设置方法是按照指定字段expireAt的时间来过期处理。在负载正常的情况下当TTL索引被唤醒的时候,对比expireAt字段的值和当前DB的ISO时间,如果发现当前DB的ISO时间大于设置的expireAt字段的值的时间,那么就删除掉对应的文档。说到这可能有人会问,这种方式创建索引有啥用,直接用上面的看着不更直观么?别急,继续

某个集合业务预期每天写入百万量级数据,但是要求只保留最近30天的有效数据。
db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 2592000 } )
是不是直接对时间字段来个上面方式的索引? 如果是的话,会面临什么呢? 
比如当时插入的时候分钟级写入相同lastModifiedDate是十万量级,那么删除的时候也是分钟级十万量级。
导致实例负载突然上升,当实例负载很高的时候,删除效率会明显下降导致大量删除数据堆积。


按照传统的思路,可以采用将数据分批分散删除,每批次控制删除数据量并且删除后sleep一下来缓解DB压力,然后继续删除。 
是的,无论采用上述哪种方式创建TTL索引如果能控制好分钟级数据量那么在到期后就不会产生大量数据同时删除。


比如当前时间是ISODate("2021-05-17T00:00:00.000Z")
db.eventlog.createIndex( { "lastModifiedDate": 1 }, { expireAfterSeconds: 0 } )
在程序里面通过设置lastModifiedDate这个来指定一个月后某分钟有多少数据过期,这样就将之前的集中删除转变成分散删除。
db.eventlog.save({event_name:"delete", lastModifiedDate:ISODate("2021-06-17T00:00:00.000Z")})

4

限制

TTL索引有哪些限制呢?

  • TTL索引是单字段索引。

  • _id字段不支持TTL索引。

  • 不要在固定集合上面创建TTL索引,虽然能创建成功,但是该TTL索引并不会生效。

  • 不能通过createIndex方法来修改过期时间,相反应该使用下面的方法来修改过期时间。

    db.runCommand( { collMod: "eventlog",
                index: { keyPattern: { lastModifiedDate: 1 },
                         expireAfterSeconds: 3600
                       }
    })
    
  • 如果已经在某个字段上面有索引了,不能在该字段上面继续创建索引。

  • TTL索引字段不支持时间戳类型,也不支持字符串时间。

5

总结

  • 如果是新集合有这种定时删除数据的需求在设计之初就要设计好打散策略防止数据集中过期。

  • 那么如果是一个既存集合,有大量数据并且按照第一种方式在使用TTL索引,那么在读完本文后千万不要立刻就去手动修改TTL索引的expireAfterSeconds为0,至于为什么,如果真理解了上面说的第二种创建方法的原理,肯定就想到了。

  • TTL索引也是索引,仍然在查询的时候可以起到索引的作用,不是单纯为删除数据而生。

  • 通过collMod去修改expireAfterSeconds的时候要注意,在修改时候会获取一次DB级别的W

END

查看历史文章

Kubevirt在360的探索之路(K8S接管虚拟化)
如何提高大规模正则匹配的效能
kvm post interrupt

360技术公众号

技术干货|一手资讯|精彩活动

扫码关注我们