写点什么

Elasticsearch 聚合学习之五:排序结果不准的问题分析

用户头像
极客good
关注
发布于: 刚刚

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 字段的值相加:


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


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 |

分析问题

  1. 在聚合排序的操作中,实际上是每个分片自身先做排序,然后将每个分片的前 17 名放在一起再次聚合,再排序,将排序后的前 5 条记录作为结果返回;

  2. 为什么用每个分片的前 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 不低于桶的数量,那么就是准确值了。

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
Elasticsearch聚合学习之五:排序结果不准的问题分析