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

欢迎访问我的GitHub

https://github.com/zq2599/blog_demos

内容:原创文章分类汇总及配套源码,涉及Java、Docker、K8S、Devops等

Elasticsearch上的索引如果有多个分片,那么在聚合排序后取TopN时,返回的结果可能是不准的,今天我们就通过实战来研究分析此问题,并验证解决方法;

环境信息

以下是本次实战的环境信息,请确保您的Elasticsearch可以正常运行:

  1. 操作系统:Ubuntu 18.04.2 LTS
  2. JDK:1.8.0_191
  3. Elasticsearch:6.7.1
  4. Kibana:6.7.1

相关文章列表

  1. 《Elasticsearch聚合学习之一:基本操作》;
  2. 《Elasticsearch聚合学习之二:区间聚合》;
  3. 《Elasticsearch聚合学习之三:范围限定》;
  4. 《Elasticsearch聚合的嵌套桶如何排序》;

复现问题第一步:创建索引

首先是将问题复现,这里我做了个简单的索引,只有两个字段,将索引分为两个分片,然后准备了一些数据写入这两个分片;

在Kibana的Dev Tools执行以下命令,即可创建名为taskcase的索引,该索引有两个分片,只有两个字段:name和value:

PUT /testcase{  "settings": {    "number_of_replicas": 1,    "number_of_shards": 2  },  "mappings" : {      "t1" : {        "properties" : {          "name" : {            "type" : "keyword"          },          "value" : {            "type" : "long"          }        }      }    }}

接下来是导入数据了。

复现问题第二步:导入数据

为了测试的准确性,按照以下要求来制造测试数据:

  1. 按照name字段聚合,name的值不宜太多,否则会有过多的桶不好分析结果;
  2. 能精确的指定哪些数据到分片1,哪些到分片2;

对于这份测试数据,这里先给出聚合结果(在生成数据的时候计算出来的),有了这些结果,我们就能和es聚合结果做对比,发现问题所在:分片一,按name聚合后,name相同的文档value字段之和:

14 : 22491  //14是name,22491是所有name等于14的文档的value字段之和8 : 216324 : 2150215 : 2123426 : 2073110 : 2030617 : 199429 : 1941825 : 1919116 : 187976 : 183063 : 1816622 : 1766924 : 1697127 : 1691118 : 1675823 : 1652713 : 157057 : 1525111 : 1501912 : 143872 : 1432930 : 140235 : 1342129 : 133091 : 1257428 : 1218919 : 1167321 : 1146020 : 10576

分片二,按name聚合后,name相同的文档value字段之和:

19 : 16858921 : 16470516 : 1620889 : 1615798 : 16045928 : 15977515 : 15812426 : 15660924 : 15620811 : 1539764 : 15347923 : 15283312 : 15205220 : 15071829 : 15032017 : 14935210 : 1484732 : 1478125 : 1477913 : 1461586 : 1456047 : 14543918 : 14498413 : 14478414 : 14400427 : 14356430 : 14098422 : 14030925 : 1338791 : 133233

所有数据,按name聚合后,name相同的文档value字段之和:

8 : 1820919 : 18099716 : 18088519 : 18026215 : 17935826 : 17734021 : 1761654 : 17498124 : 17317928 : 17196423 : 16936017 : 16929411 : 16899510 : 16877914 : 16649512 : 1664393 : 1643246 : 16391029 : 1636292 : 16214118 : 16174220 : 1612945 : 1612127 : 16069013 : 16048927 : 16047522 : 15797830 : 15500725 : 1530701 : 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返回
18 : 1820918:182091
29 : 1809979:180997
316 : 18088516:180885
419 : 18026215:179358
515 : 17935826:177340

分析问题

  1. 在聚合排序的操作中,实际上是每个分片自身先做排序,然后将每个分片的前17名放在一起再次聚合,再排序,将排序后的前5条记录作为结果返回;
  2. 为什么用每个分片的前17名?这是用官方给出的算式得来的,地址是:https://www.elastic.co/guide/en/elasticsearch/reference/6.1/search-aggregations-bucket-terms-aggregation.html ,如下图:a7dfbf0915e1d984674b2a3eb34b3cd9.png如果请求只发往一个分片,就返回前5条,如果发往多个分片,每个分片返回的条数是:5*1.5+10=17

用一幅图来描述,如下图,汇总数据中的紫色,是由分片一和分片二中的紫色合成的:03401710b98e86080772d89c46be9cbe.png如上图所示,分片一的前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不低于桶的数量,那么就是准确值了。但实际生产环境中需要结合实际情况来设置shard_size,因为该值越大汇总的数据量就越大,对网络、内存等资源的消耗都会增加,会影响整体性能;

解决办法之二

第二种解决方式就是所有的数据都在一个分片上,具体的方法是创建索引时分片数设置为1,或者在增加数据时指定routing,并且查询的时候也使用该routing,这些方法您可以自行验证,创建一个分片的索引的脚本如下:

PUT /testcase{  "settings": {    "number_of_replicas": 1,    "number_of_shards": 1  },  "mappings" : {      "t1" : {        "properties" : {          "name" : {            "type" : "keyword"          },          "value" : {            "type" : "long"          }        }      }    }}

至此,关于聚合排序不准的实战和分析就全部完成了,在您使用es的聚合后TopN时如果遇到类似问题,希望此文能够给您提供一些参考;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值