1. 程式人生 > >ElasticSearch學習筆記之二十五 索引詞聚合

ElasticSearch學習筆記之二十五 索引詞聚合

ElasticSearch學習筆記之二十五 索引詞聚合

Terms Aggregation(索引詞聚合)

Terms Aggregation是一個動態的多分組聚合,每個分組都有一個獨一無二的索引詞。
例如:

GET /_search
{
    "aggs" : {
        "genres" : {
"terms" : { "field" : "genre" } } } }

響應如下:

{
    ...
    "aggregations" : {
        "genres" : {
            "doc_count_error_upper_bound": 0, 
            "sum_other_doc_count": 0, 
            "buckets" : [ 
                {
                    "key" : "electronic",
                    "doc_count"
: 6 }, { "key" : "rock", "doc_count" : 3 }, { "key" : "jazz", "doc_count" : 2 } ] } } }

doc_count_error_upper_bound代表每個索引詞文件計數的錯誤上限。
當存在大量不同的索引詞,ElasticSearch只返回排名靠前的索引詞分組,sum_other_doc_count代表所有沒有放在響應分組的文件計數之和。

預設情況下,索引詞通過文件計數(doc_count)排序,並且會返回排名前十的索引詞分組,我們可以通過size引數對返回結果的數量進行修改。

Size

size可以用來定義在所有的索引詞分組中需要返回的分組的數量. 預設情況下,協調搜尋程序的節點會請求每個分片提供頂層與規模大小相同的索引詞分組,當所有的分片響應完成是,節點會縮小最終結果的規模並返回給使用者。

注意:
如果你希望在巢狀聚合中檢索索引詞和索引詞的聚合,應該只用Composite aggregation ,該聚合允許在所有可能的索引詞上進行分頁 而不是設定一個大於 terms aggregation基數的size. terms aggregation意味者頂級聚合,不允許分頁。

Document counts are approximate(文件計數是近似值)

正如上面所說的, terms aggregation的文件的計數 (或者任意子聚合的結果) 不總是精確的。這是因為每個分片提供自己的排序檢視並將它們結合成一個最終檢視。考慮到下面的情況:

下面的請求從一個具有3個分片的索引中獲取product欄位按照降序的前5個索引詞,在這種情況下,每個分片會給出前5索引詞。

GET /_search
{
    "aggs" : {
        "products" : {
            "terms" : {
                "field" : "product",
                "size" : 5
            }
        }
    }
}

三個分片中的索引語如下所示,括號內是各自的檔案計數:

Shard A Shard B Shard C
1 Product A (25) Product A (30) Product A (45)
2 Product B (18) Product B (25) Product C (44)
3 Product C (6) Product F (17) Product Z (36)
4 Product D (3) Product Z (16) Product G (30)
5 Product E (2) Product G (15) Product E (29)
6 Product F (2) Product H (14) Product H (28)
7 Product G (2) Product I (10) Product Q (2)
8 Product H (2) Product Q (6) Product D (1)
9 Product I (1) Product J (8)
10 Product J (1) Product C (4)

分片會分辨返回它們前5索引詞,所以結果如下:

Shard A Shard B Shard C
1 Product A (25) Product A (30)
2 Product B (18) Product B (25)
3 Product C (6) Product F (17)
4 Product D (3) Product Z (16)
5 Product E (2) Product G (15)

把3個分片的前5索引詞組合成最終的前5索引詞

排名 數量
1 Product A (100)
2 Product Z (52)
3 Product C (50)
4 Product G (45)
5 Product B (43)

因為產品A是從所有分片中返回的,所以我們知道它的文件計數值是準確的。產品C只被分片A和C返回,所以它的文件計數顯示為50,但這不是一個精確的計數。產品C存在於分片B上,但其計數為4,不足以將產品C放入該分片的前5個列表中。產品Z也只返回2分片,但第三分片不包含這個詞。在組合結果以產生最終的索引詞列表時,Product C文件計數有錯誤而不是Product Z的。產品H在所有3個分片上有44個文件計數,但是沒有包括在最終的術語列表中,因為它沒有在任何分片上進入前5個索引詞。

Shard Size

size屬性設定的越大值越精確, 但是,計算最終結果也越昂貴(這是由於在分片級別上管理的優先順序佇列越大,也是由於節點和客戶端之間的資料傳輸越大)

shard_size 屬性可以用來最小化較大請求大小的額外工作。當定義時,它將決定協調節點將從每個分片請求多少個索引詞。一旦所有分片都響應了,協調節點將根據大小引數將它們減少到最終結果——通過這種方式,可以提高返回的索引詞的精確度,並避免將大量儲存桶列表流回客戶端的開銷。

注意:
shard_size 不能比size小 (因為這樣做沒有意義). 當它比較小時elasticsearch會重寫它和size相等

在單分片索引中shard_size將會和size一樣大, 其他的情況下等於 (size * 1.5 + 10)

Calculating Document Count Error

terms aggregation有2個錯誤值 . 第一個給出聚合的整體值,該值表示未進入最終索引詞列表的索引詞的最大潛在文件計數。這是從從每個分片返回的最後一個索引詞的文件計數的總和計算的。對於上面給出的例子,該值將是46(2 + 15 + 29)。這意味著,在最壞的情況下,沒有返回的索引詞可以具有第四的最高文件計數。

{
    ...
    "aggregations" : {
        "products" : {
            "doc_count_error_upper_bound" : 46,
            "sum_other_doc_count" : 79,
            "buckets" : [
                {
                    "key" : "Product A",
                    "doc_count" : 100
                },
                {
                    "key" : "Product Z",
                    "doc_count" : 52
                }
                ...
            ]
        }
    }
}

設定show_term_doc_count_error 引數為true將使第二個錯誤值生效:

GET /_search
{
    "aggs" : {
        "products" : {
            "terms" : {
                "field" : "product",
                "size" : 5,
                "show_term_doc_count_error": true
            }
        }
    }
}

這顯示了聚合返回的每個項的錯誤值,該值表示文件計數中的最壞情況錯誤,它在決定shard_size引數的取值時會非常有用使用。這是通過彙總所有沒有返回該項的分片返回的最後期限的檔案計數來計算的。在上面的示例中,Product C的文件計數中的錯誤是15,因為Shard B是唯一不返回索引詞的分片,並且它返回的最後一個索引詞的文件計數是15。產品C的實際文件計數是54,所以文件計數實際上只減少了4,即使最糟糕的情況是在15之前。然而,產品A的文件計數有0錯誤,因為每個分片都返回它,所以我們可以確信返回的計數是準確的。

{
    ...
    "aggregations" : {
        "products" : {
            "doc_count_error_upper_bound" : 46,
            "sum_other_doc_count" : 79,
            "buckets" : [
                {
                    "key" : "Product A",
                    "doc_count" : 100,
                    "doc_count_error_upper_bound" : 0
                },
                {
                    "key" : "Product Z",
                    "doc_count" : 52,
                    "doc_count_error_upper_bound" : 2
                }
                ...
            ]
        }
    }
}

這些誤差只能通過降序的文件計數來排序。當聚合由項值本身(升序或降序)排序時,文件計數中沒有錯誤,因為如果一個碎片沒有返回出現在來自另一個碎片的結果中的特定項,那麼它的索引中必須沒有該項。當按照子聚合或按遞增文件計數的順序對聚合進行排序時,無法確定文件計數中的錯誤,並且給出-1值來指示這一點。

Order

通過設定 order 引數可以自定義分組的排序,預設情況下,分組會按照文件計數降序排序。我們也可以改為下面這種

警告:
通過升序計數或子聚合進行排序是不鼓勵的,因為它會增加文件計數的錯誤。當查詢單分片索引時,或者當聚集的欄位被用作索引時間的routing key時,這是沒有問題的:在這些情況下,結果將是準確的,因為分片具有不相交的值。然後,相反的情況下,錯誤是無法避免的。一個可能仍然有用的特殊情況是通過MIN或MAX聚合進行排序:計數不準確,但至少正確選擇頂部分組。

根據文件的 _count 遞增排序:

GET /_search
{
    "aggs" : {
        "genres" : {
            "terms" : {
                "field" : "genre",
                "order" : { "_count" : "asc" }
            }
        }
    }
}

根據索引詞字母順序遞增排序:

GET /_search
{
    "aggs" : {
        "genres" : {
            "terms" : {
                "field" : "genre",
                "order" : { "_key" : "asc" }
            }
        }
    }
}

警告
elasticsearch6之後棄用
_key而不是 _term 去排序索引詞分組

通過單值指標子聚合排序(通過聚合名稱定義):

GET /_search
{
    "aggs" : {
        "genres" : {
            "terms" : {
                "field" : "genre",
                "order" : { "max_play_count" : "desc" }
            },
            "aggs" : {
                "max_play_count" : { "max" : { "field" : "play_count" } }
            }
        }
    }
}

通過多值指標子聚合排序(通過聚合名稱定義):

GET /_search
{
    "aggs" : {
        "genres" : {
            "terms" : {
                "field" : "genre",
                "order" : { "playback_stats.max" : "desc" }
            },
            "aggs" : {
                "playback_stats" : { "stats" : { "field" : "play_count" } }
            }
        }
    }
}

注意
Pipeline aggs 不可以用於排序。
Pipeline aggregations 在所有其他聚合完成之後,在計算階段執行。由於這個原因,它們不可以用於排序

無論聚合路徑上的最後一個聚合是單分組還是多分組,只要聚合路徑是一個單分組型別,就可以基於更深的一層聚合驚醒分組排序。單分組型別排序會繼續分組裡面的文件的數字進行(例如doc_count),多組型別應用相同的規則(多組指標聚合,路徑必須指明用來排序的指標名稱;如果是單組指標聚合,得到的值將用於排序)
路徑的定義方式如下:

AGG_SEPARATOR       =  '>' ;
METRIC_SEPARATOR    =  '.' ;
AGG_NAME            =  <the name of the aggregation> ;
METRIC              =  <the name of the metric (in case of multi-value metrics aggregation)> ;
PATH                =  <AGG_NAME> [ <AGG_SEPARATOR>, <AGG_NAME> ]* [ <METRIC_SEPARATOR>, <METRIC> ] ;
GET /_search
{
    "aggs" : {
        "countries" : {
            "terms" : {
                "field" : "artist.country",
                "order" : { "rock>playback_stats.avg" : "desc" }
            },
            "aggs" : {
                "rock" : {
                    "filter" : { "term" : { "genre" :  "rock" }},
                    "aggs" : {
                        "playback_stats" : { "stats" : { "field" : "play_count" }}
                    }
                }
            }
        }
    }
}

以上將根據搖滾歌曲的平均播放次數對藝術家的國家分組進行排序。

可以使用多個標準來通過提供一組排序標準來排序分組,例如:

GET /_search
{
    "aggs" : {
        "countries" : {
            "terms" : {
                "field" : "artist.country",
                "order" : [ { "rock>playback_stats.avg" : "desc" }, { "_count" : "desc" } ]
            },
            "aggs" : {
                "rock" : {
                    "filter" : { "term" : { "genre" : "rock" }},
                    "aggs" : {
                        "playback_stats" : { "stats" : { "field" : "play_count" }}
                    }
                }
            }
        }
    }
}

以上將根據搖滾歌曲的平均播放次數,然後按doc_count的順序對藝術家的國家分組進行排序。

注意

如果兩個分組對於所有訂單標準共享相同的值,則分組的索引詞將作為按字母順序升序的繫結斷路器,以防止分組的非確定性排序。

Minimum document count

使用min_doc_count來只返回索引詞計數大於它的分組。

GET /_search
{
    "aggs" : {
        "tags" : {
            "terms" : {
                "field" : "tags",
                "min_doc_count": 10
            }
        }
    }
}

上面的聚合只返回在10個或更多點選中找到的hints。預設值為1。

索引詞被收集並排序在分片級別,並與第二步驟中從其他分片收集的索引合併。但是,分片沒有關於可用的全域性文件計數的資訊。是否將一個項新增到候選列表的決定僅取決於使用本地分片頻率對碎分計算的順序。僅在合併所有分片的區域性項統計量後才應用MixDoc計數準則。在某種程度上,將索引詞作為候選項新增的決定是在不確定索引詞是否真正達到所需的min_doc_count的情況下作出的。如果低頻項填充到候選列表中,這可能導致許多(全球)高頻項在最終結果中丟失。為了避免這一點,可以增加shard_size引數,以允許碎片上更多的候選項。然而,這增加了記憶體消耗和網路流量。

shard_min_doc_count 引數

shard_min_doc_count引數用於調節如果索引詞實際上應該被新增到候選列表或者不應該被新增到min_doc_count中的分片所具有的確定性。如果集合中的區域性碎片頻率高於shard_min_doc_count計數,則僅考慮索引詞。如果您的文件包含許多低頻索引詞,並且您對這些索引詞不感興趣(例如,拼寫錯誤),那麼您可以設定shard_min_doc_count引數以篩選分片級別上的候選索引詞,即使經過合理的確定也不會達到所需的min_doc_count。合併本地計數。預設情況下,shard_min_doc_count設定為0,除非顯式設定,否則不會產生任何影響。

注意
設定shard_min_doc_count=0也將返回與不匹配的條件的分組。然而,一些返回的文件計數為零的索引詞可能只屬於從其他型別刪除的文件或文件,因此沒有保證._all查詢將找到這些索引詞的正文件計數。

警告
當不是用文件計數降序排序時,min_doc_count取值太大將使返回一些小於大小的分組,因為沒有從分片收集足夠的資料。丟失分組可以通過增加shard_size來恢復。設定shard_min_doc_count值太高會導致在分片級別上過濾掉索引詞。這個值應該比min_doc_count/#shards低很多。

Script

用script獲取索引詞:

GET /_search
{
    "aggs" : {
        "genres" : {
            "terms" : {
                "script" : {
                    "source": "doc['genre'].value",
                    "lang": "painless"
                }
            }
        }
    }
}

這將將指令碼引數解釋為具有預設指令碼語言和指令碼引數的內聯指令碼。使用儲存的指令碼使用以下語法:

GET /_search
{
    "aggs" : {
        "genres" : {
            "terms" : {
                "script" : {
                    "id": "my_script",
                    "params": {
                        "field": "genre"
                    }
                }
            }
        }
    }
}

Value Script

GET /_search
{
    "aggs" : {
        "genres" : {
            "terms" : {
                "field" : "genre",
                "script" : {
                    "source" : "'Genre: ' +_value",
                    "lang" : "painless"
                }
            }
        }
    }
}

Filtering Values

我們可以利用includeexclude欄位對建立分組的索引詞進行過濾,這個過程基於整個表示式或者一組精確值。此外,include子句可以使用分割槽表示式進行篩選。

Filtering Values with regular expressions

GET /_search
{
    "aggs" : {
        "tags" : {
            "terms" : {
                "field" : "tags",
                "include" : ".*sport.*",
                "exclude" : "water_.*"
            }
        }
    }
}

上面的例子中,分組會包含所有tags欄位包含sport並且不是water開頭的(所以water_sports不會被聚合)。include子句將決定哪些值允許被聚合, exclude子句決定哪些值不會被聚合. 當同時定義的時候exclude具有優先權,這也意味者,首先評估include,然後才exclude
語法和正則表示式的語法一致。

Filtering Values with exact values

對於精確詞的匹配,includeexclude欄位可以簡單的對一個字串陣列進行復制,表示在索引中被查詢的索引詞。

GET /_search
{
    "aggs" : {
        "JapaneseCars" : {
             "terms" : {
                 "field" : "make",
                 "include" : ["mazda", "honda"]
             }
         },
        "ActiveCarManufacturers" : {
             "terms" : {
                 "field" : "make",
                 "exclude" : ["rover", "jensen"]
             }
         }
    }
}

Filtering Values with partitions

有時,在單個請求/響應對中要處理的唯一索引詞太多,因此將分析分解為多個請求可能是最好的解決方案。這可以通過在查詢時將欄位值分組到多個分割槽中並在每個請求中只處理一個分割槽來實現。考慮這個請求,尋找最近沒有登入任何訪問的帳戶:

GET /_search
{
   "size": 0,
   "aggs": {
      "expired_sessions": {
         "terms": {
            "field": "account_id",
            "include": {
               "partition": 0,
               "num_partitions": 20
            },
            "size": 10000,
            "order": {
               "last_access": "asc"
            }
         },
         "aggs": {
            "last_access": {
               "max": {
                  "field": "access_date"
               }
            }
         }
      }
   }
}

This request is finding the last logged access date for a subset of customer accounts because we might want to expire some customer accounts who haven’t been seen for a long while. The num_partitions setting has requested that the unique account_ids are organized evenly into twenty partitions (0 to 19). and the partition setting in this request filters to only consider account_ids falling into partition 0. Subsequent requests should ask for partitions 1 then 2 etc to complete the expired-account analysis.

Note that the size setting for the number of results returned needs to be tuned with the num_partitions. For this particular account-expiration example the process for balancing values for size and num_partitions would be as follows:

Use the cardinality aggregation to estimate the total number of unique account_id values
Pick a value for num_partitions to break the number from 1) up into more manageable chunks
Pick a size value for the number of responses we want from each partition
Run a test request
If we have a circuit-breaker error we are trying to do too much in one request and must increase num_partitions. If the request was successful but the last account ID in the date-sorted test response was still an account we might want to expire then we may be missing accounts of interest and have set our numbers too low. We must either

increase the size parameter to return more results per partition (could be heavy on memory) or
increase the num_partitions to consider less accounts per request (could increase overall processing time as we need to make more requests)
Ultimately this is a balancing act between managing the Elasticsearch resources required to process a single request and the volume of requests that the client application must issue to complete a task.

Multi-field terms aggregation

terms aggregation不支援在同一個文件中採集多個索引詞. 原因在於terms agg 並不支援採集索引詞字串本身的值, 而是利用global ordinals來生成一個欄位中所有唯一值的列表,global ordinals可以帶來重要的效能提升。但同時不能跨多欄位聚合。

有下面2中方法在多個索引詞上執行聚合:

Script

利用指令碼在多個欄位上重複進行索引詞聚合,這會使global ordinals失效並且會比從一個欄位上詞啊擠索引詞更慢。

copy_to field

如果你提前知道需要從哪些欄位上採集索引詞,就可以使用copy_to構造一個組合欄位,可以在這個欄位上執行聚合,並且可以利用global ordinals的優化效果。

未完待續。。。