elasticsearch中使用runtime fields

huan1993發表於2023-02-01

1、背景

在我們使用es的開發過程中可能會遇到這麼一種情況,比如我們的線路名稱欄位lineName欄位在設定mapping的時候使用的是text型別,但是後期發現需要使用這個欄位來進行聚合操作,那麼我們除了對索引進行reindex操作外,還有什麼辦法可以解決這個問題呢?此處我們透過runtime field來解決。

2、runtime field介紹

2.1 runtime field可以實現的功能

執行時欄位是在查詢時評估的欄位。是在es7.11之後增加的執行時欄位使您能夠:

  1. 將欄位新增到現有文件,而無需重新索引資料
  2. 在不瞭解資料結構的情況下開始處理資料
  3. 在查詢時覆蓋從索引欄位返回的值
  4. 定義特定用途的欄位,而不修改原始mapping
    runtime field 的作用

2.2 runtime field優缺點

  1. runtime field是執行時增加的欄位,不會被索引和儲存,不會增加索引的大小。
  2. runtime field 可以像普通欄位一樣使用,可以進行查詢,排序,聚合等操作。
  3. 可以動態的新增欄位。
  4. 可以在查詢時覆蓋欄位的值。即fields中和_source中可以返回同名的欄位,但是值可能不一樣。
  5. 阻止mapping爆炸,可以先使用後定義。
  6. 針對經常被搜尋或聚合等操作的欄位,不適合使用runtime field,而應該定義在mapping中。
  7. 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"
  ]
}

透過search request定義runtime field

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中可以lineNametext型別,是不可進行聚合操作的,那麼此時我們想進行聚合操作,就可以使用runtime field來實現。

5.3.1 不使用runtime field

不使用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、完整程式碼

https://gitee.com/huan1993/spring-cloud-parent/blob/master/es/es8-api/src/main/java/com/huan/es8/runtimefield/RuntimeFieldCorrectMappingError.java

7、參考連結

1、https://www.elastic.co/guide/en/elasticsearch/reference/8.6/runtime.html

相關文章