Elasticsearch 聚合学习之五:排序结果不准的问题分析
30 : 140984
22 : 140309
25 : 133879
1 : 133233
所有数据,按 name 聚合后,name 相同的文档 value 字段之和:
8 : 182091
9 : 180997
16 : 180885
19 : 180262
15 : 179358
26 : 177340
21 : 176165
4 : 174981
24 : 173179
28 : 171964
23 : 169360
17 : 169294
11 : 168995
10 : 168779
14 : 166495
12 : 166439
3 : 164324
6 : 163910
29 : 163629
2 : 162141
18 : 161742
20 : 161294
5 : 161212
7 : 160690
13 : 160489
27 : 160475
22 : 157978
30 : 155007
25 : 153070
1 : 145807
这份数据集保存在 bulk.json 文件中,您可以在此下载:
https://raw.githubusercontent.com/zq2599/blog_demos/master/files/bulk.json
下载后,用 curl 命令导入这些数据:
curl -H 'Content-Type: application/x-ndjson' -s -XPOST http://192.168.50.75:9200/testcase/t1/_bulk --data-binary @bulk.json
在 bulk.json 中,由 routing 的值来决定数据会存在哪个分片中,已经验证过 routing=a 时会写入第一个分片,routing=b 时写入第二个分片,因此整个 bulk.json 中的 routing 的值只有 a 和 b 两种;
上述数据和统计结果都是用 java 生成的,对应的源码地址在此:
https://raw.githubusercontent.com/zq2599/blog_demos/master/files/GenerateESAggSortData.java
现在数据已经准备好了,可以复现问题了;
复现问题
导入数据成功后,执行以下命令,按照 name 做聚合,将 name 相同的文档的 value 字段的值相加:
GET /testcase/t1/_search
{
"size":0,
"aggs":{
"names":{
"terms": {
"field": "name",
"size" :5,
"order": {
"values": "desc"
}
},
"aggs": {
"values": {
"sum": {
"field": "value"
}
}
}
}
}
}
得到的结果如下:
"buckets" : [
{
"key" : "8",
"doc_count" : 356,
"values" : {
"value" : 182091.0
}
},
{
"key" : "9",
"doc_count" : 356,
"values" : {
"value" : 180997.0
}
},
{
"key" : "16",
"doc_count" : 351,
"values" : {
"value" : 180885.0
}
},
{
"key" : "15",
"doc_count" : 347,
"values" : {
"value" : 179358.0
}
},
{
"key" : "26",
"doc_count" : 353,
"values" : {
"value" : 177340.0
}
}
]
问题已经出现了,返回的数据中,第四名的 name 是 15,但实际上 19 才是第四名,对比列表如下:
| 排名 | 真实数据 | Elasticsearch 返回 |
| --- | --- | --- |
| 1 | 8 : 182091 | 8:182091 |
| 2 | 9 : 180997 | 9:180997 |
| 3 | 16 : 180885 | 16:180885 |
| 4 | 19 : 180262 | 15:179358 |
| 5 | 15 : 179358 | 26:177340 |
分析问题
在聚合排序的操作中,实际上是每个分片自身先做排序,然后将每个分片的前 17 名放在一起再次聚合,再排序,将排序后的前 5 条记录作为结果返回;
为什么用每个分片的前 17 名?这是用官方给出的算式得来的,地址是:https://www.elastic.co/guide/en/elasticsearch/reference/6.1/search-aggregations-bucket-terms-aggregation.html ,如下图:
如果请求只发往一个分片,就返回前 5 条,如果发往多个分片,每个分片返回的条数是:5*1.5+10=17
用一幅图来描述,如下图,汇总数据中的紫色,是由分片一和分片二中的紫色合成的:
如上图所示,分片一的前 17 条记录中,没有 name 等于 19 的记录(因为该记录在分片一的排名是 28),所以两个分片的数据聚合后,name 等于 19 的记录只有分片二的数据中有,即 19:168589,这个值在汇总数据中是排不上前 5 的,于是 ES 返回的 Top5 与真实数据的 Top5 就不一样了,这就是 Elasticsearch 聚合后排序不准的原因。
接下来看看如何解决此问题
解决办法之一
知道问题的原因解决起来就容易了:如果每个分片返回的不是前 17 名,而是前 28 名,那么两个分片中都含有 name 等于 19 的记录,这个指定分片返回数量的参数是 shard_size,加上 shard_size 参数的整个请求如下:
GET /testcase/t1/_search
{
"size":0,
"aggs":{
"names":{
"terms": {
"field": "name",
"size" :5,
"shard_size": 28,
"order": {
"values": "desc"
}
},
"aggs": {
"values": {
"sum": {
"field": "value"
}
}
}
}
}
}
得到结果如下,与真实排名一致:
"buckets" : [
{
"key" : "8",
"doc_count" : 356,
"values" : {
"value" : 182091.0
}
},
{
"key" : "9",
"doc_count" : 356,
"values" : {
"value" : 180997.0
}
},
{
"key" : "16",
"doc_count" : 351,
"values" : {
"value" : 180885.0
}
},
{
"key" : "19",
"doc_count" : 348,
"values" : {
"value" : 180262.0
}
},
{
"key" : "15",
"doc_count" : 347,
"values" : {
"value" : 179358.0
}
}
]
由此可以看出:shard_size 越大,得到的结果准确率越高,如果 shard_size 不低于桶的数量,那么就是准确值了。
评论