写点什么

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

作者:程序员欣宸
  • 2022 年 9 月 19 日
    广东
  • 本文字数:3825 字

    阅读完需:约 13 分钟

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

欢迎访问我的 GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

欢迎访问我的 GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 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聚合学习之四:结果排序》

  5. 《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
复制代码



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 才是第四名,对比列表如下:



分析问题



  • 如果请求只发往一个分片,就返回前 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 不低于桶的数量,那么就是准确值了。

  • 但实际生产环境中需要结合实际情况来设置 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 时如果遇到类似问题,希望此文能够给您提供一些参考;

欢迎关注 InfoQ:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...


发布于: 2022 年 09 月 19 日阅读数: 17
用户头像

搜索"程序员欣宸",一起畅游Java宇宙 2018.04.19 加入

前腾讯、前阿里员工,从事Java后台工作,对Docker和Kubernetes充满热爱,所有文章均为作者原创,个人Github:https://github.com/zq2599/blog_demos

评论

发布
暂无评论
Elasticsearch聚合学习之五:排序结果不准的问题分析_elasticsearch_程序员欣宸_InfoQ写作社区