ES数值类型慢查询优化

news2024/11/24 7:32:31

现象

某个查询ES接口慢调用告警,如图,接口P999的耗时都在2500ms:
在这里插入图片描述

基本耗时都在查询ES阶段:

在这里插入图片描述

场景与ES设定

慢调用接口为输入多个条件分页查询,慢调用接口调用的ES索引为 express_order_info,该索引通过DTS(数据同步服务)聚合了 订单服务的一张MySQL表 和 分班服务的一张MySQL表 的相关数据:

  • 一个subClazzNumber (用户查询必填) :来自分班服务,辅导班编号;
  • endIdx(查询必填,由后端自动填充):来自分班服务,班级权益相关,查询条件值固定等于-1;
  • 一个userId(用户选填):来自分班服务,用户id;
  • 一个orderNumber(用户选填):来自订单服务,订单编号
  • 订单下单payTime(用户选填):来自订单服务,订单下单时间

ES版本:7.7.1,ES集群有3个节点。order_info索引有3个主分片,2个副本,已存有数据5亿条,每个主分片大小20G。mapping如下:

{
  "express_order_info" : {
    "mappings" : {
      "properties" : {
        ...
        "businessTime" : {
          "type" : "date",
          "format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
        },
        "endIdx" : {
          "type" : "integer"
        },
        "orderNumber" : {
          "type" : "long"
        },
        "subClazzNumber" : {
          "type" : "long"
        },
        "userAddressId" : {
          "type" : "long"
        },
        "userId" : {
          "type" : "long"
        }
      }
    }
  }
}

优化方案

滚动索引方案

首先想到的是不是索引过大导致的查询效率低下。同时,组内订单服务使用的ES使用 Index Template + RollOver + Index Alias 方案稳定运行,该方案每个月会根据Index Template新建物理索引,并更新Index Alias指向该物理索引,所以有效避免了索引过大的情况,且使用别名上层调用无感知。

而慢查询的express_order_info没有任何措施预防索引膨胀问题,考虑新增滚动索引来解决该问题,整体方案如下:

在这里插入图片描述
滚动索引可以参考:ElasticSearch中使用Index Template、Index Alias和Rollover 维护索引

方案可参考:

  • 使用索引别名和Rollover滚动创建索引
  • Aliases API
  • Index templates
  • Rollover API
  • Roll over an index alias with a write index
  • Elasticsearch multindex performance

新增滚动索引涉及到 增量数据同步索引切换 和 存量数据的索引迁移。老索引数据量较大,迁移十分繁杂,也不好协调基础架构的同事;如果不迁移,仍然有查询瓶颈,因为一个索引别名对应多个物理索引时,ES会对每个物理索引都查一遍,只能达到不让慢查询变得更慢的程度。

Feign接口调用替换ES

由于ES存储字段较少,查询不复杂,考虑使用Feign直接调用服务接口,在内存中聚合,但发现不行,无法支持根据订单下单时间进行筛选查询(分班服务没存下单时间),不考虑改方案。

优化查询语句

Java应用端构建的DSL如下:

GET express_order_info/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "filter": [
        {
          "terms": {
            "subClazzNumber": [
              219794********8496
            ],
            "boost": 1
          }
        },
        {
          "terms": {
            "endIdx": [
              -1
            ],
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "sort": [
    {
      "businessTime": {
        "order": "desc"
      }
    }
  ],
  "track_total_hits": 2147483647
}

使用 Search Profiler定位慢的地方:

在这里插入图片描述

在这里插入图片描述

查询耗时集中在endIdx属性查询的build_scorer阶段。

参考网上相似案例:

  • Elasticsearch-spend-all-time-in-build-scorer
  • Elastic对类似枚举数据的搜索性能优化
  • number?keyword?傻傻分不清楚
  • 如何解释将标识字段映射为keyword比integer更好
  • Elasitcsearch 底层系列 Lucene 内核解析之Point索引

现有查询DSL可做出以下优化:

  • endIdx属性type改为keyword
  • 使用range查询endIdx

由于索引mapping不可修改,暂时使用第二种优化,优化后的DSL:

GET express_order_info/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "endIdx": {
              "gte": -1,
              "lte": -1
            }
          }
        },
        {
          "terms": {
            "subClazzNumber": [
              21979*******78496
            ],
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "sort": [
    {
      "businessTime": {
        "order": "desc"
      }
    }
  ],
  "track_total_hits": 2147483647
}

在这里插入图片描述

在这里插入图片描述

查询性能提升明显。

优化语句上线后P999为1300ms,性能提升1倍:
在这里插入图片描述

原理

慢查询耗时主要集中在endIdx的build_scorer阶段,切换后从PointInSetQuery变为IndexOrDocValusQuery
在这里插入图片描述


ES的Query/Filter可以概括为分别为每个查询条件找到满足的docID,然后将各个查询条件的结果进行合并(Conjunction)

参考:
In which order are my Elasticsearch queries/filters executed?


ES在检索在各个查询条件符合的doc之后进行build_scorer,关于build_scorer阶段:

build_scorer:This parameter shows how long it takes to build a Scorer for the query. A Scorer is the mechanism that iterates over matching documents and generates a score per-document (e.g. how well does “foo” match the document?). Note, this records the time required to generate the Scorer object, not actually score the documents. Some queries have faster or slower initialization of the Scorer, depending on optimizations, complexity, etc.
This may also show timing associated with caching, if enabled and/or applicable for the query

注意,build_scorer记录了生成记分器(Scorer)对象而不是对文档进行评分所需的时间,Scorer Object通过迭代所有匹配的doc的方式对doc进行打分和排序,但是filter query不是不进行score吗?关于这个问题,elasticsearch官方的回答是:

Scorers are created for queries and filters, the name is misleading.

The build_scorer operation in Elasticsearch is used to build a scorer for each document in the index. A scorer is used to calculate the relevance score of a document for a given query. In a filter context, no scoring is applied, but Elasticsearch still builds a scorer for each document, which can slow down searches.
This behavior is due to the fact that Elasticsearch is designed to handle both scoring and non-scoring operations in a uniform way. When a query is executed, Elasticsearch builds a scorer for each document in the index, regardless of whether scoring is actually needed for that query. This is because Elasticsearch needs to prepare for the possibility that a scoring operation might be needed later on in the query execution.
If you are dealing with numeric fields, consider using the keyword type instead of the default numeric type. This can improve the performance of TermQuery operations, which are faster than RangeQuery operations.

具体可参考:
Scorers are being created under filter context, while they shouldn’t
Filter context and Build Scorers

Scorer以迭代方式进行打分,那么build_scorer过程如何构建Scorer以支持迭代呢?倒排列表 或 BitSet.

The build_scorer function constructs an iterator internally. This iterator can traverse all matched documents. The construction of the iterator is a time-consuming operation as it involves building inverted lists or bitsets for the result set of docIds from subqueries, and doing conjunction to generate the final iterable docId bitset or inverted list


Lucene6.0以后取消了对数值类型进行倒排索引,而是使用下图的BKD Tree的方式记录doc_id来优化数值类型的Range Query,其中block位于磁盘,其他位于内存。BKD Tree也是以倒排方式查询,即拿着value找doc。

在这里插入图片描述

但也带来了2个问题:

  • 数据量大时的数值类型的Term Query查询慢
  • 数据量大时Range Query查询慢。

为什么慢?以上都涉及到数据量大的问题:

  • 数值类型的TermQuery会被转成PointInSetQuery。BKD-Tree查找到的docId集合在磁盘上并非向 Postlings list 那样按照 docid 顺序存放,也就无法利用 BKD tree 的算法优势了。而此时要实现对 docid 集合的快速 advance 操作,只能将 docid 集合拿出来,然后构造成一个代表 docid 集合的 BitSet。build_scorer阶段创建Scorer的过程即为构造该BitSet,这个过程是导致查询缓慢的罪魁祸首。
  • Range Query的结果集范围非常大的话,advance操作耗费的时间也会越长。

其中,第一个问题就是本文integer类型的endIdx属性进行Term Query时遇到的问题,这里没法直接优化,要么直接用Keyword进行Term Query,要么改造成Range Query。

为什么可以改造成Range Query来提升查询性能? 但是在数据量很大时,Range Query也会很慢啊,因为ES从5.4版本中通过IndexOrDocValuesQuery对Range Query进一步做了优化。

首先,考虑引入IndexOrDocValuesQuery的动机:

Range Query的过程分为以下两个步骤:

  1. 找到所有符合的doc;
  2. 筛选符合的doc中满足其他查询条件的doc(conjunction过程).

针对numeric类型,numeric类型根据BKD Tree(organized by Value,即倒排)能在第1步快速找到符合条件的doc。但是第2步如果用倒排:比如我要在步骤1中的结果里筛选subClazzNumber=xxx的doc,我只能拿着subClazzNumber=xxx去倒排索引查符合subClazzNumber条件的doc,然后把这两个条件的doc做个交集(通过构造BitSet完成)。太慢了,显然,检验endIdx=-1的doc中是否满足subClazzNumber=xxx 使用正排索引更高效,因此,ES引入Doc values数据结构来优化conjunction过程。

Numerics are indexed using a tree structure, called “points”, that is organized by value: it can help you find matching documents for a given range of value, but if you want to verify whether a particular document matches, you have no choice but to compute the set of matching documents and test whether it contains your document.Elasticsearch also has doc values enabled on numeric fields. Doc values are a per-field lookup structure that associates each document with the set of values it contains. This is great for sorting or aggregations, but we could also use them in order to verify matches: this is the right data-structure for it.

关于IndexOrDocValuesQuery,参考:
Better Query Planning for Range Queries in Elasticsearch
再提IndexOrDocValuesQuery的问题
All About Elasticsearch Filter BitSets

即我们endIdx改为Range Query后通过IndexOrDocValuesQuery来查询,IndexOrDocValuesQuery不会去查询所有文档中符合endIdx=-1的,而是在subClazzNumber=xxx的Term Query (lead the iteration)后的doc_id结果中进行确认(verify matches as follow iteration),这样Scorer对象的构建就不用对所有满足endIdx=-1的doc_id生成BitSet。注意只有IndexOrDocValuesQuery为follow iteration时才会使用正排方式筛选结果,即conjuntion从其他查询条件的结果开始,IndexOrDocValuesQuery针对别人的结果进行正排筛选。

也就是ES 5.4版本后,numeric类型在不同的查询场景下时使用不同的Query:

  • Term Query: PointInSetQuery (数据量大会导致慢)
  • Range Query: IndexOrDocValuesQuery (很快啊)

Elasticsearch干货(三):对于数值类型索引优化
K-d Tree是什么
Bkd-Tree: A Dynamic Scalable kd-Tree

总结

在设置mapping时请结合查询场景合理设置type:

numeric 类型在数据区分度很高、量不大(如订单编号这种唯一属性)时,可以使用Term Query;在区分度低、数据量大(如枚举值、状态值)时不要设为numeric类型而是设为Keyword类型进行Term Query,若已经设为numeric类型请使用Range Query,参考本文endIdx查询DSL的优化。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1849927.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FlinkSQL开发经验分享

最近做了几个实时数据开发需求,也不可避免地在使用Flink的过程中遇到了一些问题,比如数据倾斜导致的反压、interval join、开窗导致的水位线失效等问题,通过思考并解决这些问题,加深了我对Flink原理与机制的理解,因此将…

Ubuntu配置ssh+vnc(完整版)

Ubuntu配置sshvnc(完整版) 1 配置ssh 1. 安装openssh-server,配置开机自启 # 更新包 sudo apt-get update # 安装openssh-server sudo apt-get install -y openssh-server # 启动服务 sudo service ssh start # 配置开机自启 sudo systemc…

corepack管理包管理器;nvm管理node版本;nrm管理npm源地址

corepack corepack 管理"包管理器",包括 yarn 和 pnpm。corepack 并不能管理 npm。 corepack 是 nodejs 提供的功能,安装 nodejs 时 corepack 就一起安装了。它还是实验性功能,默认是关闭的,具体介绍看官方文档。 注…

DevOps学习回顾01-技能发展路线-岗位能力-体系认知

事为先,人为重–事在人为 参考来源: 极客时间专栏:DevOps实战笔记,作者:石雪峰 课程链接:https://time.geekbang.org/column/intro/235 时代的典型特征 VUCA VUCA 是指易变性(Volatility&…

高性能并行计算华为云实验一:MPI矩阵运算

目录 一、实验目的 二、实验说明 三、实验过程 3.1 创建矩阵乘法源码 3.1.1 实验说明 3.1.2 实验步骤 3.2 创建卷积和池化操作源码 3.2.1 实验说明 3.2.2 实验步骤 3.3 创建Makefile文件并完成编译 3.4 建立主机配置文件与运行监测 四、实验结果与分析 4.1 矩阵乘法…

qt 简单实验 一个可以向右侧拖拽缩放的矩形

1.概要 目的是设置一个可以拖拽缩放的矩形,这里仅用右侧的一个边模拟这个过程。就是为了抓住核心,这个便解决了,其他的边也是一样的。而这个更能体现原理。 2.代码 2.1 resizablerectangle.h #ifndef RESIZABLERECTANGLE_H #define RESIZ…

Linux驱动调试——使用DEVICE_ATTR实现cat、echo指令调试驱动

在平常做一些驱动调试的时候,每次都写应用去调试相对较麻烦,有一个非常便捷的操作方法就是使用device_attr,只需要执行shell指令例如echo和cat就可以看到效果,不需要再单独写一个测试demo。 看网上很多博客在这一块的使用上写的都…

RK3568平台(音频篇)RT5651解码芯片Codec驱动分析

一.Audio Codec的必要性 在理想状况下,对于录音过程,只需要将麦克风获取到的analog信号通过ADC转换为digital信号并存储即可,对于播放音过程,只需要将digital信号通过DAC转换为analog并输出到speaker播放即可。 但在实际的过程中…

C++ 编程技巧分享

侯捷 C 学习路径:面向对象的高级编程 -> STL库 -> C11新特性 -> cmake 1.1. C 与 C的区别 在C语言中,主要存在两大类内容,数据和处理数据的函数,二者彼此分离,是多对多的关系。不同的函数可以调用同一个数据…

Docker开机自动重启及自动启动容器

Docker开机自动重启及自动启动容器 Windows开机自动重启设置容器自动启动 Windows开机自动重启 勾选 Start Docker Desktop when you sign in to your computer 设置容器自动启动 1.docker update 命令 Usage: docker update [OPTIONS] CONTAINER [CONTAINER...]Update co…

32.基于分隔符解决黏包和半包

LineBasedFrameDecoder 基于换行/n (linux)或回车换行/r/n(windows)进行分割。 使用LIneBasedFrameDecoder构造方法,需要设定一个最大长度。 如果超过了最大长度,还是没有找到换行符,就这位这个数据段太长了,抛出ToolLongFrameException DelimiterBasedFrameDecoder …

IF膨胀时代,“水刊”当赢?2023热门“水刊”影响因子详解!

【欧亚科睿学术】 1 “四大水刊”详情 图片来源:欧亚科睿学术整理 “四大水刊”的影响因子均有所下跌,其中,曾经被列入中科院预警名单的期刊MEDICINE,其影响因子已是连续三年持续下降。从JCR分区来看,四本期刊分区均…

新手(初学者)学R语言第一课,从学正确导入数据开始

初看题目好像我在教你怎么导入数据,不不不,我是在教你正确的导入数据,不是说数据导入R就叫正确导入数据了。本章为新手教程,老手可以跳过。 这个内容早就想写了,今天有点空和大家聊一下。为什么R语言对于新手而言不太友…

threeJS 基础 03---动画

1.动画效果渲染循环 注: 使用循环渲染时,不用手动渲染到画布且再次调用监听更新事件,两者只用使用其中之一即可 周期循环,默认理想状态下每秒循环60次 requestAnimationFrame 渲染帧率(详情见threeJs的文档&#xff…

区块链会议投稿资讯CCF A--WINE 2024 截止7.15 附录用率 附录用的区块链文章

Conference:The Conference on Web and Internet Economics (WINE) CCF level:CCF A Categories:Cross-cutting/comprehensive/emerging Year:2024 Conference time: December 2-5, 2024 录用率: sele…

Pyqt5 + Qt Creator实现QML开发环境配置

先安装Qt Creator, 该软件主要是为了编辑QML文件 在pycharm中配置外部插件,实现Qt Creator的调用 配置完成后,右击qml文件选择Qt Creator就可以直接进行编辑了

高效22KW双向DCDC储能、充电电源模块项目设计开发

22kW 双向CLL谐振变换器的目标是输出电压范围宽、高效率和高功率密度的双向应用,如电动汽车车载充电器和储能系统。研究了一种新的灵活的 CLLC 双向谐振变换器增益控制方案,以便在充放电模式下实现高效率和宽电压增益范围。得益于 Wolfspeed C3MTM 1200V…

Python+Selenium自动化入门

本章内容需有一定Python基础,如何不懂的,请先学习Python。 什么??没有好的学习资料,给你准备好了!! Web自动化环境搭建 1、软件准备 python64位安装包chrome64位浏览器&驱动浏览器驱动下…

基于SpringBoot+协同过滤算法的家政服务平台设计和实现(源码+LW+调试文档+讲解等)

💗博主介绍:✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者,博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 🌟文末获取源码数据库🌟 感兴趣的可以先收藏起来,…

Java 超详细实现导入导出 (包含时间转换问题和样式)

序言 工作中遇到了导入导出问题,并且出现了导入或导出Excel时间格式变为数字的问题。通过学习解决实现了这些功能,记录总结分享给大家。本文将详细介绍如何使用 Java 编程语言和 Apache POI 库来实现这些功能。我们将通过一个示例项目演示如何从数据库中…