為什么 Elasticsearch 中高基數(shù)字段上的聚合是一個(gè)壞主意以及如何優(yōu)化它

來源:CSDN博客 | 2023-09-12 10:11:21 |

Elasticsearch 是分布式搜索和分析引擎,是滿足搜索和聚合需求的最受歡迎的選擇。


【資料圖】

Elasticsearch 提供了 2 種數(shù)據(jù)類型來存儲(chǔ)字符串值:

Text:- 在存儲(chǔ)到倒排索引之前對這些內(nèi)容進(jìn)行分析,并針對全文搜索進(jìn)行優(yōu)化。 文本字段不允許聚合Keyword:- 它們按原樣存儲(chǔ)在倒排索引中,如果需要,可以在查詢期間進(jìn)行分析。 這些針對聚合進(jìn)行了優(yōu)化,因?yàn)樗鼈円惨灾鶢罘绞酱鎯?chǔ)(稱為 doc values),以便可以引用單個(gè)字段,而無需在內(nèi)存中加載完整文檔

有關(guān) text 及 keyword 搜索的更多比較,請參閱我之前的文章 “Elasticsearch:Text vs. Keyword - 它們之間的差異以及它們的行為方式”。

Elasticsearch 將 keyword 存儲(chǔ)為 doc values 中的序數(shù),以獲得更緊湊的表示。 這種映射的工作原理是根據(jù)每個(gè)術(shù)語的字典順序?yàn)槊總€(gè)術(shù)語分配一個(gè)增量整數(shù)或“序數(shù)(ordinal)”。 該字段的 doc values 僅存儲(chǔ)每個(gè)文檔的序數(shù)而不是原始術(shù)語,并具有單獨(dú)的查找結(jié)構(gòu)來在序數(shù)和術(shù)語之間進(jìn)行轉(zhuǎn)換。

在聚合期間使用時(shí),序數(shù)可以極大地提高性能。 作為術(shù)語(terms)聚合的示例,依賴于序數(shù)將文檔分組到分片級別的存儲(chǔ)桶中,然后在跨分片組合結(jié)果時(shí)將序數(shù)轉(zhuǎn)換回其原始術(shù)語值。

每個(gè)分片包含多個(gè) “段(segements)”,其中一個(gè)段就是一個(gè)倒排索引。 分片中的搜索將依次搜索每個(gè)片段,然后將其結(jié)果合并到該分片的最終結(jié)果中。 當(dāng)你為文檔建立索引時(shí),Elasticsearch 將它們收集在內(nèi)存中(為了安全起見,收集在 trasaction log 中),然后每隔一秒左右將一個(gè)新的小段寫入磁盤,并 “刷新” 搜索。這使得新段中的數(shù)據(jù)對搜索可見(即它們是 “可搜索的”)

每個(gè)段都定義自己的序數(shù)映射,但聚合會(huì)跨整個(gè)分片收集數(shù)據(jù)。 因此,為了能夠使用序數(shù)(ordinals)進(jìn)行聚合等分片級操作,Elasticsearch 創(chuàng)建了一個(gè)稱為全局序數(shù)(global ordinals)的統(tǒng)一映射。 全局序數(shù)映射建立在段序數(shù)之上,并通過維護(hù)每個(gè)段的從全局序數(shù)到局部序數(shù)的映射來工作。

必須先構(gòu)建全局序數(shù)映射,然后才能在搜索過程中使用序數(shù)。 默認(rèn)情況下,第一次需要全局序數(shù)時(shí)會(huì)在搜索過程中加載映射。 通常,全局序數(shù)在加載時(shí)間和內(nèi)存使用方面不會(huì)產(chǎn)生很大的開銷。 但是,對于具有大分片的索引,或者如果字段包含大量唯一術(shù)語值,則加載全局序數(shù)可能會(huì)很昂貴。 由于全局序號為分片上的所有段提供了統(tǒng)一的映射,因此當(dāng)新段可見時(shí),它們也需要完全重建。

解決方案

聚合提供了一個(gè)參數(shù) execution_hint,有助于控制存儲(chǔ)桶的收集方式。 它默認(rèn)為 global_ordinals,但可以設(shè)置為 map 來直接使用術(shù)語值。 這避免了創(chuàng)建全局序數(shù)的麻煩并提高了性能,但僅當(dāng)很少有文檔與查詢匹配時(shí)才應(yīng)考慮,否則基于序數(shù)的執(zhí)行模式會(huì)明顯更快。 默認(rèn)情況下,僅在對腳本運(yùn)行聚合時(shí)使用 map,因?yàn)樗鼈儧]有序數(shù)。它可以指定如下:

"aggs": { "make": { "terms": { "field": "make", "execution_hint": "map", "size": 10 } } }

我們還可以使用 eager_global_ordinals,在這種情況下,全局序數(shù)是在刷新分片時(shí)構(gòu)建的。 Elasticsearch 總是在公開索引內(nèi)容的更改之前加載它們。 這將構(gòu)建全局序數(shù)的成本從搜索時(shí)間轉(zhuǎn)移到索引時(shí)間。 可以為字段啟用它,如下所示:

PUT my-index-000001/_mapping{ "properties": { "tags": { "type": "keyword", "eager_global_ordinals": true } }}

可以通過更新 eager_global_ordinals 設(shè)置隨時(shí)禁用預(yù)加載:

PUT my-index-000001/_mapping{ "properties": { "tags": { "type": "keyword", "eager_global_ordinals": false } }}

關(guān)鍵詞: