日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

一、前言

本文分享了在工作中關于 ElasticSearch 的一些使用建議。和其他更偏向手冊化更注重結論的文章不同,本文將一定程度上闡述部分建議背后的原理及使用姿勢參考,避免流于表面,只知其然而不知其所以然。如有不當的地方,歡迎指正!

二、查詢相關

充分利用緩存

1)分片查詢緩存(Shard Request Cache)

ES 層面的緩存實現,封裝在 IndicesRequestCache 類中。緩存的 Key 是整個客戶端請求,緩存內容為單個分片的查詢結果。主要作用是對聚合的緩存,查詢結果中被緩存的內容主要包括:Aggregations(聚合結果)、Hits.total、以及 Suggestions等。

并非所有的分片級查詢都會被緩存。只有客戶端查詢請求中 size=0 的情況下才會被緩存。其他不被緩存的條件還包括 Scroll、設置了 Profile 屬性,查詢類型不是 QUERY_THEN_FETCH,以及設置了 requestCache=false 等。另外一些存在不確定性的查詢例如:范圍查詢帶有 Now,由于它是毫秒級別的,緩存下來沒有意義,類似的還有在腳本查詢中使用了 Math.random() 等函數的查詢也不會進行緩存。

當有新的 Segment 寫入到分片后,緩存會失效,因為之前的緩存結果已經無法代表整個分片的查詢結果。所以分片每次 Refresh 之后,緩存會被清除。

2)節點查詢緩存/過濾器緩存(Node Query Cache /Filter Cache)

Lucene 層面的緩存實現,封裝在 LRUQueryCache 類中,默認開啟。緩存的是某個 Filter 子查詢語句在一個 Segment 上的查詢結果。

并非所有的 Filter 查詢都會被緩存。對于體積較小的 Segment 不會建立 Query Cache,因為他們很快會被合并。Segment 的 Doc 數量需要大于 10000,并且占整個分片的 3% 以上才會走 Cache 策略(參考:緩存)。

當 Segment 合并的時候,被刪除的 Segment 其關聯 Cache 會失效。

01.使用過濾器上下文(Filter)替代查詢上下文(Query)

  • Filter不會進行打分操作,而 Must 會。
  • Filter 查詢可以被緩存,從而提高查詢性能。

正例:

// 創建BoolQueryBuilder

BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

// 構建過濾器上下文

boolQuery.filter(QueryBuilders.termQuery("field", "value"));

反例:

// 創建BoolQueryBuilder

BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

// 構建查詢上下文

boolQuery.must(QueryBuilders.termQuery("field1", "value1"));

02. 只關注聚合結果而不關注文檔細節時,Size 設置為 0 利用分片查詢緩存

參考示例:

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

// 添加聚合查詢

sourceBuilder.aggregation(

AggregationBuilders.terms("term_agg").field("field")

.subAggregation(AggregationBuilders.sum("sum_agg").field("field"))

);

// 設置size為0,只返回聚合結果而不返回文檔

sourceBuilder.size(0);

03. 日期范圍查詢使用絕對時間值

日期字段上使用 Now,一般來說不會被緩存,因為匹配到的時間一直在變化。因此, 可以從業務的角度來考慮是否一定要用 Now,盡量使用絕對時間值,不需要解析相對時間表達式且利用 Query Cache 能夠提高查詢效率。例如時間范圍查詢中使用 Now/h,使用小時級別的單位,可以讓緩存在 1 小時內都可能被訪問到。

正例:

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

// 獲取當前日期并格式化為絕對時間值

LocalDateTime now = LocalDateTime.now();

DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;

String currentDate = now.format(formatter);

// 創建日期范圍查詢

sourceBuilder.query(QueryBuilders.rangeQuery("date_field")

.gte("2022-01-01")

.lte(currentDate));

反例:

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

// 創建日期范圍查詢,使用相對時間值

sourceBuilder.query(QueryBuilders.rangeQuery("date_field")

.gte("now-7d")

.lte("now"));

聚合查詢

04. 避免多層聚合嵌套查詢

聚合查詢的中間結果和最終結果都會在內存中進行,嵌套過多,會導致內存耗盡。

如:

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

// 創建主要查詢

sourceBuilder.query(QueryBuilders.matchAllQuery());

// 創建第一層聚合

TermsAggregationBuilder termAggBuilder1 = AggregationBuilders.terms("term_agg1").field("field_name1");

// 創建第二層聚合

TermsAggregationBuilder termAggBuilder2 = AggregationBuilders.terms("term_agg2").field("field_name2");

termAggBuilder1.subAggregation(termAggBuilder2);

// 創建第三層聚合

TermsAggregationBuilder termAggBuilder3 = AggregationBuilders.terms("term_agg3").field("field_name3");

termAggBuilder2.subAggregation(termAggBuilder3);

sourceBuilder.aggregation(termAggBuilder1);

05. 嵌套查詢建議使用 Composite 聚合查詢方式

對于常見的 Group by A,B,C 這種多維度 Groupby 查詢,嵌套聚合的性能很差,嵌套聚合被設計為在每個桶內進行指標計算,對于平鋪的 Group by 來說有存在很多冗余計算,另外在 Meta 字段上的序列化反序列化代價也非常大,這類 Group by 替換為 Composite 可以將查詢速度提升 2 倍左右。

正例:

// 創建Composite Aggregation構建器

CompositeAggregationBuilder compositeAggregationBuilder = AggregationBuilders

.composite("group_by_A_B_C")

.sources(

AggregationBuilders.terms("group_by_A").field("fieldA.keyword"),

AggregationBuilders.terms("group_by_B").field("fieldB.keyword"),

AggregationBuilders.terms("group_by_C").field("fieldC.keyword")

);

// 創建查詢條件

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()

.query(QueryBuilders.matchAllQuery())

.aggregation(compositeAggregationBuilder)

.size(0);

反例:

// 創建Terms Aggregation構建器,按照字段A分組

TermsAggregationBuilder termsAggregationA = AggregationBuilders.terms("group_by_A").field("fieldA.keyword");

// 在字段A的基礎上創建Terms Aggregation構建器,按照字段B分組

TermsAggregationBuilder termsAggregationB = AggregationBuilders.terms("group_by_B").field("fieldB.keyword");

// 在字段B的基礎上創建Terms Aggregation構建器,按照字段C分組

TermsAggregationBuilder termsAggregationC = AggregationBuilders.terms("group_by_C").field("fieldC.keyword");

// 將字段C的聚合添加到字段B的聚合中

termsAggregationB.subAggregation(termsAggregationC);

// 將字段B的聚合添加到字段A的聚合中

termsAggregationA.subAggregation(termsAggregationB);

// 創建查詢條件

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()

.query(QueryBuilders.matchAllQuery())

.aggregation(termsAggregationA)

.size(0);

06. 避免大聚合查詢

聚合查詢的中間結果和最終結果都會在內存中進行,數據量太大會導致內存耗盡。

07. 高基數場景嵌套聚合查詢建議使用 BFS 搜索

聚合是在 ES 內存完成的。當一個聚合操作包含了嵌套的聚合操作時,每個嵌套的聚合操作都會使用上一級聚合操作中構建出的桶作為輸入,然后根據自己的聚合條件再進行桶的進一步分組。這樣對于每一層嵌套,都會再次動態構建一組新的聚合桶。在高基數場景,嵌套聚合操作會導致聚合桶數量隨著嵌套層數的增加指數級增長,最終結果就是占用 ES 大量內存,從而導致 OOM 的情況發生。

默認情況下,ES 使用 DFS(深度優先)搜索。深度優先先構建完整的樹,然后修剪無用節點。BFS(廣度優先)先執行第一層聚合,在繼續下一層聚合之前會先做修剪。

在聚合查詢中,使用廣度優先算法需要在每個桶級別上緩存文檔數據,然后在剪枝階段后向子聚合重放這些文檔。因此,廣度優先算法的內存消耗取決于每個桶中的文檔數量。對于許多聚合查詢,每個桶中的文檔數量都非常大,聚合可能會有數千或數十萬個文檔。

但是,有大量桶但每個桶中文檔數量相對較少的情況下,使用廣度優先算法能更加高效地利用內存資源,而且可以讓我們構建更加復雜的聚合查詢。雖然可能會產生大量的桶,但每個桶中只有相對較少的文檔,因此使用廣度優先搜索算法可以更加節約內存。

參考示例:

searchSourceBuilder.aggregation(

AggregationBuilders.terms("brandIds")

.collectMode(Aggregator.SubAggCollectionMode.BREADTH_FIRST)

.field("brandId")

.size(2000)

.order(BucketOrder.key(true))

);

08. 避免對 text 字段類型使用聚合查詢

text 的 Fielddata 會加大對內存的占用,如有需求使用,建議使用 Keyword。

09. 不建議使用 bucket_sort 進行聚合深分頁查詢

ES 的高 Cardinality 聚合查詢非常消耗內存,超過百萬基數的聚合很容易導致節點內存不夠用以至 OOM。

bucket_sort 使用桶排序算法,性能問題主要是由于它需要在內存中緩存所有的文檔和聚合桶,然后才能進行排序和分頁,隨著文檔數量增多和分頁深度增加,性能會逐漸變差,有深分頁問題。因為桶排序需要對所有文檔進行整體排序,所以它的時間復雜度是 O(NlogN),其中 N 是文檔總數。

目前Elasticsearch支持聚合分頁(滾動聚合)的目前只有復合聚合(Composite Aggregation)一種。滾動的方式類似于SearchAfter。聚合時指定一個復合鍵,然后每個分片都按照這個復合鍵進行排序和聚合,不需要在內存中緩存所有文檔和桶,而是可以每次返回一頁的數據。

反例:使用 bucket_sort 深分頁 RT 達到 5000ms+

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

boolQuery.filter(QueryBuilders.termQuery(EsNewApplyDocumentFields.IS_DEL, 0));

TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("spuIdAgg").field("spuId").order(BucketOrder.key(false)).size(pageNum*pageSize);

termsAggregationBuilder.subAggregation(new BucketSortPipelineAggregationBuilder("spuBucket",null).from((pageNum-1)*pageSize).size(pageSize)); searchSourceBuilder.query(boolQuery).aggregation(termsAggregationBuilder).size(0);

正例:使用 Composite Aggregation 優化后深分頁查詢:423ms

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

boolQuery.filter(QueryBuilders.termQuery(EsNewApplyDocumentFields.IS_DEL, 0));

CompositeAggregationBuilder compositeBuilder = new CompositeAggregationBuilder(

"spuIdAgg",

Collections.singletonList(new TermsValuesSourceBuilder("spuId").field("spuId").order("desc"))

).aggregateAfter(ImmutableMap.of("spuId", "603030")).size(20);

searchSourceBuilder.query(boolQuery).aggregation(compositeBuilder).aggregation(totalAgg).size(0);

分頁

10. 避免使用 from+size 方式

ES 中深度翻頁排序的花費會隨著分頁的深度而成倍增長,分頁搜索不會單獨“Cache”。每次分頁的請求都是一次重新搜索的過程,而不是從第一次搜索的結果中獲取。如果數據特別大對 CPU 和內存的消耗會非常巨大甚至會導致 OOM。

11. 避免高實時性&大結果集場景使用 Scroll 方式

基于快照的上下文。實時性高的業務場景不建議使用。大結果集場景將生成大量Scroll 上下文,可能導致內存消耗過大,建議使用 SearcheAfter 方式

思考:對于 Scroll 和 SearchAfter 的選用怎么看?兩者分別適用于哪種場景?SearchAfter 可以完全替代 Scroll 嗎?

Scroll 維護一份當前索引段的快照,適用于非實時滾動遍歷全量數據查詢,但大量Contexts 占用堆內存的代價較高;7.10 引入的新特性 Search After + PIT,查詢本質是利用前向頁面的一組排序之檢索匹配下一頁,從而保證數據一致性;8.10 官方文檔明確指出不再建議使用 Scroll API 進行深分頁。如果分頁檢索超過 Top10000+ 推薦使用 PIT + Search After。

12. SearchAfter 分頁/Scroll ID/ 遍歷索引中的數據指定 Sort 字段要保證唯一性,否則會造成分頁/遍歷數據不完整或重復

13. 建議指定業務字段排序,不要采用默認打分排序

ES 默認使用“_score”字段按評分排序。如在使用 Scroll API 獲取數據時,如果沒有特殊的排序需求,推薦使用"sort":"_doc"讓 ES 按索引順序返回命中文檔,可以節省排序開銷。原因如下:

  • 使用非文檔 ID 排序,會導致每次查詢 ES 需要在每個分片記住上次返回的最后一個文檔,然后下次查詢中會對之前已經返回的文檔進行忽略過濾,同時在協調節點進行排序操作。文檔 ID 排序則不需要上述操作。

一口氣看完43個關于 ElasticSearch 的實操建議

  • 對于文檔 ID 排序,ES 內部進行了特殊優化,性能表現更優。

一口氣看完43個關于 ElasticSearch 的實操建議

14. Scroll 查詢確保顯式調用 clearScroll() 方法清除 Scroll ID

否則會導致 ES 在過期時間前無法釋放 Scroll 結果集占用的內存資源,同時也會占用默認 3000 個 Scroll 查詢的容量,導致 too many scroll ID 的查詢拒絕報錯,影響業務。

其他

15. 注意 Must 和 Should 同時出現在語句里的時候,Should 會失效;注意 Must 和 Should 同時出現在同一層級的 bool 查詢時,Should 查詢會失效

正例:

{"query":{ "bool":{

"must":[

{"bool":{

"must":[

{

"term":{

"status.keyword":"1"

} }]}},

{"bool":{

"should":[

{"term":{

"tag.keyword":"1"

} } ] }}]}}}

反例:

{"query":{

"bool":{

"must":[

{

"term":{

"status.keyword":"1"

}}],

"should":[

{

"term":{

"tag.keyword":"1"

}

}]}}}

16. 避免查詢 indexName-*

因為 Elasticsearch 中的索引名稱是全局可見的,可以通過查詢所有索引的方式來枚舉某個集群中的所有索引名稱。可以通過在 Elasticsearch 配置文件中設置 action.destructive_requires_name 參數來禁止查詢 indexName-*。

17. 腳本使用 Stored 方式,避免使用 Inline 方式

對于固定結構的 Script,使用 Stored 方式,把腳本通過 Kibana 存入 ES 集群,降低重復編譯腳本帶來的性能損耗。

正例:

第1步:通過stored方式,建script模版:

POST _script/activity_discount_price

{

"script":{

"lang":"pAInless",

"source":"doc.xxx.value * params.discount"

}

}

第2步:調用script腳本模版:cal_activity_discount

GET index/_search

{

"script_fields": {

"discount_price": {

"script": {

"id": "activity_discount_price",

"params":{

"discount": 0.8

}

}}}}

反例:

//直接inline方式,請求中傳入腳本:

GET index/_search

{

"script_fields": {

"activity_discount_price": {

"script": {

"source":"doc.xxx.value * 0.8"

}

}

}

}

18. 避免使用 _all 字段

_all 字段包含了所有的索引字段,如果沒有獲取原始文檔數據的需求,可通過設置Includes、Excludes 屬性來定義放入 _source 的字段。_all 默認將寫入的字段拼接成一個大的字符串,并對該字段進行分詞,用于支持整個 Doc 的全文檢索,“_all”字段在查詢時占用更多的 CPU,同時占用更多的磁盤存儲空間,默認為“false”,不建議開啟該字段和使用。

19. 建議用 Get 查詢替換 Search 查詢

GET/MGET 直接根據文檔 ID 從正排索引中獲取內容。Search 不指定_id,根據關鍵詞從倒排索引中獲取內容。

20. 避免進行多索引查詢

反例:

GET /index1,index2,index3/_search

{

"query": {

"match_all": {}

}

}

21. 避免單次召回大量數據,建議使用 _source_includes 和 _source_excludes 參數來包含或排除字段

大型文檔尤其有用,部分字段檢索可以節省網絡開銷。

參考示例:

// 創建SearchSourceBuilder,并設置查詢條件

SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

sourceBuilder.query(QueryBuilders.matchAllQuery());

// 設置要包含的字段

String[] includes = {"field1", "field2"};

sourceBuilder.fetchSource(includes, Strings.EMPTY_ARRAY);

// 設置要排除的字段

String[] excludes = {"field3"};

sourceBuilder.fetchSource(Strings.EMPTY_ARRAY, excludes);

22. 避免使用 Wildcard 進行中綴模糊查詢

ES 官方文檔并不推薦使用 Wildcard 來進行中綴模糊的查詢,原因在于 ES 內部為了加速這種帶有通配符查詢,會將輸入的字符串 Pattern 構建成一個 DFA (Deterministic Finite Automaton),而帶有通配符的 Pattern 構造出來的 DFA 可能會很復雜,開銷很大。

一口氣看完43個關于 ElasticSearch 的實操建議

建議使用 ES 官方在 7.9 推出的一種專門用來解決模糊查詢慢的 Wildcard 字段類型。與 Text 字段相比,它不會將文本看作是標點符號分割的單詞集合;與 Keyword 字段比,它在中綴搜索場景下具有無與倫比的查詢速度,且對輸入沒有大小限制,這是 Keyword 類型無法相比的。

23. 避免使用 Scripting

Painless 腳本語言語法相對簡單,靈活度高,安全性高,性能高(相對于其他腳本,但是其性能比 DSL 要低)。不適用于非復雜業務,一般 DSL 能解決大部分的問題,解決不了的用類似 Painless 等腳本語言。主要性能影響如下:單次查詢或更新耗時增加,腳本的執行時間相比于其他查詢和更新操作可能會更長,因為在執行腳本之前需要對其進行詞法分析、語法分析和代碼編譯等預處理工作。

24. 避免使用腳本查詢(Script Query)計算動態字段,建議在索引時計算并在文檔中添加該字段

例如,我們有一個包含大量用戶信息的索引,我們需要查詢以"1234"開頭的所有用戶。運行一個腳本查詢如"source":“doc[‘num’].value.startsWith(‘1234’)”。這個查詢非常耗費資源,索引時考慮添加“num_prefix”的keyword字段,然后查詢"name_prefix":“1234”。

三、寫入相關

25. 避免代碼中或手工直接 Refresh 操作

合理設置索引 Settings/Refresh_Interval 時間,通過系統完成 Refresh 動作。

26. 避免單個文檔過大

鑒于默認 http.max_content_length 設置為 100MB,Elasticsearch 將拒絕索引任何大于該值的文檔。

27. 寫入數據不指定 Doc_ID,讓 ES 自動生成

索引具有顯式 ID 的文檔時 ES 在寫入過程中會多一步判斷的過程,即檢查具有相同ID 的文檔是否已經存在于相同的分片中,隨著索引增長而變得更加昂貴。

28. 合理使用 Bulk API 批量寫

大數據量寫入時可以使用 Bulk,但是請求響應的耗時會增加,即使連接斷開,ES 集群內部也仍然在執行。高速大批量數據寫入時,可能造成集群短時間內響應緩慢甚至假死的的情況。

  • 可以通過性能測試確定最佳數量,官方建議大約 5-15mb。
  • 超時時間需要足夠長,建議 60s 以上。
  • 寫入端盡量將數據輪詢打到不同節點上。

29. 腳本刷大量數據,寫入前調大 Refresh Interval,不建議將副本分片為 0,待寫入完成后再調回來

副本分片重新加入節點會觸發副分片恢復 Recovery 流程,如果是大分片會影響集群性能。

四、索引創建

分片

30. 副本分片數大于等于 1

高可用性保證。增加副本數可以一定程度上提高搜索性能;但會降低寫入性能,建議每個主分片對應 1-2 個副本分片即可

31. 官方建議單分片限制最大數據條數不超過 2^32 - 1

32. 索引主分片數量不要設置過大

ES 創建好索引后,一般情況下不再動態調整主分片數量。

每個分片本質上就是一個 Lucene 索引,因此會消耗相應的文件句柄、內存和 CPU 資源。

ES 使用詞頻統計來計算相關性,當然這些統計也會分配到各個分片上,如果在大量分片上只維護了很少的數據,則將導致最終的文檔相關性較差。

一般來說,我們遵循一些原則:

  • 讀場景較多則可以設置少一點,寫場景則可以設置多一些。
  • 控制每個分片占用的硬盤容量不超過ES的最大 JVM 的堆空間設置(32G),因此,如果索引的總容量在 200G 左右,那分片大小在 7-8 個左右即可。
  • 考慮一下 Node 數量,一般一個節點對應一臺物理機,如果分片數遠大于節點數,則一個節點上存在多個分片,一旦該節點故障,即使保持了1個以上的副本,同樣有可能會導致數據丟失,集群無法恢復。所以, 一般都設置分片數不超過節點數的 3 倍

33. 單個分片數據量不要超過 50GB

單個索引的規模控制在 1TB 以內,單個分片大小控制在 30 ~ 50GB ,Docs 數控制在 10 億內,如果超過建議滾動。

Mapping設計

34. 避免使用字段動態映射功能,指定具體字段類型,子類型(若需要),分詞器(特別有場景需要)

35. 對于不需要分詞的字符串字段,使用 Keyword 類型而不是 Text 類型

36. ES 默認字段個數最大 1000,建議不要超過 100

單個 Doc 在建立索引時的運算復雜度,最大的因素不在于 Doc 的字節數或者說某個字段 Value 的長度,而是字段的數量。例如在滿負載的寫入壓力測試中,Mapping 相同的情況下,一個有 10 個字段,200 字節的 Doc, 通過增加某些字段 Value 的長度到 500 字節,寫入 ES 時速度下降很少,而如果字段數增加到 20,即使整個 Doc 字節數沒增加多少,寫入速度也會降低一倍。

37. 對于不索引字段,Index 屬性設置為 False

在下面的例子中,Title 字段的 Index 屬性被設置為 False,表示該字段不會被包含在索引中。而 Content 字段的 Index 屬性默認為 True,表示該字段會被包含在索引中。需要注意的是,即使 Index 屬性被設置為 False,該字段仍然會被保存在文檔中,可以被查詢和聚合。

參考示例:

{

"mappings": {

"properties": {

"title": {

"type": "text",

"index": false

},

"content": {

"type": "text"

}

}

}

}

38. 避免使用 Nested 或 Parent/Child

Nested Query慢,Parent/Child Query 更慢,針對 1 個 Document,每一個 Nested Field 都會生成一個獨立的 Document,這將使 Doc 數量劇增,影響查詢效率尤其是 JOIN 的效率。因此能在 Mapping 設計階段搞定的(大寬表設計或采用比較 Smart 的數據結構),就不要用父子關系的 Mapping。如果一定要使用 Nested Fields,保證 Nested Fields字段不能過多,目前ES默認限制是Index.mapping.nested_fields.limit=50。不建議使用 Nested,那有什么方式來解決 ES 無法 JOIN 的問題?主要有幾種實現方式:

  • 在文檔建模上盡可能在設計時將業務轉化有關聯關系的文檔形式,使用扁平的文檔模型。
  • 獨立索引存儲,實際業務層分多次請求實現。
  • 通過寬表冗余存儲避免關聯。
  • 否則 Nested 和 Parent/Child 存儲對性能均有一定影響,由于 Nested 更新子文檔時需要 Reindex 整個文檔,所以對寫入性能影響較大,適用于 1 對 n(n 較小)場景;Parent/Child 存儲在相同 Type中,寫入相比 Nested性能高,用于 1 對 n(n 較大)場景,但比 Nested 查詢更慢,官網說是 5-10 倍左右。

39. 避免使用 Norms

Norm 是索引評分因子,如果不用按評分對文檔進行排序,設置為“False”。

參考示例:

"title": {"type": "string","norms": {"enabled": false}}

對于 Text 類型的字段而言,默認開啟了 Norms,而 Keyword 類型的字段則默認關閉了 Norms。

開啟 Norms 之后,每篇文檔的每個字段需要一個字節存儲 Norms。對于 Text 類型的字段而言是默認開啟 Norms 的,因此對于不需要評分的 Text 類型的字段,可以禁用 Norms。

40. 對不需要進行聚合/排序的字段禁用列存 Doc_Values

面向列的方式存儲,主要用戶排序、聚合和訪問腳本中字段值等數據訪問場景。幾乎所有字段類型都支持 Doc_Values,值得注意的是,需要分析的字符串字段除外。默認情況下,所有支持 Doc_Values 的字段都啟用了這個功能。如果確定不需要對字段進行排序或聚合,或從腳本訪問字段值,則可以禁用此功能以減少冗余存儲成本。

Keyword和Numeric的選擇

Keyword 類型的主要缺點是在聚合的時候需要構建全局序數,而數值類型則不用。但低基數字段通常會命中大量結果集,例如性別,使用 Numeric 則會在構建 Bitset 上產生很高的代價。

綜上所述,在類型選擇上可以參考下面的原則:

  • 在僅查詢的情況下,如果有 Range 查詢需求,使用 Numeric,否則使用 KeyWord。
  • 在僅聚合的情況下,如果明確字段是低基數的,使用 Keyword 配合 Execution_hint:map,其他情況使用 Numeric。
  • 剩下 Term 查詢+聚合的場景,需要綜合考慮 Numeric 類型 Term 查詢構建 BitSet 和 Keyword 類型構建全局序數哪個代價更大,需要看實際場景,但是目前所知的最壞情況下,構建 Bitset 會導致 CPU 跑滿,構建全局序數的主要問題是帶來的查詢延遲,也會給 JVM 帶來一些壓力。

41. 對于極少使用 Range 查詢的數字值,使用 Keyword 類型

并非所有數值數據都應映射為數值字段數據類型。Elasticsearch 為查詢優化數字字段,例如 Integer or long。如果不需要范圍查找,對于 Term 查詢而言,Keyword 比 Integer 性能更好。

42. 對于有頻繁且較為固定的 Range 查詢字段,增加 Keyword 類型 Pre-Indexing字段。

如果對字段的大多數查詢在一個固定的范圍上運行 Range 聚合,那么可以增加一個 Keyword 類型的字段,通過將范圍“Pre-Indexing”到索引中并使用 Terms 聚合來加快聚合速度。

43. 對需要聚合查詢的高基數 Keyword 字段啟用Eager_Global_Ordinals

  •  

參考:eager_global_ordinals

序號(Ordinals)用于在 Keyword 字段上運行 Terms 聚合。序號用一個自增數值表示,ES 維護這個自增數字與實際值的映射關系,并為每一數值分配一個 Bucket,映射關系是 Segment 級別的。

但是做聚合操作時往往需要結合多個 Segment 的結果,而每個 Segment 的 Ordinals 映射關系是不一致的,所以 ES 會在每個分片上創建全局序號(Global Ordinals)結構 ,一個全局統一的映射,維護全局的 Ordinal 與每個 Segment 的 Ordinal 的映射關系。

默認情況下,Global Ordinals 默認是延時構建,在第一次查詢如 Term Aggregation 使用到時才會構建。因為 ES 不知道哪些字段將用于 Terms 聚合,哪些字段不會。對于基數大的字段,構建成本較大。

啟用 eager_global_ordinals 后,Elasticsearch 會在分片構建時預先計算出全局詞項表,以便在查詢時能夠更快地加載和使用。但啟用 eager_global_ordinals 后,每次執行 Refresh 操作都會構建 Global Ordinals,相當于把搜索時候花費的構建成本轉移到寫入時,所以會對寫入效率有一定的影響,可以配合增大索引的 Refresh Interval 來使用。

參考示例:

PUT index

{

"mappings": {

"type": {

"properties": {

"foo": {

"type": "keyword",

"eager_global_ordinals" : true

}

}

}

}

}

五、總結

最近十年,Elasticsearch 已經成為了最受歡迎的開源檢索引擎,并沉淀了大量的實踐案例及優化總結。在本文中,我們盡可能全面地總結了 Elasticsearch 日常開發中的一些重要實踐&避坑指南,希望能為大家提供 Elasticsearch 使用上的一些借鑒點,歡迎討論!

>>>>參考資料

  • 1.《Elasticsearch 源碼解析與優化實戰》
  • 2.《Elasticsearch權威指南》
  • 3.https://www.easyice.cn/archives/367
  • 4.https://www.elastic.co/guide/en/elasticsearch/guide/current/filter-caching.html#_independent_query_caching
  • 5.https://www.elastic.co/guide/cn/elasticsearch/guide/current/_preventing_combinatorial_explosions.html
  • 6.https://www.elastic.co/guide/en/elasticsearch/reference/current/eager-global-ordinals.html

作者丨希希

來源丨公眾號:得物技術(ID:gh_13ba5621e65c)

分享到:
標簽:ElasticSearch
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定