半年時間聚合桶數量指定都忘了
一、版本
elasticsearch 8.13
二、背景
最近接到一個需求,將 MySQL 中的數據遷移到 Elasticsearch 中,并且相關的業務接口全部切換為使用 Elasticsearch 實現。
其中有個統計的功能,先根據 group 進行分組,然后對每個組內對象的 type 值進行分組統計。
舉個例子:對學生進行統計,相當于先按照班級分組,在統計每個班級里面男女生的人數。
我一想切換到 Elasticsearch 中,相當于嵌套子聚合,一個聚合查詢就出來結果,半小時搞定這個接口。
三、問題來了
1.初始化數據
創建索引:
PUT zuiyu_index
{
"settings": {
"number_of_replicas": 1,
"number_of_shards": 1
},
"mappings": {
"properties": {
"group": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"type": {
"type": "long"
}
}
}
}
插入測試數據:
POST _bulk
{ "index" : { "_index" : "zuiyu_index", "_id" : "1" } }
{ "group" : "1","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "2" } }
{ "group" : "1","type":2 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "3" } }
{ "group" : "1","type":3 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "4" } }
{ "group" : "2","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "5" } }
{ "group" : "2","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "6" } }
{ "group" : "2","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "7" } }
{ "group" : "3","type":2 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "8" } }
{ "group" : "4","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "9" } }
{ "group" : "5","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "10" } }
{ "group" : "6","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "11" } }
{ "group" : "7","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "12" } }
{ "group" : "8","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "13" } }
{ "group" : "9","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "14" } }
{ "group" : "10","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "15" } }
{ "group" : "10","type":2 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "16" } }
{ "group" : "11","type":1 ,"sort":1}
{ "index" : { "_index" : "zuiyu_index", "_id" : "17" } }
{ "group" : "11","type":3 ,"sort":1}
像這種簡單的一條 sql 就出來結果的業務,我一般都是先寫個 sql 語句,然后根據 sql 再寫代碼。所以這里我就先寫了個DSL語句。
2.聚合查詢
GET zuiyu_index/_search
{
"aggregations": {
"agg_group": {
"aggregations": {
"agg_type": {
"terms": {
"field": "type"
}
}
},
"terms": {
"field": "group.keyword"
}
}
},
"query": {
"bool": {
"must": [
{
"terms": {
"group.keyword": ["1","2","3","4","5","6","7","8","9","10","11"]
}
}
]
}
},
"size":0
}
同學們可以看一下上面的語句有問題嗎,如果你能發現問題,那么這篇文章也希望你能看下去,也許會有意想不到的收獲。
提示一下:就像標題所說,可以關心一下聚合桶的數量。
四、發現問題
上述 DSL 語句執行之后,大眼一看 ,結果 OK,是我想要的,那就按這個邏輯直接寫 Java 代碼。上述 DSL 語句中聚合操作對應的 Java 代碼如下:
Aggregation aggType = Aggregation.of(agg -> agg.terms(t -> t.field("type")));
Aggregation aggGroup = Aggregation.of(agg -> agg.terms(t -> t.field("group"))
.aggregations("agg_type", aggType));
做接口數據層的遷移,最簡單的就是修改完業務代碼之后直接對比返回結果,保持返回結果的一致,這樣的修改對于前端來說沒有影響。
所以,修改完代碼之后直接拿接口的返回值與修改之前的版本進行比對,驗證業務邏輯是否一致。直接 F12 控制臺,找到該接口的返回值,復制,粘貼到對比工具中,進行對比。
此處使用的對比工具是 Beyond Compare 。
通過對比返回結果發現,聚合桶的數量少了一個。
上面的例子中,我們的預期結果是,最外層 group 的分組最少11個,排除 group 不存在的情況。這里 terms 中條件 group 在索引中都已存在。
然后我就趕緊再去執行了一遍上面 DSL 語句,一個一個的驗證聚合桶,發現返回結果中竟然沒有 group=9 的桶存在。
DSL 返回結果如下:
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 17,
"relation": "eq"
},
"max_score": null,
"hits": []
},
"aggregations": {
"agg_group": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 1,
"buckets": [
{
"key": "1",
"doc_count": 3,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
},
{
"key": 2,
"doc_count": 1
},
{
"key": 3,
"doc_count": 1
}
]
}
},
{
"key": "2",
"doc_count": 3,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 3
}
]
}
},
{
"key": "10",
"doc_count": 2,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
},
{
"key": 2,
"doc_count": 1
}
]
}
},
{
"key": "11",
"doc_count": 2,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
},
{
"key": 3,
"doc_count": 1
}
]
}
},
{
"key": "3",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 2,
"doc_count": 1
}
]
}
},
{
"key": "4",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
}
]
}
},
{
"key": "5",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
}
]
}
},
{
"key": "6",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
}
]
}
},
{
"key": "7",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
}
]
}
},
{
"key": "8",
"doc_count": 1,
"agg_type": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": 1,
"doc_count": 1
}
]
}
}
]
}
}
}
到了這,其實我還沒想到是什么原因造成的,然后看了好幾遍的聚合語句,都沒有發現問題。一度的自我懷疑,聚合不是這樣用的嗎,嵌套的聚合難道還有花樣?
1.查閱官方文檔
官方文檔肯定是最權威的,所以去官方文檔看看吧,是不是可以給自己點靈感,找到解決方案。
首先去的是如下地址:
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html#run-sub-aggs
官方給的代碼示例好簡單,確實沒毛病,對我沒啥啟發,告辭轉下個網頁。
還是這個網頁,回到頁面頂端,有一個 Bucket 字樣的地方,點進去。
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket.html
在這個頁面,發現可以通過 search.max_buckets 設置請求返回聚合桶的總數,然后我腦海中那丟失的記憶回來了。
想起了在 Elasticsearch 聚合時,聚合桶的返回數量是可以指定的,但是怎么指定,參數是什么,我又忘了。
但是大方向肯定是這個了,我就開始找相關的資料,翻閱官網關于聚合的文檔,終于在官方文檔的嵌套聚合中找到了相關的說明。
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-composite-aggregation.html#_size
大體意思就是size參數可以控制返回聚合桶的數量,默認 10。
但是這里也沒有給出示例,我還是不會用啊。
畢竟咱也是有點 ES 基礎的,內心其實已經有了想法,大概知道怎么用了,只是還得需要確認下。
最后想起來之前寫過關于聚合的文章,抱著試試看的態度,在回顧一下吧。
2.聚合在Elasticsearch中的使用及示例驗證
發文日期,2023年8月2日,真是老了,才半年多的時間都忘了,好了回到主題。
在這篇文檔中,發現了 size 參數的使用,在這里終于確認,聚合桶數量需要指定,并且根據自己的查詢條件進行設置,或直接設置一個最大值兼容自己所有的聚合請求。
所以修改之后的 DSL 語句如下:
GET zuiyu_index/_search
{
"aggregations": {
"agg_group": {
"aggregations": {
"agg_type": {
"terms": {
"field": "type"
}
}
},
"terms": {
"field": "group.keyword",
"size": 11
}
}
},
"query": {
"bool": {
"must": [
{
"terms": {
"group.keyword": ["1","2","3","4","5","6","7","8","9","10","11"]
}
}
]
}
},
"size":0
}
相對應的 Java 代碼也修改:
Aggregation aggType = Aggregation.of(agg -> agg.terms(t -> t.field("type")));
Aggregation aggGroup = Aggregation.of(agg -> agg.terms(t -> t.field("group").size(groupList.size())
.aggregations("agg_type", aggType));
groupList.size() 為 terms 查詢條件值的數量。
五、search.max_buckets
可以通過 _cluster 的 API 設置此參數。
PUT _cluster/settings
{
"transient": {
"search.max_buckets":100
}
}
此處使用的是 transient ,還可以使用 persistent,他倆的區別就是transient 的配置會在集群重啟之后失效,persistent會持久化保存。
其中這個參數在之前的索引分片分配策略一文中講過了,還沒看過的可以跳過去看一下,鏈接我放下面。
Elasticsearch Index Shard Allocation 索引分片分配策略
六、總結
本文通過 demo 示例,從發現聚合桶數量丟失,到排查產生丟失的問題,最后通過 size 參數解決聚合桶數量丟失問題的過程。
意外收獲的是一次請求返回聚合桶數量的總數也是可以通過 search.max_buckets設置的。
日常的積累固然重要,熟悉官方文檔中相關 API 的位置也是必不可少的。