ES数值类型慢查询优化

现象

某个查询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.mzph.cn/web/32016.shtml

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

相关文章

STM32人工智能检测-筛选机器人

前言 本文描述了一种使用STM32进行机器人筛选的办法。筛选对象是我的粉s,删选办法是瞪眼法。 问题现象 每次当我的STM32 向外界发出一篇新的的报文,总能在1H之内得到focus,格式如下 [title][body][tail]于是我对各个focus 我报文的对象进…

Redis数据过期、淘汰策略

数据过期策略: 惰性删除: 设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。 这种方式对cpu友好(只在用…

C# OCCT Winform 界面搭建

目录 1.创建一个WInform项目 2.代码总览 代码解析 3.添加模型到场景 4.鼠标交互 1.创建一个WInform项目 2.代码总览 using Macad.Occt.Helper; using Macad.Occt; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Remoting.Co…

vue简介实例

先看样式 再看代码 <div v-else class"relative mt-4 h-44 cursor-pointer overflow-hidden rounded-xl"><divclass"absolute flex h-44 w-full blur-lg":style"{ backgroundImage: url(${currentSongList.list[0]?.coverImgUrl}) }"…

STM32单片机USART串口收发数据包

文章目录 1. 串口通信 1.1 串口初始化 1.2 库函数 2. 串口收发HEX数据包 2.1 Serial.c 2.2 Serial.h 2.3 main.c 3. 串口收发文本数据包 3.1 Serial.c 3.2 Serial.h 3.3 main.c​​​​​​​ 1. 串口通信 对于串口通信的详细​​​​​​​解析可以看下面这篇文章…

Java 图书管理系统功能实现

承接上一篇的 图书管理系统 &#xff0c;点击这里跳转 要实现什么功能 1.查找图书 2.增加图书 3.删除图书 4.展示图书 5.退出系统 6.借阅图书 7.归还图书 1.查找图书 要完成这个功能需要以下步骤 输入书名&#xff0c; 然后在书架里找到这本书打印出来&#xff0c;…

04_FFmpeg常用API及内存模型

【说明】课程学习地址&#xff1a;https://ke.qq.com/course/468797 FFmpeg内存模型 FFmpeg内存模型 int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt); int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);问题(数据的申请和释放): …

1.1 数据采集总览

正所谓巧妇难为无米之炊&#xff0c;数据采集是数据处理的第一步。 什么是数据采集 数据采集&#xff0c;也称为数据收集&#xff0c;是将原始数据从各种来源获取并存储起来的过程。这个过程是数据分析和数据仓库建设的第一步&#xff0c;涉及到从不同的数据源中提取数据&…

Spring的自动注入(也称为自动装配)

自动注入&#xff08;也称为自动装配&#xff09;是Spring框架中的一个核心概念&#xff0c;它与手动装配相对立&#xff0c;提供了一种更简洁、更灵活的方式来管理Bean之间的依赖关系。 在Spring应用程序中&#xff0c;如果类A依赖于类B&#xff0c;通常需要在类A中定义一个类…

qt 一个可以拖拽的矩形

1.概要 2.代码 2.1 mycotrl.h #ifndef MYCOTRL_H #define MYCOTRL_H#include <QWidget> #include <QMouseEvent>class MyCotrl: public QWidget {Q_OBJECT public://MyCotrl();MyCotrl(QWidget *parent nullptr); protected:void paintEvent(QPaintEvent *even…

MySQL 死锁查询和解决死锁

来了来了来了&#xff01;客户现场又要骂街了&#xff0c;你们这是什么破系统怎么这么慢啊&#xff1f;&#xff01;&#xff1f;&#xff01; 今天遇到了mysql死锁&#xff0c;直接导致服务器CPU被PUA直接GUA了&#xff01; 别的先别管&#xff0c;先看哪里死锁&#xff0c;或…

【MySQL数据库】:MySQL视图特性

视图的概念 视图是一个虚拟表&#xff0c;其内容由查询定义&#xff0c;同真实的表一样&#xff0c;视图包含一系列带有名称的列和行数据。视图中的数据并不会单独存储在数据库中&#xff0c;其数据来自定义视图时查询所引用的表&#xff08;基表&#xff09;&#xff0c;在每…

[保姆级教程]uniapp实现底部导航栏

文章目录 前置准备工作安装HBuilder-X新建uniapp项目教程使用HBuilder-X启动uniapp项目教程 实现底部导航栏package.json中配置导航栏详细配置内容 前置准备工作 安装HBuilder-X 详细步骤可看上文》》 新建uniapp项目教程 详细步骤可看上文》》 使用HBuilder-X启动uniapp项…

vivado、vitis2022安装及其注意事项(省时、省空间)

1、下载 AMD官网-资源与支持-vivado ML开发者工具&#xff0c;或者vitis平台&#xff0c; 下载的时候有个官网推荐web安装&#xff0c;亲测这个耗时非常久&#xff0c;不建议使用&#xff0c;还是直接下载89G的安装包快。 注意&#xff1a;安装vitis平台会默认安装vivado&…

【Deep Learning】Self-Supervised Learning:自监督学习

自监督学习 本文基于清华大学《深度学习》第12节《Beyond Supervised Learning》的内容撰写&#xff0c;既是课堂笔记&#xff0c;亦是作者的一些理解。 在深度学习领域&#xff0c;传统的监督学习(Supervised Learning)的形式是给你输入 x x x和标签 y y y&#xff0c;你需要训…

树莓派4B_OpenCv学习笔记12:OpenCv颜色追踪_画出轨迹

今日继续学习树莓派4B 4G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 版本是4.5.1&#xff1a; 今日尝试使用倒叙的方式来学习OpenCV颜色追踪&#xff0…

Claude 3.5 强势出击:解析最新AI模型的突破与应用

近年来&#xff0c;人工智能领域的发展迅猛&#xff0c;各大科技公司纷纷推出了自家的高级语言模型。在这场技术竞赛中&#xff0c;Anthropic的Claude系列模型凭借其强大的性能和创新的功能脱颖而出。最近&#xff0c;Anthropic发布了Claude 3.5 Sonnet模型&#xff0c;引起了广…

如何设置Excel单元格下拉列表

如何设置Excel单元格下拉列表 在Excel中设置单元格下拉列表可以提高数据输入的准确性和效率。以下是创建下拉列表的步骤&#xff1a; 使用数据验证设置下拉列表&#xff1a; 1. 选择单元格&#xff1a; 选择你想要设置下拉列表的单元格或单元格区域。 2. 打开数据验证&…

高斯算法的原理及其与常规求和方法的区别

高斯算法的原理 高斯算法的原理源于数学家卡尔弗里德里希高斯在他少年时期发现的一种求和方法。当时老师让学生们计算1到100的和&#xff0c;高斯发现了一种快速计算的方法。 高斯注意到&#xff0c;如果将序列的首尾两数相加&#xff0c;结果总是相同的。例如&#xff1a; …

DVWA 靶场 JavaScript 通关解析

前言 DVWA代表Damn Vulnerable Web Application&#xff0c;是一个用于学习和练习Web应用程序漏洞的开源漏洞应用程序。它被设计成一个易于安装和配置的漏洞应用程序&#xff0c;旨在帮助安全专业人员和爱好者了解和熟悉不同类型的Web应用程序漏洞。 DVWA提供了一系列的漏洞场…