征服ES(ElasticSearch)的慢查询实战

766 阅读4分钟

在Elasticsearch(ES)中,大数据查询可能会因为多种原因导致性能大幅下降。以下是几种常见的情况及其解决方案。

  1. 深分页、大排序:大量数据扫描和多分片上的多次排序会严重拖慢性能。
  2. 通配符查询:导致全表扫描。
  3. 正则表达式查询:同样导致全表扫描。
  4. 高基数字段聚合:高基数排序会耗尽内存和计算资源,比如按照玩家ID分组。
  5. 脚本查询:脚本执行在每个文档上进行,消耗CPU和内存,无法利用缓存。
  6. 大字段全文搜索:大字段的倒索引和存储非常耗费资源。

下面分类介绍一下我总结的解决方案。

一、 深分页、大排序

深分页的性能问题来源于ES需要扫描和排序大量数据,这期间不仅要下推到每个分片上进行扫描排序,还需要在主查询节点上召回汇总,涉及到二次排序。

1. 深分页

深分页的问题在于总记录数量的处理,可以从产品和技术两个方面进行改进。

产品方面:

  • 使用虚拟滚动实现分页。
  • 限制总数track_total_hits,控制在百万以内。例如,totalCount最多记录100万,多余的直接显示为100w+。

技术方面:

  • 使用search_after查询方式代替偏移量查询,性能会好很多。但要注意尽量加入id或其他为一字段,因为after的条件必须为一才能保证结果准确性。
POST /randy_index/_search
{
  "size": 10,
  "query": {
    "match_all": {}
  },
  "sort": [
    {"timestamp": "asc"},
    {"_id": "asc"}
  ]
}

POST /randy_index/_search
{
  "size": 10,
  "query": {
    "match_all": {}
  },
  "sort": [
    {"timestamp": "asc"},
    {"_id": "asc"}
  ],
  "search_after": [1672534800000, "2"]
}

2. 大排序

大排序指的是在大量数据上进行排序。虽然没有完美的解决方案,但可以尝试以下方法:

  • 多使用filter过滤:ES执行顺序是query -> filter -> sort,前两步如果能排除掉更多的数据,sort就会处理更少的数据。
  • 列存储:将keyword类型的字段不要关闭doc_values,列存储的keyword对聚合和排序性能更好。以存储空间换性能。

二、通配符查询和正则查询

这两种查询容易导致全表扫描,解决方案包括:

  1. 用prefix实现:对字段创建正序和倒序两个值索引字段,用prefix来查询。
  2. 使用gram分词:ngram或者edge gram分词,虽然会多占用一些索引空间,但查询会更高效。

注意:模糊查询的字段不需要进行评分,应放到filter中。另外这也是一个存储空间换性能的方案。

三、高基数聚合查询

高基数聚合查询常见于数据统计场景,当然也有一些处于产品需求考虑(比如从订单详情的物化索引中聚合出订单列表)。

  • 产品和技术配合:确定需求理解无误后,可以独立创建另一个粒度的索引。
  • 技术手段:使用composite多桶聚合,降低查询压力;预先用cardinality判断基数大小。

四、脚本查询

脚本查询常见于字段需要二次处理或排序时,这两种场景说白了都是在数据的预处理不足。

说句题外话,大家往往认为ES就是一个全能的查询,什么样的查询只要能写出来就能实现,这种思路是不对的。 无论从软件工程化的角度出发还是从维护性和性能出发都不要把ES当做一个复杂的业务服务,要把他当成一种数据存储引擎。 灵活性有的时候还是要为性能做一些让路的。这不是说它不能做这种查询,但是如果你想让它高效,就要考虑它的实现原理和特性。

处理这种问题我的两个常用手段

  • 数据预处理:新建索引,数据reindex时运行脚本,补全需要查询或排序的字段。
  • 脚本优化:尽量将脚本放在filter阶段,并且只保留一个脚本。脚本尽量使用ES官方的Painless来开发脚本。

五、大字段全文搜索

这个问题其实挺有意思的,涉及到一个经验值。首先要知道什么样的文本属于大文本:

  • 小文本:长度在1KB以下(约1000字符以内)。
  • 中等文本:长度在1KB到10KB之间(约1000到10000字符)。
  • 大文本:长度在10KB以上(约10000字符以上)。

name处理大文本的两种思路:

  1. 分片分段:将大文本字段拆分为多个较小的字段或段落,分别进行索引和查询。
  2. 语义搜索:引入embedding向量查询,放弃传统的分词搜索。