ES系列之原來ES的聚合統計不準確啊

本篇文章不是講ElasticSearch(下面簡稱ES)聚合分析的基本概念和用法的,這些網上的資料很多,不清楚的可以自行查閱。

我下面聚合分析使用的數據都是kibana自帶的,這樣方便有些讀者實際測試文中的示例。

基本概念

ES爲了滿足搜索的實時性,在聚合分析的一些場景會通過損失精準度的方式加快結果的返回。這其實ES在實時性和精準度中間的權衡。

需要明確的是,並不是所有的聚合分析都會損失精準度,比如min,max等這些就沒有精準度的問題。

可能這樣直接說不好理解,下面會有詳細的分析。

問題描述

我們通過一個示例引入問題。

首先我會把kibana自帶的航班信息索引(名爲kibana_sample_data_flights)reindex到我自定義的一個索引(名爲my_flights)中,我的mapping和自帶的索引完全一樣,唯一的區別在於我設置了20個分片。索引的設置如下:

PUT my_flights
{
  "settings": {
    "number_of_shards": 20
  },
  "mappings" : {
      "properties" : {
        "AvgTicketPrice" : {
          "type" : "float"
        },
        省略其它部分

reindex(以後有專門的文章講reindex)的過程比較慢,我的電腦大概需要一分鐘左右。

POST _reindex
{
  "source": {
    "index": "kibana_sample_data_flights"
  },
  "dest": {
    "index": "my_flights"
  }
}

然後我們執行聚合分析的查詢,這個查詢是根據航班的目的地進行分桶。

GET my_flights/_search
{
  "size": 0, 
  "aggs": {
    "dest": {
      "terms": {
        "field": "DestCountry"
      }
    }
  }
}

結果如下,

{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 20,
    "successful" : 20,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "dest" : {
      "doc_count_error_upper_bound" : 52,
      "sum_other_doc_count" : 3187,
      "buckets" : [
        {
          "key" : "IT",
          "doc_count" : 2371
        },
        {
          "key" : "US",
          "doc_count" : 1987
        },
        
        其它部分省略

在返回結果的aggregations中,有兩個值:doc_count_error_upper_bound和sum_other_doc_count,我先來解釋下,

  • doc_count_error_upper_bound:表示沒有在這次聚合中返回,但是可能存在的潛在聚合結果。
  • sum_other_doc_count:表示這次聚合中沒有統計到的文檔數。這個好理解,因爲ES統計的時候默認只會根據count顯示排名前十的分桶。如果分類(這裏是目的地)比較多,自然會有文檔沒有被統計到。

而這個doc_count_error_upper_bound就是我們本文要關注的重點對象,這個指標其實就是告訴用戶本次的聚合結果究竟有多不精確。

問題分析

ES基於分佈式,聚合分析的請求都是分發到所有的分片上單獨處理,最後彙總結果。ES的terms聚合本身是前幾個(size指定)結果,這就導致了結果必然有誤差。

在這裏插入圖片描述

如上圖所示,我們進行一個terms分桶查詢,取前面3個結果。ES給出的結果是 A,B,C三個term,文檔數量分別是12, 6, 4。

但是我們看最下面兩個分片上的文檔分佈,人工也能看出來其實D應該是在結果中的,因爲D的文檔數量有6個,比C多,所以比較精確的結果應該是A,B,D。

產生問題的原因在於ES在對每個分片單獨處理的時候,第一個分片的結果是A,B,C,第二個分片是A,B,D,並且第一個分片的C的文檔數量大於D。所以彙總後的結果是A,B,C。

如何提高精準度

討論完了問題,現在來看看如何解決問題。一般的方案有幾種:

不分片

設置主分片爲1,也就是不分片了。這個顯而易見,上面分析聚合不精確的核心原因就在於分片,所以不分片肯定可以解決問題。但是缺點也是顯然的,只適用於數據量小的情況下,如果數據量大都在一個分片上會影響ES的性能。

我們來做個測試,看看不分片的效果。我們使用自帶的kibana_sample_data_flights索引來執行分桶聚合。

GET kibana_sample_data_flights/_search
{
  "size": 0, 
  "aggs": {
    "dest": {
      "terms": {
        "field": "DestCountry"      
        , "size": 3
      }
    }
  }
}

結果是,

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "dest" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 7605,
      "buckets" : [
        {
          "key" : "IT",
          "doc_count" : 2371
        },
        其它部分省略

因爲kibana_sample_data_flights索引的分片數量是1,所以沒有損失精準度。

提高聚合的數量

如下所示,把size設置成20(默認情況是10)聚合查詢。size是指定聚合返回的結果數量。返回的結果越多,精確度肯定就越高。

GET my_flights/_search
{
  "size": 0, 
  "aggs": {
    "dest": {
      "terms": {
        "field": "DestCountry"      
        , "size": 20
      }
    }
  }
}

結果,

"aggregations" : {
    "dest" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 571,
      "buckets" : [
        {
          "key" : "IT",
          "doc_count" : 2371
        },
        其它部分省略

結果也是沒有精準度的損失了。

調大shard_size值

這個值表示要從分片上拿來計算的文檔數量。默認情況下和size是一樣的。取得size的值越大,結果會越接近準確,不過很明顯會影響性能。

總結

  1. ES某些聚合統計會存在損失精準度的問題
  2. 損失精準度的原因是分片處理中間結果,彙總引起的誤差,是ES實時性和精準度的權衡
  3. 可以通過調大shard_size等方法增加精準度

參考:

  • 極客時間《Elasticsearch核心技術與實戰》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章