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月前
|
SQL JSON 大数据
ElasticSearch的简单介绍与使用【进阶检索】 实时搜索 | 分布式搜索 | 全文搜索 | 大数据处理 | 搜索过滤 | 搜索排序
这篇文章是Elasticsearch的进阶使用指南,涵盖了Search API的两种检索方式、Query DSL的基本语法和多种查询示例,包括全文检索、短语匹配、多字段匹配、复合查询、结果过滤、聚合操作以及Mapping的概念和操作,还讨论了Elasticsearch 7.x和8.x版本中type概念的变更和数据迁移的方法。
ElasticSearch的简单介绍与使用【进阶检索】 实时搜索 | 分布式搜索 | 全文搜索 | 大数据处理 | 搜索过滤 | 搜索排序
|
1月前
|
存储 自然语言处理 关系型数据库
ElasticSearch基础3——聚合、补全、集群。黑马旅游检索高亮+自定义分词器+自动补全+前后端消息同步
聚合、补全、RabbitMQ消息同步、集群、脑裂问题、集群分布式存储、黑马旅游实现过滤和搜索补全功能
ElasticSearch基础3——聚合、补全、集群。黑马旅游检索高亮+自定义分词器+自动补全+前后端消息同步
|
2月前
|
自然语言处理 Java 关系型数据库
ElasticSearch 实现分词全文检索 - 聚合查询 cardinality
ElasticSearch 实现分词全文检索 - 聚合查询 cardinality
39 1
|
3月前
|
数据库
面试题ES问题之Elasticsearch的排序分页和高亮功能如何解决
面试题ES问题之Elasticsearch的排序分页和高亮功能如何解决
30 0
|
4月前
|
SQL 安全 数据挖掘
Elasticsearch如何聚合查询多个统计值,如何嵌套聚合?并相互引用,统计索引中某一个字段的空值率?语法是怎么样的?
Elasticsearch聚合查询用于复杂数据分析,包括统计空值率。示例展示了如何计算字段`my_field`非空非零文档的百分比。查询分为三步:总文档数计数、符合条件文档数计数及计算百分比。聚合概念涵盖度量、桶和管道聚合。脚本在聚合中用于动态计算。常见聚合类型如`sum`、`avg`、`date_histogram`等。组合使用可实现多值统计、嵌套聚合和空值率计算。[阅读更多](https://zhangfeidezhu.com/?p=515)
284 0
Elasticsearch如何聚合查询多个统计值,如何嵌套聚合?并相互引用,统计索引中某一个字段的空值率?语法是怎么样的?
|
4月前
|
存储 缓存 自然语言处理
elasticsearch 聚合 : 指标聚合、桶聚合、管道聚合解析使用总结
elasticsearch 聚合 : 指标聚合、桶聚合、管道聚合解析使用总结
|
4月前
|
缓存 Java API
在生产环境中部署Elasticsearch:最佳实践和故障排除技巧——聚合与搜索(三)
在生产环境中部署Elasticsearch:最佳实践和故障排除技巧——聚合与搜索(三)
|
5月前
|
存储 缓存 自然语言处理
Elasticsearch框架学习的难点和重点有哪些
Elasticsearch是基于Lucene的开源搜索引擎,广泛应用于全文检索和日志分析。学习重点包括理解节点、集群、索引、分片和副本等基本概念,掌握数据索引、查询DSL、聚合和性能优化。倒排索引和分词器是全文搜索的核心,集群管理和监控对于稳定性至关重要。实践中需根据数据量和查询模式优化分片策略,利用缓存提升搜索性能。学习Elasticsearch要结合实际项目,关注官方文档和社区资源。【5月更文挑战第6天】
|
1月前
|
NoSQL 关系型数据库 Redis
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
mall在linux环境下的部署(基于Docker容器),docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongodb、minio详细教程,拉取镜像、运行容器
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
|
2月前
|
数据可视化 Docker 容器
一文教会你如何通过Docker安装elasticsearch和kibana 【详细过程+图解】
这篇文章提供了通过Docker安装Elasticsearch和Kibana的详细过程和图解,包括下载镜像、创建和启动容器、处理可能遇到的启动失败情况(如权限不足和配置文件错误)、测试Elasticsearch和Kibana的连接,以及解决空间不足的问题。文章还特别指出了配置文件中空格的重要性以及环境变量中字母大小写的问题。
一文教会你如何通过Docker安装elasticsearch和kibana 【详细过程+图解】