细说Mmongo ES 数据过期机制

数据过期在redis上非常容易实现,mongo中可使用TTL索引实现类似的功能。

Mongo数据过期

TTL索引

TTL是mongo 中的一种特殊的单字段索引,可以支持文档在一定时间之后自动删除,字段类型必须是 ISODate类型或者包含有ISODate类型的数组,创建TTL索引和创建普通索引的方法一样,只是多加了一个属性而已:

1
db.collection(集合).createIndex({create_time(ISODate类型字段):1(正序),{expireAfterSeconds:过期时间,单位秒},{backgroud(后台执行):true}})

TTL索引包含以下特点:

  • _id字段不支持TTL索引
  • 如果索引字段是数组,并且字段中多个日期值,则mongo使用数组中最早的日期值来计算到期阈值
  • 如果文档中的索引字段不是ISODate 类型或者不包含ISODate 类型的数组,则文档不会过期
  • 如果某个字段已经存在非TTL索引,则无法在同一字段上创建TTL索引

另外一点,如果想实现如同redis一样动态设置过期时间,可以在集合中增加一个TTL索引,并指定其过期时间为0,表示过期时间由索引字段的时间来决定。

与redis的自动过期自动删除数据相比,mongo的自动删除数据并不能保证原子性,其原因在于TTL索引不保证数据在过期后立即删除,而是起一个线程定时(默认60s)扫描并删除过期数据,该删除动作会根据mongo实例的负载情况做出调整,如果负载很高,可能会稍微延迟一段时间再删除,所以过期数据可能会比过期时间多存在一段时间。还有一个需要注意的地方,在副本集中,TTL后台线程只会删除主节点的过期数据,从节点从主节点中复制删除操作。

实操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- part1 创建TTL索引 60s 过期
db.eventlog.createIndex( { "create_time": 1 }, { expireAfterSeconds: 60 } )
-- 插入数据,使用当前时间
db.eventlog.insertOne({'create_time':ISODate()})
-- 查询
db.eventlog.find()
-- 立即查询,数据存在,过60s查询数据为空

-- 修改过期时间
db.runCommand({collMod: "eventlog",index: { keyPattern: { create_time: 1 },expireAfterSeconds: 0}})

-- part2 创建TTL索引,并设置过期时间为0
db.eventlog.createIndex( { "create_time": 1 }, { expireAfterSeconds: 0 } )

-- 插入数据,时间设置为60s后
db.eventlog.insertOne({'create_time':new Date(ISODate().getTime() + 1000*60)})

-- 验证是否生效
db.eventlog.find()

ES数据过期

ES在 6.6 以后推出了 Index Lifecycle Management ,可对索引生命周期进行管理,新版本默认开启了ILM 。

IML

ILM 生命周期主要定义了四个阶段:

  1. Hot :通常是用来放最新的数据,写入、可以查询
  2. Warm : 数据保存后,不在写入时,但是会常常的查询,通常会放在这个阶段,不能写入,可以查询
  3. Cold : 数据存放时间很久,不常使用,但是希望需要用的时候能马上能用,可以接受速度慢一些,放在这一阶段,不能写入,可以查询(但是较慢)
  4. Delete : 从ES中删除

上图可以解释数据随着时间变化,不断产生新的Index,并且随着时间变化,逐渐移动到下一个阶段中。
在 IML 中,可以建立一个 Policy 来指定想要设定哪些阶段,以及每个阶段要进行的动作(Action), IML 中可进行的动作如下:

Action 含义
Rollover 当Index 达到 一定大小、文档个数、文档存放时间 时,自动建立新的 Index 来存放新进来的文档,不会让某 Index一直无限增长下去。这个动作可以针对 Index Alias 或者 Data Stram 进行设定
Shrink 将多个Shard 的Index 转成较少 Shard 数量的Index
Force merge 将一个 Shard 中的 Segment Files 进行合并,可以释放出被删掉的文件在原先 read-only 的 Segment File 所占用的空间,也能加快查询的速度
Freeze 将很少使用的Index,以尽量不使用 heap size 的方式来存放
Delete 删除 Index
Allocate 指定 Index replica 的数量,以及指定Index 可以被放在哪些 shards 的规则
Set Priority 指定 Index replica 的数量,以及指定Index 可以被放在哪些 shards 的规则
Set Priority 指定Index的处理先后权,也就是当 node重新启动的时候,较高权限的Index会先被 recover 而先会到可用的状态
Unfollow 将 Cross Cluster Replication 机制中,会 follow 的index 给取消 follow 。在Rollover ,Shrink 处理时会自动执行这个动作
Wait for snapshot 等到 Snapshot 完成后才能删除Index

ILM 生命周期每个阶段可以进行的动作有哪些:

IML生命周期 可进行的动作
Hot Force merge,Rollover,Set priority, Unfollow
Warm Allocate, Force merge, Read only, Set priority, Shrink, Unfollow
Cold Allocate, Freeze, Set priority, Unfollow
Delete Delete, Wait for snapshot

那么如何配置 ILM 呢?

实操

我们可以从Kibana 的Stack Management进行设置

设置好 Index Lifecycle Policy 后,需要将策略添加到索引模板

绑定好模板后,当新的Index被建立时,就会自动套用刚刚创建的生命周期管理机制。关于模板可以参考 Index templates | Elasticsearch Guide [7.10] | Elastic。若想知道某个 Index ,Index Alias 所对应的 Index Lifecycle Policy 执行情况,目前在哪个阶段,可以通过 Explain Lifecyle API 来查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
GET my-index-000001/_ilm/explain
--
{
"indices": {
"my-index-000001": {
"index": "my-index-000001",
"managed": true,
"policy": "my_policy",
"lifecycle_date_millis": 1538475653281,
"age": "15s",
"phase": "new",
"phase_time_millis": 1538475653317,
"action": "complete",
"action_time_millis": 1538475653317,
"step": "complete",
"step_time_millis": 1538475653317
}
}
}

更多API可以参考 Explain lifecycle API | Elasticsearch Guide [7.9] | Elastic

参考资料
TTL 索引
ILM: Manage the index lifecycle | Elasticsearch Guide [7.9] | Elastic