1、背景
在我們使用es
的開發過程中可能會遇到這麼一種情況,比如我們的線路名稱欄位lineName
欄位在設定mapping
的時候使用的是text
型別,但是後期發現需要使用這個欄位來進行聚合操作
,那麼我們除了對索引進行reindex
操作外,還有什麼辦法可以解決這個問題呢?此處我們透過runtime field
來解決。
2、runtime field介紹
2.1 runtime field可以實現的功能
執行時欄位是在查詢時評估的欄位。是在es7.11之後增加的
執行時欄位使您能夠:
- 將欄位新增到現有文件,而無需重新索引資料
- 在不瞭解資料結構的情況下開始處理資料
- 在查詢時覆蓋從索引欄位返回的值
- 定義特定用途的欄位,而不修改原始mapping
2.2 runtime field優缺點
- runtime field是執行時增加的欄位,不會被索引和儲存,不會增加索引的大小。
- runtime field 可以像普通欄位一樣使用,可以進行
查詢
,排序
,聚合
等操作。 - 可以動態的新增欄位。
- 可以在查詢時覆蓋欄位的值。即
fields
中和_source
中可以返回同名的欄位,但是值可能不一樣。 - 阻止mapping爆炸,可以先使用後定義。
- 針對經常被搜尋或聚合等操作的欄位,不適合使用runtime field,而應該定義在mapping中。
- runtime field不會出現在
_source
中,需要透過fields api
來獲取。
3、建立runtime field的方式
3.1 透過mapping的方式建立
3.1.1、新增runtime field
PUT /index_script_fields
{
"mappings": {
"runtime": {
"aggLineName": {
"type": "keyword",
"script": {
"source": "emit(doc['lineName'].value)"
}
}
},
"properties": {
"lineId": {
"type": "keyword"
},
"lineName": {
"type": "text"
}
}
}
}
3.1.2、更新 runtime field
POST /index_script_fields/_mapping
{
"runtime": {
"aggLineName": {
"type": "keyword",
"script": {
"source": "emit(doc['lineName'].value)"
}
}
}
}
3.1.3、刪除runtime field
POST /index_script_fields/_mapping
{
"runtime": {
"aggLineName": null
}
}
3.2 透過search request定義runtime field
GET /index_script_fields/_search
{
"runtime_mappings": {
"lineName": {
"type": "keyword",
"script": "emit(params['_source']['lineName']+'new')"
}
},
"query": {
"match_all": {}
},
"fields": [
"lineName"
]
}
4、需求
我們存在一個線路mapping,其中lineName
在設計的使用使用了text
型別,現在我們需要根據這個欄位來進行聚合操作,那麼使用runtime field
該如何操作呢?
5、實現
5.1 mapping
PUT /index_script_fields
{
"mappings": {
"properties": {
"lineId": {
"type": "keyword"
},
"lineName": {
"type": "text"
}
}
}
}
注意此時的lineName
的型別是text
5.2 插入資料
PUT /index_script_fields/_bulk
{"index":{"_id":1}}
{"lineId":"line-01","lineName":"線路A"}
{"index":{"_id":2}}
{"lineId":"line-01","lineName":"線路A"}
{"index":{"_id":3}}
{"lineId":"line-02","lineName":"線路C"}
5.3、根據線路來進行聚合
從上方的mapping
中可以lineName
是text
型別,是不可進行聚合操作的,那麼此時我們想進行聚合操作,就可以使用runtime field
來實現。
5.3.1 不使用runtime field
5.3.2 使用runtime field
5.3.2.1 dsl
GET /index_script_fields/_search
{
"runtime_mappings": {
"aggLineName": {
"type": "keyword",
"script": "emit(params['_source']['lineName']+'new')"
}
},
"query": {
"match_all": {}
},
"fields": [
"lineName"
],
"aggs": {
"agg_line_name": {
"terms": {
"field": "aggLineName",
"size": 10
}
}
}
}
5.3.2.2 java程式碼
@Test
@DisplayName("lineName欄位是text型別,無法進行聚合操作,定義一個runtime field來進行聚合操作")
public void test01() throws IOException {
SearchRequest request = SearchRequest.of(searchRequest ->
searchRequest.index(INDEX_NAME)
// 查詢所有資料
.query(query -> query.matchAll(matchAll -> matchAll))
// runtime field欄位不會出現在 _source中,需要使用使用 fields api來獲取
.fields(fields -> fields.field("lineName"))
// 建立一個 runtime filed 欄位型別是 keyword
.runtimeMappings("aggLineName", runtime ->
runtime
// 此處給欄位型別為keyword
.type(RuntimeFieldType.Keyword)
.script(script ->
script.inline(inline ->
// runtime field中如果使用 painless指令碼語言,需要使用emit
inline.lang(ScriptLanguage.Painless)
.source("emit(params['_source']['lineName']+'new')")
)
)
)
// 進行聚合操作
.aggregations("agg_line_name", agg ->
// 此處的 aggLineName即為上一步runtime field的欄位
agg.terms(terms -> terms.field("aggLineName").size(10))
)
.size(100)
);
System.out.println("request: " + request);
SearchResponse<Object> response = client.search(request, Object.class);
System.out.println("response: " + response);
5.3.3.3 執行結果
6、完整程式碼
7、參考連結
1、https://www.elastic.co/guide/en/elasticsearch/reference/8.6/runtime.html