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

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: Elasticsearch聚合后娶TopN的时候,经常会出现不准确的问题,今天就通过实战来分析这个问题

欢迎访问我的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 : 21632
4 : 21502
15 : 21234
26 : 20731
10 : 20306
17 : 19942
9 : 19418
25 : 19191
16 : 18797
6 : 18306
3 : 18166
22 : 17669
24 : 16971
27 : 16911
18 : 16758
23 : 16527
13 : 15705
7 : 15251
11 : 15019
12 : 14387
2 : 14329
30 : 14023
5 : 13421
29 : 13309
1 : 12574
28 : 12189
19 : 11673
21 : 11460
20 : 10576
  • 分片二,按name聚合后,name相同的文档value字段之和:
19 : 168589
21 : 164705
16 : 162088
9 : 161579
8 : 160459
28 : 159775
15 : 158124
26 : 156609
24 : 156208
11 : 153976
4 : 153479
23 : 152833
12 : 152052
20 : 150718
29 : 150320
17 : 149352
10 : 148473
2 : 147812
5 : 147791
3 : 146158
6 : 145604
7 : 145439
18 : 144984
13 : 144784
14 : 144004
27 : 143564
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

分析问题

在这里插入图片描述

  • 如果请求只发往一个分片,就返回前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时如果遇到类似问题,希望此文能够给您提供一些参考;

欢迎关注阿里云开发者社区博客:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...
相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
2月前
|
自然语言处理 Java 网络架构
elasticsearch学习三:elasticsearch-ik分词器的自定义配置 分词内容
这篇文章是关于如何自定义Elasticsearch的ik分词器配置以满足特定的中文分词需求。
156 0
elasticsearch学习三:elasticsearch-ik分词器的自定义配置 分词内容
|
4月前
|
SQL JSON 大数据
ElasticSearch的简单介绍与使用【进阶检索】 实时搜索 | 分布式搜索 | 全文搜索 | 大数据处理 | 搜索过滤 | 搜索排序
这篇文章是Elasticsearch的进阶使用指南,涵盖了Search API的两种检索方式、Query DSL的基本语法和多种查询示例,包括全文检索、短语匹配、多字段匹配、复合查询、结果过滤、聚合操作以及Mapping的概念和操作,还讨论了Elasticsearch 7.x和8.x版本中type概念的变更和数据迁移的方法。
ElasticSearch的简单介绍与使用【进阶检索】 实时搜索 | 分布式搜索 | 全文搜索 | 大数据处理 | 搜索过滤 | 搜索排序
|
1月前
|
存储 SQL 监控
|
2月前
|
JSON Java 网络架构
elasticsearch学习四:使用springboot整合 rest 进行搭建elasticsearch服务
这篇文章介绍了如何使用Spring Boot整合REST方式来搭建和操作Elasticsearch服务。
148 4
elasticsearch学习四:使用springboot整合 rest 进行搭建elasticsearch服务
|
2月前
|
自然语言处理 搜索推荐 关系型数据库
elasticsearch学习六:学习 全文搜索引擎 elasticsearch的语法,使用kibana进行模拟测试(持续更新学习)
这篇文章是关于Elasticsearch全文搜索引擎的学习指南,涵盖了基本概念、命令风格、索引操作、分词器使用,以及数据的增加、修改、删除和查询等操作。
37 0
elasticsearch学习六:学习 全文搜索引擎 elasticsearch的语法,使用kibana进行模拟测试(持续更新学习)
|
2月前
|
Web App开发 JavaScript Java
elasticsearch学习五:springboot整合 rest 操作elasticsearch的 实际案例操作,编写搜索的前后端,爬取京东数据到elasticsearch中。
这篇文章是关于如何使用Spring Boot整合Elasticsearch,并通过REST客户端操作Elasticsearch,实现一个简单的搜索前后端,以及如何爬取京东数据到Elasticsearch的案例教程。
223 0
elasticsearch学习五:springboot整合 rest 操作elasticsearch的 实际案例操作,编写搜索的前后端,爬取京东数据到elasticsearch中。
|
2月前
|
自然语言处理 Java Maven
elasticsearch学习二:使用springboot整合TransportClient 进行搭建elasticsearch服务
这篇博客介绍了如何使用Spring Boot整合TransportClient搭建Elasticsearch服务,包括项目创建、Maven依赖、业务代码和测试示例。
132 0
elasticsearch学习二:使用springboot整合TransportClient 进行搭建elasticsearch服务
|
2月前
|
存储 JSON Java
elasticsearch学习一:了解 ES,版本之间的对应。安装elasticsearch,kibana,head插件、elasticsearch-ik分词器。
这篇文章是关于Elasticsearch的学习指南,包括了解Elasticsearch、版本对应、安装运行Elasticsearch和Kibana、安装head插件和elasticsearch-ik分词器的步骤。
221 0
elasticsearch学习一:了解 ES,版本之间的对应。安装elasticsearch,kibana,head插件、elasticsearch-ik分词器。
|
3月前
|
存储 自然语言处理 关系型数据库
ElasticSearch基础3——聚合、补全、集群。黑马旅游检索高亮+自定义分词器+自动补全+前后端消息同步
聚合、补全、RabbitMQ消息同步、集群、脑裂问题、集群分布式存储、黑马旅游实现过滤和搜索补全功能
|
4月前
|
自然语言处理 Java 关系型数据库
ElasticSearch 实现分词全文检索 - 聚合查询 cardinality
ElasticSearch 实现分词全文检索 - 聚合查询 cardinality
158 1