Elasticsearch 聚合數據結果不精確,怎麼破?

一、實戰開發遇到聚合問題
請教一個問題,ES 在聚合的時候發生了一個奇怪的現象聚合的語句裏面size設置爲10和大於10致使聚合的數量不一致,這個size不就是返回的條數嗎?會影響統計結果嗎?dsl語句摘要(手機敲不方便,雙引號就不寫了):面試

aggs:{topcount:{terms:{field:xx,size:10}}}
就是這個size,設置10和大於10將會致使聚合結果不同,難道是es5.x的bug嗎?微信

以上是實戰中的真實問題,基於這個問題,有了本篇文章。網絡

本文探討的聚合主要指:terms 分桶聚合。下圖爲分桶 terms 聚合示意圖。elasticsearch

Elasticsearch 聚合數據結果不精確,怎麼破?

從一堆多分類的產品中聚合出 TOP 3 的產品分類和數量。TOP3 結果:ide

產品 Y:4
產品 X:3
產品 Z:2
二、前提認知:Elasticsearch terms 分桶聚合結果是不精確的
2.1 Elasticsearch 分片 和 副本
Elasticsearch 索引由一個或多個主分片以及零個或者多個副本分片組成。性能

Elasticsearch 聚合數據結果不精確,怎麼破?
索引的大小超過了單個節點的硬件限制,分片就能夠解決。fetch

分片包含索引數據的一個子集,而且自己具備徹底的功能和獨立性,你能夠將分片視爲「獨立索引」。es5

分片的核心要義:spa

分片能夠拆分並擴展數據量。
若是數據量不斷增長,將會遇到存儲瓶頸。舉例:有1TB的數據,但只有兩個節點(單節點512GB存儲)?單獨沒法存儲,切分分片後,問題遊刃有餘的解決。
操做能夠分佈在多個節點上,從而能夠並行化提升性能。
主分片:寫入過程先寫主分片,寫入成功後再寫入副本分片,恢復階段也以主分片爲主。blog

副本分片的目的:

在節點或分片發生故障時提供高可用性。
副本分片永遠不會分配給與主分片相同的節點。

提升搜索查詢的性能。
由於能夠在全部主、副本上並行執行搜索、聚合操做。

2.2 分片的分配機制
Elasticsearch 如何知道要在哪一個分片上存儲新文檔,以及在經過 ID 檢索它時如何找到它?

默認狀況下,文檔應在節點之間平均分配,這樣就不會有一個分片包含的文檔比另外一個分片多很是多。

肯定給定文檔應存儲在哪一個分片的機制稱爲:路由。

爲了使 Elasticsearch 儘量易於使用,默認狀況下會自動處理路由,而且大多數用戶不須要手動 reroute 處理它。

Elasticsearch 使用以下圖的簡單的公式來肯定適當的分片。

Elasticsearch 聚合數據結果不精確,怎麼破?

shard = hash(routing) % total_primary_shards
routing: 文檔 id,能夠本身指定或者系統生成 UUID。
total_primary_shards:主分片數。
這裏推演一道面試題:一旦建立索引後,爲何沒法更改索引的主分片數量?

考慮如上路由公式,咱們就能夠找到答案。

若是咱們要更改分片的數量,那麼對於文檔,運行路由公式的結果將發生變化。

假設:設置有 5 個分片時文檔已存儲在分片 A 上,由於那是當時路由公式的結果。

後面咱們將主分片更改成7個,若是再嘗試經過ID查找文檔,則路由公式的結果可能會有所不一樣。

如今,即便文檔實際上存儲在Shard A上,該公式也可能會路由到ShardB。這意味着永遠不會找到該文檔。

以此能夠得出:主分片建立後不能更改的結論。

較真的同窗,看到這裏可能會說:不是還有 Split 切分分片和 Shrink 壓縮分片機制嗎?

畢竟Split 和 Shrink 對分片的處理是有條件的(如:都須要先將分片設置爲只讀)。

因此,長遠角度仍是建議:提早根據容量規模和增量規模規劃好主分片個數。

2.3 Elasticsearch 如何檢索 / 聚合數據?
接收客戶端請求的節點爲:協調節點。以下圖中的節點 1 。

在協調節點,搜索任務被分解成兩個階段:query 和 fetch 。

真正搜索或者聚合任務的節點稱爲:數據節點。以下圖中的:節點 二、三、4。

Elasticsearch 聚合數據結果不精確,怎麼破?

聚合步驟:

客戶端發送請求到協調節點。
協調節點將請求推送到各數據節點。
各數據節點指定分片參與數據聚集工做。
協調節點進行總結果聚集。
2.4 示例說明 聚合結果不精確
集羣:3個節點,3個主分片,每一個分片有5個產品的數據。用戶指望返回Top 3結果以下:

產品X:40
產品A:40
產品Y:35
用戶執行以下 terms 聚合,指望返回集羣 prodcuts 索引Top3 結果。

POST products/_search
{
"size":0,
"aggs": {
"product_aggs": {
"terms": {
"field":"name.keyword",
"size":3
}
}
}
}
實際執行以下圖所示:各節點的分片:取本身的Top3 返回給協調節點。協調節點聚集後結果爲:

產品Y:35,
產品X: 35,
產品A:30。
這就產生了實際聚合結果和預期聚合結果不一致,也就是聚合結果不精確。

Elasticsearch 聚合數據結果不精確,怎麼破?

致使聚合不精確的緣由分析:

效率因素:每一個分片的取值Top X,並非彙總所有的 TOP X。
性能因素:ES 能夠不每一個分片Top X,而是全量聚合,但勢必這會有很大的性能問題。
三、如何提升聚合精確度?
思考題——terms 聚合中的 size 和 shard_size 有什麼區別?

size:是聚合結果的返回值,客戶指望返回聚合排名前三,size值就是 3。
shard_size: 每一個分片上聚合的數據條數。shard_size 原則上要大於等於 size(若設置小於size,實則沒有意義,elasticsearch 會默認置爲size)
請求的size值越高,結果將越準確,但計算最終結果的成本也將越高。

那到底如何提供聚合精準度呢?這裏提供了四種方案供參考:

方案1:設置主分片爲1
注意7.x版本已經默認爲1。

適用場景:數據量小小集羣規模業務場景。

方案2:調大 shard_size 值
設置 shard_size 爲比較大的值,官方推薦:size*1.5+10

適用場景:數據量大、分片數多的集羣業務場景。

shard_size 值越大,結果越趨近於精準聚合結果值。

此外,還能夠經過show_term_doc_count_error參數顯示最差狀況下的錯誤值,用於輔助肯定 shard_size 大小。

方案3:將size設置爲全量值,來解決精度問題
將size設置爲2的32次方減去1也就是分片支持的最大值,來解決精度問題。

緣由:1.x版本,size等於 0 表明所有,高版本取消 0 值,因此設置了最大值(大於業務的全量值)。

全量帶來的弊端就是:若是分片數據量極大,這樣作會耗費巨大的CPU 資源來排序,並且可能會阻塞網絡。

適用場景:對聚合精準度要求極高的業務場景,因爲性能問題,不推薦使用。

方案4:使用Clickhouse 進行精準聚合
在星球微信羣裏,張超大佬指出:分析系統裏跑全量的 group by 我以爲是合理的需求, clickhouse很擅長作這種事,es若是不在這方面增強,分析場景不少會被 clickhouse替掉。

騰訊大佬指出:聚合這塊比較看場景。由於我這邊有一些業務是作聚合,也就是 olap 場景,多維分析,ES並非特別擅長。若是有豐富的多維分析場景,還有比較高的性能要求。我建議能夠調研下clickhouse。咱們這邊測評過開源和內部的 大部分場景 clickhouse 幾十億的級別,基本也在秒級返回甚至毫秒級。

此外,除了 chlickhouse, spark也有相似聚合的功能。

適用場景:數據量很是大、聚合精度要求高、響應速度快的業務場景。

四、小結
回到開頭提到的問題,設置10和大於10將會致使聚合結果不同是因爲 Elasticsearch 聚合實現機制決定的,不是Bug。Elasticsearch自己不提供精準分桶聚合。要提升聚合精度,參考文章提到的幾種方案。

你們有更好的精度提高方案,歡迎留言交流。

Elasticsearch 聚合數據結果不精確,怎麼破?

相關文章
相關標籤/搜索