在之前的很多文章中,我详述了如何使用 runtime fields。在今天的文章中,我想更多地介绍 runtime fields 及其一些用例。
我们知道, 从历史上看,Elasticsearch 依靠 schema on write 的模式来快速搜索数据。如果一个索引,在一开始是没有定义 mapping 的,那么当我们写入第一个数据时,Elasticsearch 会根据自己的猜测来给写入的文档的字段定义类型。现在,我们向 Elasticsearch 添加了 schema on read 模式,以便用户可以灵活地在摄取后更改文档的 schema,还可以生成仅作为搜索查询一部分存在的字段。这个字段只存在于 read 的时候,也就是在查询的时候。schema on read 和 schema on write 一起为用户提供了选择,可以根据他们的需求来平衡性能和灵活性。Runtime fields 的使用,让 schema on read 模式成为可能。
这是由两篇文章组成的系列文章:
Runtime field 也被称为运行时字段。运行时字段是在查询时评估的字段。 运行时字段使你能够:
你可以像访问任何其他字段一样从搜索 API 访问运行时字段,并且 Elasticsearch 看到运行时字段并没有什么不同。 你可以在 index mapping 或 search request 中定义运行时字段。这将在我下面的文章中做介绍。 你的选择,这是运行时字段固有的灵活性的一部分。
使用 _search API 上的 fields 参数来检索运行时字段的值。 运行时字段不会显示在 _source 中,但字段 API 适用于所有字段,即使是那些未作为原始 _source 的一部分发送的字段。
运行时字段在处理日志数据时很有用(参见示例),尤其是当你不确定数据结构时。 你的搜索速度会降低(这是因为需要运行脚本来生成字段的原因),但你的索引大小要小得多,你可以更快地处理日志,而无需为它们编制索引。
因为运行时字段没有被索引,所以添加运行时字段不会增加索引大小。你可以直接在索引映射中定义运行时字段,从而节省存储成本并提高摄取速度。你可以更快地将数据引入 Elastic Stack 并立即访问。当你定义运行时字段时,你可以立即在搜索请求、聚合、过滤和排序中使用它。
如果将运行时字段设为索引字段,则无需修改任何引用运行时字段的查询。更好的是,你可以引用字段是运行时字段的一些索引,以及字段是索引字段的其他索引。你可以灵活地选择要索引哪些字段以及保留哪些字段作为运行时字段。
就其核心而言,运行时字段最重要的好处是能够在你提取字段后将字段添加到文档中。此功能简化了映射决策,因为你不必预先决定如何解析数据,并且可以使用运行时字段随时修改映射。使用运行时字段允许更小的索引和更快的摄取时间,这结合使用更少的资源并降低你的运营成本。
运行时字段可以替代你使用 _search API 编写脚本的许多方式。你使用运行时字段的方式受包含的脚本运行的文档数量的影响。例如,如果你使用 _search API 上的 fields 参数来检索运行时字段的值,则脚本仅针对 top hits 文档运行,就像脚本字段一样。
你可以使用脚本字段(script fields)访问 _source 中的值并根据脚本评估返回计算值。运行时字段具有这些相同的功能,但提供了更大的灵活性,因为你可以在搜索请求中查询和聚合运行时字段。脚本字段只能获取值。
同样,你可以编写一个脚本查询(script query),根据脚本过滤搜索请求中的文档。运行时字段提供了一个非常相似的更灵活的功能。你编写一个脚本来创建字段值,它们随处可用,例如字段、所有查询和聚合。
你还可以使用脚本对搜索结果进行排序,但相同的脚本在运行时字段中的工作方式完全相同。
如果你将脚本从搜索请求中的任何这些部分移动到从相同数量的文档计算值的运行时字段,则性能应该大致相同。这些功能的性能很大程度上取决于包含的脚本正在运行的计算以及脚本运行的文档数量。
运行时字段使用较少的磁盘空间并为您访问数据的方式提供了灵活性,但可能会影响基于运行时脚本中定义的计算的搜索性能。
为了平衡搜索性能和灵活性,索引你经常搜索和过滤的字段,例如时间戳。这些字段你不建议使用 runtime fields。 Elasticsearch 在运行查询时会自动首先使用这些索引字段,从而加快响应时间。然后,你可以使用运行时字段来限制 Elasticsearch 需要为其计算值的字段数量。将索引字段与运行时字段结合使用可为你索引的数据以及如何为其他字段定义查询提供灵活性。
使用异步搜索 API 运行包含运行时字段的搜索。这种搜索方法有助于抵消计算包含该字段的每个文档中的运行时字段的值对性能的影响。如果查询无法同步返回结果集,你将在结果可用时异步获取结果。
重要:针对运行时字段的查询被认为是昂贵的。 如果 search.allow_expensive_queries 设置为 false,则不允许进行昂贵的查询,并且 Elasticsearch 将拒绝针对运行时字段的任何查询。
你可以通过在映射定义下添加 runtime 部分并定义 painless 脚本来映射运行时字段。 此脚本可以访问文档的整个上下文,包括原始 _source 和任何映射字段以及它们的值。 在查询时,脚本运行并为查询所需的每个脚本化字段生成值。
重要:定义 Painless 脚本以与运行时字段一起使用时,你必须包含 emit 方法以发出计算值。
例如,以下请求中的脚本根据定义为日期类型的 @timestamp 字段计算星期几。 该脚本根据时间戳的值计算星期几,并使用 emit 返回计算值。
PUT my-index
{
"mappings": {
"runtime": {
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
},
"properties": {
"@timestamp": {"type": "date"}
}
}
}
我们先运行上面的命令。接着我们可以使用如下的例子来做一个练习:
PUT _ingest/pipeline/add-timestamp
{
"processors": [
{
"set": {
"field": "@timestamp",
"value": "{{_ingest.timestamp}}"
}
}
]
}
PUT my-index/_doc/1?pipeline=add-timestamp
{
"text": "This is nice"
}
我们通过上面的两个指令来完成一个文档的写入。我们接着可以使用如下的代码来进行搜索:
GET my-index/_search?filter_path=**.hits
{
"fields": [
"@timestamp",
"text",
"day_of_week"
]
}
在上面,我们通过 fields 来指定 day_of_week 这个字段。这个和之前的 scripted field 非常相似。上面的命令显示的结果为:
{
"hits" : {
"hits" : [
{
"_index" : "my-index",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"@timestamp" : "2022-06-16T06:33:22.615572Z",
"text" : "This is nice"
},
"fields" : {
"@timestamp" : [
"2022-06-16T06:33:22.615Z"
],
"text" : [
"This is nice"
],
"day_of_week" : [
"Thursday"
]
}
}
]
}
}
我们可以看到在搜索的结果中多一个叫做 day_of_week 的字段。这个字段也可以被用来进行搜索或者做聚合,比如:
GET my-index/_search?filter_path=**.hits
{
"query": {
"match": {
"day_of_week": "Thursday"
}
}
}
上面返回结果为:
{
"hits" : {
"hits" : [
{
"_index" : "my-index",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"@timestamp" : "2022-06-16T06:33:22.615572Z",
"text" : "This is nice"
}
}
]
}
}
我们可以做如下的聚合:
GET my-index/_search?filter_path=aggregations
{
"size": 0,
"aggs": {
"days_dist": {
"terms": {
"field": "day_of_week",
"size": 10
}
}
}
}
上面返回的结果是:
{
"aggregations" : {
"days_dist" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "Thursday",
"doc_count" : 1
}
]
}
}
}
运行时部分可以是以下任何数据类型:
boolean
composite
date
double
geo_point
ip
keyword
long
JSON data type | "dynamic": "true" | "dynamic": "runtime" |
---|---|---|
null | 不添加任何字段 | 不添加任何字段 |
true 或者 false | boolean | boolean |
double | float | double |
long | long | long |
object | object | 不添加任何字段 |
array | 依赖于数组里的第一个非 null 值 | 依赖于数组里的第一个非 null 值 |
通过 date detection 的字符串 | date | date |
通过 numeric detection 的字符串 | float 或者 long | double 或者 long |
不通过 date detection 或者 numberic detection 的字符串 | 含有 .keyword 子字段的 text 类型 | keyword |
比如,我们使用如下的方法来创建一个索引:
PUT my_index-1
{
"mappings": {
"dynamic": "runtime",
"properties": {
"timestamp": {
"type": "date",
"format": "yyyy-MM-dd"
}
}
}
}
在上面,我们使用 format 来定义一个 timestamp 字段,同时我们设置 dynamic 为 runtime。我们使用如下的命令来创建一个文档:
POST my_index-1/_doc/1
{
"timestamp": "2021-01-01",
"message": "my message",
"voltage": "12"
}
由于 message 及 voltage 没有在 mapping 中定义,它们将自动根据上面的表格进行匹配从而得出相应的类型。执行上面的命令,然后我们来查看它们的类型:
GET my-index/_mapping
{
"my_index-1" : {
"mappings" : {
"dynamic" : "runtime",
"runtime" : {
"message" : {
"type" : "keyword"
},
"voltage" : {
"type" : "keyword"
}
},
"properties" : {
"timestamp" : {
"type" : "date",
"format" : "yyyy-MM-dd"
}
}
}
}
}
我们可以看到在 runtime 中,message 及? voltage 被映射为 keyword 字段。
运行时字段通常包括以某种方式操作数据的 painless 脚本。 但是,在某些情况下,你可能会在没有脚本的情况下定义运行时字段。 例如,如果你想从 _source 检索单个字段而不进行更改,则不需要脚本。 你可以只创建一个没有脚本的运行时字段,例如 day_of_week:
PUT my-index-000001/
{
"mappings": {
"runtime": {
"day_of_week": {
"type": "keyword"
}
}
}
}
如果没有提供脚本,Elasticsearch 会在查询时隐式在 _source 中查找与运行时字段同名的字段,如果存在则返回一个值。 如果不存在具有相同名称的字段,则响应不包含该运行时字段的任何值。比如,我们可以写入如下的文档:
PUT my-index-000001/_doc/1
{
"day_of_week": "Thursday"
}
在上面,我们在 _source 里定义了一个叫做 day_of_week 的字段。
在大多数情况下,尽可能通过 doc_values 检索字段值。 由于从 Lucene 加载数据的方式,使用运行时字段访问 doc_values 比从 _source 检索值更快。
但是,有些情况下需要从 _source 检索字段。 例如,默认情况下 text 字段没有可用的 doc_values,因此你必须从 _source 检索值。 在其他情况下,你可能会选择在特定字段上禁用 doc_values。
注意:你也可以使用 params._source(例如 params._source.day_of_week)为要检索值的字段添加前缀。 为简单起见,建议尽可能在不使用脚本的情况下在 mapping 定义中定义运行时字段。
你可以随时更新或删除运行时字段。 要替换现有的运行时字段,请将新的运行时字段添加到具有相同名称的映射中。 要从映射中删除运行时字段,请将运行时字段的值设置为 null:
PUT my-index-000001/_mapping
{
"runtime": {
"day_of_week": null
}
}
Downstream 影响:在相关查询运行时更新或删除运行时字段可能会返回不一致的结果。 每个分片可能有权访问不同版本的脚本,具体取决于映射更改何时生效。
在上面,我们展示了如何在 mapping 中定义 runtime fields。事实上,你可以在搜索请求中指定 runtime_mappings 部分,以创建仅作为查询一部分存在的运行时字段。 你可以将脚本指定为 runtime_mappings 部分的一部分,就像我们在上面将运行时字段添加到映射中一样。
在搜索请求中定义运行时字段使用与在索引映射中定义运行时字段相同的格式。 只需将搜索请求中 runtime_mappings 中的字段定义复制到索引映射的运行时部分即可。
以下搜索请求将 day_of_week 字段添加到 runtime_mappings 部分。 字段值将动态计算,并且仅在此搜索请求的上下文中:
GET my-index-000001/_search
{
"runtime_mappings": {
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
},
"aggs": {
"day_of_week": {
"terms": {
"field": "day_of_week"
}
}
}
}
这个和我们之前所讲的那种 scripted fields 非常相似。我们可以比较一下文章 “Elasticsearch:Script fields 及其调试” 以了解他们之间的区别。在未来,我们使用 runtime fields 来替代 scripted fields。
你甚至可以在 search 请求中运用其他运行时字段的返回值来定义运行时字段。 例如,假设你批量索引一些传感器数据:
POST my-index-2/_bulk?refresh=true
{"index":{}}
{"@timestamp":1516729294000,"model_number":"QVKC92Q","measures":{"voltage":"5.2","start": "300","end":"8675309"}}
{"index":{}}
{"@timestamp":1516642894000,"model_number":"QVKC92Q","measures":{"voltage":"5.8","start": "300","end":"8675309"}}
{"index":{}}
{"@timestamp":1516556494000,"model_number":"QVKC92Q","measures":{"voltage":"5.1","start": "300","end":"8675309"}}
{"index":{}}
{"@timestamp":1516470094000,"model_number":"QVKC92Q","measures":{"voltage":"5.6","start": "300","end":"8675309"}}
{"index":{}}
{"@timestamp":1516383694000,"model_number":"HG537PU","measures":{"voltage":"4.2","start": "400","end":"8625309"}}
{"index":{}}
{"@timestamp":1516297294000,"model_number":"HG537PU","measures":{"voltage":"4.0","start": "400","end":"8625309"}}
你在索引后意识到你的数字数据被映射为 text 类型。 你想在 measures.start 和 measures.end 字段上进行聚合,但聚合失败,因为你无法在文本类型的字段上进行聚合。 运行时字段可以来帮我们救援! 你可以添加与索引字段同名的运行时字段并修改数据类型:
PUT my-index-2/_mapping
{
"runtime": {
"measures.start": {
"type": "long"
},
"measures.end": {
"type": "long"
}
}
}
运行时字段优先于索引映射中同名定义的字段。 这种灵活性允许你隐藏现有字段并计算不同的值,而无需修改字段本身。 如果你在索引映射中出错,你可以使用运行时字段来计算在搜索请求期间覆盖映射中的值的值。
现在,你可以轻松地在 measure.start 和 measure.end 字段上运行平均聚合:
GET my-index-2/_search
{
"aggs": {
"avg_start": {
"avg": {
"field": "measures.start"
}
},
"avg_end": {
"avg": {
"field": "measures.end"
}
}
}
}
响应包括聚合结果而不更改基础数据的值:
"aggregations" : {
"avg_start" : {
"value" : 333.3333333333333
},
"avg_end" : {
"value" : 8658642.333333334
}
}
此外,你可以将运行时字段定义为计算值的搜索查询的一部分,然后在同一查询中对该字段运行统计信息聚合。
索引映射中不存在 duration 运行时字段,但我们仍然可以在该字段上进行搜索和聚合。 以下查询返回持续时间字段的计算值并运行统计聚合以计算从聚合文档中提取的数值的统计信息。
GET my-index-2/_search?filter_path=aggregations
{
"size": 0,
"runtime_mappings": {
"duration": {
"type": "long",
"script": {
"source": """
emit(doc['measures.end'].value - doc['measures.start'].value);
"""
}
}
},
"aggs": {
"duration_stats": {
"stats": {
"field": "duration"
}
}
}
}
在上面,我们使用了在 mapping 中定义好的 measures.start 及 measures.end 运行时字段来创建一个新的运行时字段 duration。即使 duration 运行时字段仅存在于搜索查询的上下文中,你也可以在该字段上进行搜索和聚合。 这种灵活性非常强大,使你能够纠正索引映射中的错误并在单个搜索请求中动态完成计算。上面的搜索的结果为:
{
"aggregations" : {
"duration_stats" : {
"count" : 6,
"min" : 8624909.0,
"max" : 8675009.0,
"avg" : 8658309.0,
"sum" : 5.1949854E7
}
}
}
如果你创建与映射中已存在的字段同名的运行时字段,则运行时字段会隐藏映射的字段。 在查询时,Elasticsearch 评估运行时字段,根据脚本计算一个值,并将该值作为查询的一部分返回。 因为运行时字段会隐藏映射字段,所以你可以覆盖搜索中返回的值,而无需修改映射字段。
例如,假设你将以下文档索引到 my-index-3:
POST my-index-3/_bulk?refresh=true
{"index":{}}
{"@timestamp":1516729294000,"model_number":"QVKC92Q","measures":{"voltage":5.2}}
{"index":{}}
{"@timestamp":1516642894000,"model_number":"QVKC92Q","measures":{"voltage":5.8}}
{"index":{}}
{"@timestamp":1516556494000,"model_number":"QVKC92Q","measures":{"voltage":5.1}}
{"index":{}}
{"@timestamp":1516470094000,"model_number":"QVKC92Q","measures":{"voltage":5.6}}
{"index":{}}
{"@timestamp":1516383694000,"model_number":"HG537PU","measures":{"voltage":4.2}}
{"index":{}}
{"@timestamp":1516297294000,"model_number":"HG537PU","measures":{"voltage":4.0}}
你稍后会意识到 HG537PU 传感器并未报告其真实电压。 索引值应该是报告值的 1.7 倍! 你可以在 _search 请求的 runtime_mappings 部分中定义一个脚本,而不是重新索引你的数据,以隐藏 voltage 字段并在查询时计算一个新值。
如果你搜索型号与 HG537PU 匹配的文档:
GET my-index-3/_search?filter_path=**.hits
{
"query": {
"match": {
"model_number": "HG537PU"
}
}
}
响应包括与型号 HG537PU 匹配的文档的索引值:
{
"hits" : {
"hits" : [
{
"_index" : "my-index-3",
"_id" : "IGvNb4EBD_ZocgsvRosO",
"_score" : 1.0296195,
"_source" : {
"@timestamp" : 1516383694000,
"model_number" : "HG537PU",
"measures" : {
"voltage" : 4.2
}
}
},
{
"_index" : "my-index-3",
"_id" : "IWvNb4EBD_ZocgsvRosO",
"_score" : 1.0296195,
"_source" : {
"@timestamp" : 1516297294000,
"model_number" : "HG537PU",
"measures" : {
"voltage" : 4.0
}
}
}
]
}
}
以下请求定义了一个运行时字段,脚本在其中评估值为 HG537PU 的 model_number 字段。 对于每个匹配,脚本将电压字段的值乘以 1.7。
使用 _search API 上的 fields 参数,你可以检索脚本为匹配搜索请求的文档的 measure.voltage 字段计算的值:
POST my-index-3/_search?filter_path=**.hits
{
"runtime_mappings": {
"measures.voltage": {
"type": "double",
"script": {
"source":
"""if (doc['model_number.keyword'].value.equals('HG537PU'))
{emit(1.7 * params._source['measures']['voltage']);}
else{emit(params._source['measures']['voltage']);}"""
}
}
},
"query": {
"match": {
"model_number": "HG537PU"
}
},
"fields": ["measures.voltage"]
}
查看响应,每个结果的 measures.voltage 的计算值为 7.14 和 6.8。 这还差不多! 运行时字段将此值计算为搜索请求的一部分,而不修改映射值,该值仍会在响应中返回:
{
"hits" : {
"hits" : [
{
"_index" : "my-index-3",
"_id" : "IGvNb4EBD_ZocgsvRosO",
"_score" : 1.0296195,
"_source" : {
"@timestamp" : 1516383694000,
"model_number" : "HG537PU",
"measures" : {
"voltage" : 4.2
}
},
"fields" : {
"measures.voltage" : [
7.14
]
}
},
{
"_index" : "my-index-3",
"_id" : "IWvNb4EBD_ZocgsvRosO",
"_score" : 1.0296195,
"_source" : {
"@timestamp" : 1516297294000,
"model_number" : "HG537PU",
"measures" : {
"voltage" : 4.0
}
},
"fields" : {
"measures.voltage" : [
6.8
]
}
}
]
}
}
使用 _search API 上的 fields 参数来检索运行时字段的值。 运行时字段不会显示在 _source 中,但 fields API 适用于所有字段,即使是那些未作为原始 _source 的一部分发送的字段。
例如,以下请求添加了一个名为 day_of_week 的运行时字段。 运行时字段包含一个脚本,该脚本根据 @timestamp 字段的值计算星期几。 我们将在请求中包含 "dynamic":"runtime",以便将新字段作为运行时字段添加到映射中。
PUT my-index-4/
{
"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-4/_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"}
以下请求使用搜索 API 检索原始请求在映射中定义为运行时字段的 day_of_week 字段。 此字段的值在查询时动态计算,无需重新索引文档或索引 day_of_week 字段。 这种灵活性允许你在不更改任何字段值的情况下修改映射。
GET my-index-4/_search?filter_path=**.hits
{
"fields": [
"@timestamp",
"day_of_week"
],
"_source": false
}
上一个请求返回所有匹配文档的 day_of_week 字段。它的响应为:
{
"hits" : {
"hits" : [
{
"_index" : "my-index-4",
"_id" : "ImvUb4EBD_Zocgsv1Isf",
"_score" : 1.0,
"fields" : {
"@timestamp" : [
"2020-06-21T20:00:01.000Z"
],
"day_of_week" : [
"Sunday"
]
}
},
{
"_index" : "my-index-4",
"_id" : "I2vUb4EBD_Zocgsv1Isf",
"_score" : 1.0,
"fields" : {
"@timestamp" : [
"2020-06-21T20:00:01.000Z"
],
"day_of_week" : [
"Sunday"
]
}
},
...
我们可以定义另一个名为 client_ip 的运行时字段,它也对消息字段进行操作,并将进一步细化查询:
PUT my-index-4/_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-4/_search?filter_path=**.hits
{
"size": 1,
"query": {
"match": {
"client_ip": "211.11.9.0"
}
},
"fields" : ["*"]
}
这一次,响应仅包括两个命中。 day_of_week(Sunday)的值是在查询时使用映射中定义的运行时脚本计算的,结果仅包括与 211.11.9.0 IP 地址匹配的文档。上面的查询的结果为:
{
"hits" : {
"hits" : [
{
"_index" : "my-index-4",
"_id" : "ImvUb4EBD_Zocgsv1Isf",
"_score" : 1.0,
"_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"
]
}
}
]
}
}
警告:此功能处于技术预览阶段,可能会在未来版本中更改或删除。 Elastic 将尽最大努力解决任何问题,但技术预览版中的功能不受官方 GA 功能的支持 SLA 约束。
_search API 上的 fields 参数还可用于通过具有 lookuo 类型的运行时字段从相关索引中检索字段。
POST ip_location/_doc?refresh
{
"ip": "192.168.1.1",
"country": "Canada",
"city": "Montreal"
}
PUT logs/_doc/1?refresh
{
"host": "192.168.1.1",
"message": "the first message"
}
PUT logs/_doc/2?refresh
{
"host": "192.168.1.2",
"message": "the second message"
}
POST logs/_search?filter_path=**.hits
{
"runtime_mappings": {
"location": {
"type": "lookup", #1
"target_index": "ip_location", #2
"input_field": "host", #3
"target_field": "ip", #4
"fetch_fields": ["country", "city"] #5
}
},
"fields": [
"host",
"message",
"location"
],
"_source": false
}
上述搜索从 ip_location 索引中为返回的搜索命中的每个 ip 地址返回 country 和 city。
{
"hits" : {
"hits" : [
{
"_index" : "logs",
"_id" : "1",
"_score" : 1.0,
"fields" : {
"host" : [
"192.168.1.1"
],
"location" : [
{
"country" : [
"Canada"
],
"city" : [
"Montreal"
]
}
],
"message" : [
"the first message"
]
}
},
{
"_index" : "logs",
"_id" : "2",
"_score" : 1.0,
"fields" : {
"host" : [
"192.168.1.2"
],
"message" : [
"the second message"
]
}
}
]
}
}
lookup 字段的响应被分组以保持每个文档与查找索引的独立性。 每个输入值的查找查询预计最多匹配查找索引上的一个文档。 如果查找查询匹配多个文档,则将选择随机文档。