ES 0.90 的 _ttl 能让文档自动过期,后来为什么被拿掉了

0.90 里给 mapping 加一个 _ttl,文档到期就会被自动删掉——不用定时任务、不用 delete-by-query。这个功能 2.0 被标记废弃,5.0 正式移除。这篇看它当年的实现细节,以及官方给的替代方案。

说明

实测环境:本地另起一个 Elasticsearch 7.17.29 单节点实例(官方 tar 包,端口 9200,独立 path.data);本机没有 0.x / 1.x / 2.x 二进制,所以这里验的是这些限制在较新版本上的当前行为与默认值,不是当年的执行过程。文末「在 7.17.29 上跑一遍」里的命令与响应均取自该实例。

先看 0.90 的原文

0.90 的文档(docs/reference/mapping/fields/ttl-field.asciidoc @ v0.90.13,锚点 mapping-ttl-field)开篇是这么写的:

A lot of documents naturally come with an expiration date. Documents can therefore have a _ttl (time to live), which will cause the expired documents to be deleted automatically.

"自动删除"这四个字在 2013 年很有吸引力——那时候没有 ILM,管理"日志只留 7 天"这类需求,要么自己写定时任务删,要么按天建索引再整体删索引。ES 内置一个 TTL,等于把这件事收进引擎里。

当时的用法

默认关闭,要在 mapping 里开:

{
    "tweet" : {
        "_ttl" : { "enabled" : true }
    }
}

想给整个 type 一个默认值:

{
    "tweet" : {
        "_ttl" : { "enabled" : true, "default" : "1d" }
    }
}

几个当时需要知道的细节,文档里都写了:

  • 时间单位:可以写 d(天)、m(分钟)、h(小时)、ms(毫秒)、w(周);不写单位默认是毫秒——这个坑不小,"default": "7" 是 7 毫秒。
  • 不给 default 也不在文档里带 _ttl:文档的 TTL 是无限,永不过期。
  • 存储方式:默认 store: yes、index: not_analyzed,而且文档特意强调 index 必须是 not_analyzed,否则清理流程不工作。
  • 改默认值:可以用 put mapping 动态更新 default,但只影响之后写入的文档,已索引的文档 TTL 不变。

过期是怎么执行的

文档里有一节 "Note on documents expiration",把实现说得很直白:

Expired documents will be automatically deleted regularly. You can dynamically set the indices.ttl.interval to fit your needs. The default value is 60s.

也就是说:后台有一个周期性任务,默认每 60 秒跑一次,扫出过期的文档删掉。相关参数:

设置默认作用
indices.ttl.interval60s清理任务的运行间隔
indices.ttl.bulk_size10000每批删除的文档数

还有一条设计得挺细的说明:

Note that the expiration procedure handle versioning properly so if a document is updated between the collection of documents to expire and the delete order, the document won't be deleted.

即"扫描出待删列表"到"真正发删除"之间,如果文档被更新过,就不会被删。避免刚写入的新数据被误删。

它为什么被拿掉

2.0 先标记废弃(migrate_2_0/mapping.asciidoc @ v2.4.6):

260: ==== `_timestamp` and `_ttl` deprecated
262: The `_timestamp` and `_ttl` fields are deprecated, but will remain functional
268: The current `_ttl` functionality will be replaced in a future version with a

关键词是 "remain functional"——2.0 只是宣布,功能照旧能用。

5.0 正式移除(migrate_5_0/mapping.asciidoc @ v5.6.16):

The _timestamp and _ttl fields were deprecated and are now removed. As a replacement for _timestamp, you should populate a regular date field with the current timestamp on application side. For _ttl, you should either use time-based indices, or delete documents with a delete-by-query on a timestamp field.

官方给的替代方案两条,也正是现在还在用的做法:

  • _timestamp:别指望引擎给你记时间,应用层自己写一个普通 date 字段;
  • _ttl:要么按时间建索引(到期整个索引删掉),要么按时间字段跑 delete-by-query。

至于为什么:文档没直说。能核实到的机制问题是——_ttl 的清理靠周期性扫描 + 批量删除,文档过期这件事被放进了引擎的后台任务里,规模一大就是持续的额外负担;而"按时间建索引"把删除变成整个索引的丢弃,代价低得多。这段属判断,不是官方说法。

它不是孤例:0.90 的元字段家族

_ttl 不是单独存在的。0.90 的 docs/reference/mapping/fields/ 目录下并排放着这些元字段页面:

_all、_analyzer、_boost、_id、_index、_parent、_routing、_size、_source、_timestamp、_ttl、_type、_uid。

可以看到当时的思路:ES 倾向于把"文档生命周期里常见的需求"直接做成元字段——时间有 _timestamp、过期有 _ttl、父子关系有 _parent、路由有 _routing、权重有 _boost。

这些元字段后来的处理方式高度一致:_boost 在 1.0 被移除(改用 function_score),_timestamp 和 _ttl 在 2.0 废弃、5.0 移除,_all 在 6.0 默认关闭、7.0 彻底移除,_parent 最终被 join 字段接替。活到今天的只有 _source、_id、_routing 等少数几个。

这条脉络比单个字段的存废更值得记:ES 从"什么都内置"走向"引擎只做引擎"的过程,就是靠一次次迁移文档里的 deprecated / removed 完成的。

如果你还在维护带 _ttl 的老集群

升级路径上有一条硬阻断,5.0 的迁移文档写得很直接(migrate_5_0/mapping.asciidoc#L183):

_ttl and _timestamp cannot be created

意思是 5.0 之后带这两个元字段的 mapping 直接创建失败,不是告警、是报错,不是忽略。所以:

  • 升到 5.x 之前,必须先把 mapping 里的 _ttl、_timestamp 清掉;
  • 清掉之后,原本靠 _ttl 自动删除的数据要用别的手段接住——按官方给的替代方案,就是把"按时间建索引 + 到期删索引"这条链路先搭好再切;
  • 切换期间注意数据量的台阶:从逐文档删除变成整索引删除,磁盘占用的曲线形状会不一样,别按老经验设告警。

现在的做法对照

需求0.90 的做法现在的做法
文档自动过期mapping 里 _ttl.enabled + default按时间建索引(rollup/数据流 + ILM),到期删整个索引
保留时间戳_timestamp 元字段应用层写普通 date 字段
按条件清理引擎后台扫描delete-by-query / ILM 的删除阶段

在 7.17.29 上跑一遍

_ttl 在 5.0 移除,用较新版本声明一次就能确认。

图 1:7.17.29 单节点实例的实测输出。mapping 顶层带 _ttl 报 Root mapping definition has unsupported parameters: [_ttl : {enabled=true}];去掉后索引能建,回读 mapping 顶层只有 properties。

curl -s -X PUT 'http://localhost:9200/ttl-x' -H 'Content-Type: application/json' \
  -d '{"settings":{"index":{"number_of_replicas":0}},"mappings":{"_ttl":{"enabled":true},"properties":{"v":{"type":"integer"}}}}' \
  | jq -r '.error.root_cause[0].reason'
# Root mapping definition has unsupported parameters:  [_ttl : {enabled=true}]
curl -s -X DELETE 'http://localhost:9200/ttl-x' >/dev/null
curl -s -X PUT 'http://localhost:9200/ttl-x' -H 'Content-Type: application/json' \
  -d '{"settings":{"index":{"number_of_replicas":0}},"mappings":{"properties":{"v":{"type":"integer"}}}}' \
  | jq -c '.acknowledged'
# true
curl -s 'http://localhost:9200/ttl-x/_mapping' | jq -c '."ttl-x".mappings | keys'
# ["properties"]

图 1 把「5.0 正式移除」验成了当前版本的具体报错。本机没有 0.90 二进制,_ttl 当年自动过期与 purge 线程的行为没有实例可对照。

边界:

  • 图 1 出自 7.17.29 单节点实例,没有 0.90 / 2.x 二进制;
  • 替代方案(按时间字段配合 delete-by-query、ILM)未实测;
  • 只验了 mapping 声明阶段,写入带 _ttl 的文档未测。

写在最后

  • _ttl 的兴衰很能说明 ES 设计取向的变化:0.90 什么都想内置(TTL、timestamp、boost 字段、facets 都是),2.0 开始往"引擎只做引擎"的方向收,5.0 把这些内置功能一次性清理掉。
  • 现在不要再找替代品去实现"单文档 TTL"——官方明确指向按时间分索引这条路,顺着做,成本最低。
  • 老集群升级时注意:5.0 起带 _ttl 的 mapping 直接创建失败(migrate_5_0/mapping.asciidoc#L183 那条 _ttl and _timestamp cannot be created),是报错,不是警告。

参考

本文引用的原文均取自本地 elasticsearch/ 仓库的 v0.90.13 / v2.4.6 / v5.6.16 标签。0.90 那份文档没有可用的行号锚点,按章节标题引用;2.0/5.0 的引用带行号。「为什么被拿掉」一节中官方未说明原因的部分已标明属判断。「在 7.17.29 上跑一遍」里已实际声明 _ttl mapping:报 Root mapping definition has unsupported parameters: [_ttl : {enabled=true}],去掉后索引可建、回读 mapping 只有 properties。本机无 0.90 二进制,自动过期与 purge 线程行为未实测;替代方案未实测。

0
0
0
0
评论
未登录
暂无评论