Elasticsearch:检索运行时字段及使用 lookup 运行时字段丰富数据
2023年4月23日 | by mebius
使用 _search API 上的 fields 参数检索运行时字段(runtime fields)的值。 运行时字段不会显示在 _source 中,但 fieldsAPI 适用于所有字段,即使是那些未作为原始 _source 的一部分发送的字段。
在一下的测试,我将使用 Elastic Stack 8.7.0 来进行测试。
定义一个运行时字段来计算星期几
例如,以下请求添加一个名为 day_of_week 的运行时字段。 runtime 字段包含一个脚本,该脚本根据 @timestamp 字段的值计算星期几。 我们将在请求中包含 “dynamic”:”runtime” 以便将新字段添加到映射中作为运行时字段。
PUT my-index-000001/
{
"mappings": {
"dynamic": "runtime",
"runtime": {
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
},
"properties": {
"@timestamp": {
"type": "date"
}
}
}
}
摄取一些数据
让我们提取一些示例数据,这将产生两个索引字段:@timestamp 和 message。
POST /my-index-000001/_bulk?refresh
{ "index": {}}
{ "@timestamp": "2020-06-21T15:00:01-05:00", "message" : "211.11.9.0 - - [2020-06-21T15:00:01-05:00] "GET /english/index.html HTTP/1.0" 304 0"}
{ "index": {}}
{ "@timestamp": "2020-06-21T15:00:01-05:00", "message" : "211.11.9.0 - - [2020-06-21T15:00:01-05:00] "GET /english/index.html HTTP/1.0" 304 0"}
{ "index": {}}
{ "@timestamp": "2020-04-30T14:30:17-05:00", "message" : "40.135.0.0 - - [2020-04-30T14:30:17-05:00] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736"}
{ "index": {}}
{ "@timestamp": "2020-04-30T14:30:53-05:00", "message" : "232.0.0.0 - - [2020-04-30T14:30:53-05:00] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736"}
{ "index": {}}
{ "@timestamp": "2020-04-30T14:31:12-05:00", "message" : "26.1.0.0 - - [2020-04-30T14:31:12-05:00] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736"}
{ "index": {}}
{ "@timestamp": "2020-04-30T14:31:19-05:00", "message" : "247.37.0.0 - - [2020-04-30T14:31:19-05:00] "GET /french/splash_inet.html HTTP/1.0" 200 3781"}
{ "index": {}}
{ "@timestamp": "2020-04-30T14:31:27-05:00", "message" : "252.0.0.0 - - [2020-04-30T14:31:27-05:00] "GET /images/hm_bg.jpg HTTP/1.0" 200 24736"}
{ "index": {}}
{ "@timestamp": "2020-04-30T14:31:29-05:00", "message" : "247.37.0.0 - - [2020-04-30T14:31:29-05:00] "GET /images/hm_brdl.gif HTTP/1.0" 304 0"}
{ "index": {}}
{ "@timestamp": "2020-04-30T14:31:29-05:00", "message" : "247.37.0.0 - - [2020-04-30T14:31:29-05:00] "GET /images/hm_arw.gif HTTP/1.0" 304 0"}
{ "index": {}}
{ "@timestamp": "2020-04-30T14:31:32-05:00", "message" : "247.37.0.0 - - [2020-04-30T14:31:32-05:00] "GET /images/nav_bg_top.gif HTTP/1.0" 200 929"}
{ "index": {}}
{ "@timestamp": "2020-04-30T14:31:43-05:00", "message" : "247.37.0.0 - - [2020-04-30T14:31:43-05:00] "GET /french/images/nav_venue_off.gif HTTP/1.0" 304 0"}
搜索计算出的星期几(day of week)
以下请求使用 searchAPI 检索原始请求在映射中定义为运行时字段的 day_of_week 字段。 该字段的值是在查询时动态计算的,无需重新索引文档或索引 day_of_week 字段。 这种灵活性允许你在不更改任何字段值的情况下修改映射。
GET my-index-000001/_search?filter_path=**.hits
{
"fields": [
"@timestamp",
"day_of_week"
],
"_source": false
}
上面命令返回的结果为:
{
"hits": {
"hits": [
{
"_index": "my-index-000001",
"_id": "ztggW4cBwUf0C0P9Hbtq",
"_score": 1,
"fields": {
"@timestamp": [
"2020-06-21T20:00:01.00tgcode0Z"
],
"day_of_week": [
"Sunday"
]
}
},
{
"_index": "my-index-000001",
"_id": "z9ggW4cBwUf0C0P9Hbtq",
"_score": 1,
"fields": {
"@timestamp": [
"2020-06-21T20:00:0tgcode1.000Z"
],
"day_of_week": [
"Sunday"
]
}
tgcode },
...
先前的请求返回所有匹配文档的 day_of_week 字段。 我们可以定义另一个名为 client_ip 的运行时字段,它也对 message 字段进行操作,并将进一步细化查询:
PUT /my-index-000001/_mapping
{
"runtime": {
"client_ip": {
"type": "ip",
"script" : {
"source" : "String m = doc["message"].value; int end = m.indexOf(" "); emit(m.substring(0, end));"
}
}
}
}
运行另一个查询,但使用 client_ip 运行时字段搜索特定 IP 地址:
GET my-index-000001/_search?filter_path=**.hits
{
"size": 1,
"query": {
"match": {
"client_ip": "211.11.9.0"
}
},
"fields" : ["*"]
}
上面的命令返回的结果为:
{
"hits": {
"hits": [
{
"_index": "my-index-000001",
"_id": "ztggW4cBwUf0C0P9Hbtq",
"_score": 1,
"_source": {
"@timestamp": "2020-06-21T15:00:01-05:00",
"message": """211.11.9.0 - - [2020-06-21T15:00:01-05:00] "GET /english/index.html HTTP/1.0" 304 0"""
},
"fields": {
"@timestamp": [
"2020-06-21T20:00:01.000Z"
],
"client_ip": [
"211.11.9.0"
],
"message": [
"""211.11.9.0 - - [2020-06-21T15:00:01-05:00] "GET /english/index.html HTTP/1.0" 304 0"""
],
"day_of_week": [
"Sunday"
]
}
}
]
}
}
这次,响应仅包含两个匹配。 day_of_week(星期日)的值是在查询时使用映射中定义的运行时脚本计算的,结果仅包含与 211.11.9.0 IP 地址匹配的文档。由于我们设置 size 为 1,所以在上面的显示中只有一个结果。
从相关索引中检索字段
警告:此功能处于技术预览阶段,可能会在未来的版本中更改或删除。 Elastic 将尽最大努力修复任何问题,但技术预览中的功能不受官方 GA 功能的支持 SLA 约束。
_search API 上的 fields 参数也可用于通过具有一种 lookup 类型的运行时字段从相关索引中检索字段。
注意:由 lookup 类型的运行时字段检索的字段可用于丰富搜索响应中的命中。 我们无法查询或聚合这些字段。
有关 runtime fields 的一点点小的背景知识
运行时字段是在查询时而非索引时计算出来的字段,它允许我们在查询阶段修改我们的模式(schema)。 你可以在 “Elasticsearch:Runtime fields 入门, Elastic 的 schema on read 实现 – 7.11 发布” 中了解有关运行时字段的更多信息。
运行时字段允许你:
- 在不更改基本模式的情况下为特定用途定义字段。
- 在不重新索引数据的情况下向现有文档添加字段。
- 在不了解数据结构的情况下开始使用你的数据。
- 覆盖查询时从索引字段带回的值。
运行时字段没有索引,这意味着索引的大小不会因添加运行时字段而增加。 事实上,它们可以提高摄取速度,并降低存储成本。 另一方面,添加运行时字段会降低查询速度,因为脚本是在运行时为结果集中的每个文档执行的。
运行时字段可以从 search API 以与其他字段相同的方式访问,Elasticsearch 以相同的方式对待它们。 它们可以在索引时或查询时定义。
Lookup 运行时字段是索引中的一个字段,其值是从另一个索引中检索的。 Lookup 运行时字段使你能够在不同索引中的文档之间创建关系。
查找运行时字段用于什么
Lookup 运行时字段可用于在查询阶段通过同时从相关索引中获取字段来丰富数据。 通过这种方式,你可以轻松丰富频繁更改的数据,并就何时使用其他数据更新主索引做出明智的决定。
对于许多用户来说,重要数据存在于不同的索引中。 这通常是由于该数据的性质不断变化(例如每日指标、安全日志记录等)。 使用 lookup 运行时字段提供了在用户有权访问的动态数据和静态数据之间创建有益连接的能力,从而为分析开辟了更为高级的机会。
如何实现查找运行时字段
具有查找类型的运行时字段可以使用 _search API 上的字段参数从关联的索引中检索字段值。
首先需要在主搜索请求中定义一个 runtime field,带有一个 lookup 类型,需要指定如下参数:
- type:应该是 lookup。
- target_index:表示我们要从中检索字段值的索引,查找查询将针对该索引运行。
- input_field:表示主索引上的字段,其值用作查找词查询的输入值。
- target_field:表示查找查询在查找 target_index 上搜索的字段。
- fetch_fields:表示需要从 lookup target_index 中取出的字段。
在下面的示例中,我们说明了 lookup 运行时字段连接两个索引的功能,其中目标索引包含有显着变化的数据(尽管在示例中,authors 当然不太可能经常更改他们的名字)。
我们有两个索引,一个由三个字段组成的 authors 索引:作者的名字(first_name)和姓氏(last_name)以及图书 ID(book_id)。 第二个索引用于 books,由两个字段组成:书籍 ID(id)和书名(title)。
我们想要从 authors 索引中检索与由 first_name 和 last_name 字段组成的图书 ID 关联的 author_name 的值。 因此,除了本书作者的全名之外,我们还将拥有图书 ID 和书名。
POST authors/_doc?refresh
{
"book_id": "113606",
"first_name": "Mark",
"last_name": "Kim"
}
PUT books/_doc/1?refresh
{
"id": "113606",
"title": "machine learning"
}
PUT books/_doc/2?refresh
{
"id": "142480",
"title": "deep learning"
}
POST books/_search?filter_path=**.hits
{
"runtime_mappings": {
"author_name": {
"type": "lookup",
"target_index": "authors",
"input_field": "id",
"target_field": "book_id",
"fetch_fields": ["first_name", "last_name"]
}
},
"fields": [
"id",
"title",
"author_name"
],
"_source": false
}
在该示例中,我们在主搜索请求中定义了一个名为 author_name 的运行时字段,其中包含一种 lookup 类型,该查找使用术语查询从目标索引(authors)中检索字段(first_name 和 last_name)。
Lookup 查询执行的目标索引是 authors 的索引,其中包含需要检索的字段。 输入字段,即 ID,表示主索引(books)上的字段,其值用作查找词查询的输入值。 目标字段是 book_id,它表示查找 lookup 搜索(authors)的查找索引上的字段。 first_name 和 last_name 的提取字段表示需要从查找索引中检索的字段。
上述搜索返回以下匹配项:
{
"hits": {
"hits": [
{
"_index": "books",
"_id": "2",
"_score": 1,
"fields": {
"id": [
"142480"
],
"title": [
"deep learning"
]
}
},
{
"_index": "books",
"_id": "1",
"_score": 1,
"fields": {
"author_name": [
{
"last_name": [
"Kim"
],
"first_name": [
"Mark"
]
}
],
"id": [
"113606"
],
"title": [
"machine learning"
]
}
}
]
}
}
如返回的命中所示,上述搜索从 authors 索引中为返回的搜索命中的每个图书 ID 返回 first_name 和 last_name。 为了使每个文档独立于查找索引,对查找字段的响应被聚合。 每个输入值的查找查询预计只匹配一个查找索引文档。 如果有多个文档匹配查找查询,将随机选择一个文档。
注意事项和需要知道的点
- 在摄取文档后向文档添加字段的能力是运行时字段的主要优势。
- 运行时字段可以节省磁盘空间并让你更灵活地访问数据,但根据运行时脚本中的计算,它们可能会对搜索性能产生不利影响。
- 如果目标索引没有显着变化,另一个更好的解决方案是利用丰富处理器而不是使用运行时字段进行丰富。
- 查找运行时字段仅从 ES 8.2 开始可用
- 添加运行时字段不会增加索引的大小,因为运行时字段未编制索引。 你可以通过直接在索引映射中定义运行时字段来降低存储成本并加速数据摄取。 数据可以更快地摄入 Elastic Stack 并立即访问,这两者共同消耗更少的资源并节省运营费用。
- 当依赖查询正在进行时,更新或删除运行时字段可能会导致不一致的结果。 根据映射更新发生的时间,每个分片都可以访问脚本的不同副本。
- 如果删除或更新运行时字段,可能会破坏 Kibana 中的现有搜索或可视化效果。