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的优化。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

搜索引擎推广基本概念与方法分享-华媒舍

销量是每个企业及个人在商业领域中追求的目标之一。而引擎霸屏推广就是一种高效的手段,通过该方法可以助你实现销量的狂揽。本文将为你科普引擎霸屏推广的基本概念与方法,帮助你了解如何运用这一有效的推广策略。 一、引擎霸屏推广 引擎霸屏推广指的是在…

【unity笔记】四、Enviro- Sky and Weather插件使用

一、 简介 Enviro内置 RP、URP、HDRP,开箱即用。 动态天空 随附的天空系统经过精心设计,以实现最佳性能和灵活性。使用多种颜色渐变,而不是调整人工数字。为您的项目创建独特且非常逼真的天空非常简单! 灯光 由 Enviro 控制的逼…

Redis大key有什么危害?如何排查和处理?

什么是 bigkey? 简单来说,如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准: String 类型的 value 超过 1MB 复合类型(Li…

WPF 程序 分布式 自动更新 登录 打包

服务器server端 core api 客户端WPF // 检查应用更新 //1、获取最新文件列表 // var files fileService.GetUpgradeFiles(); // 2、文件判断,新增的直接下载;更新的直接下载;删除的直接删除 // 客户端本地需要一个记录…

CentOS7在2024.6.30停止维护后,可替代的Linux操作系统

背景 Linux的发行版本可以大体分为两类,一类是商业公司维护的发行版本,一类是社区组织维护的发行版本,前者以著名的Redhat(RHEL)为代表,后者以Debian为代表。国内占有率最多的却是Centos,这是由…

C++设计模式——Composite组合模式

一,组合模式简介 真实世界中,像企业组织、文档、图形软件界面等案例,它们在结构上都是分层次的。将系统分层次的方式使得统一管理和添加不同子模块变得容易,在软件开发中,组合模式的设计思想和它们类似。 组合模式是…

Navicat和SQLynx产品功能比较二(SQL查询)

数据库管理工具最常用的功能就是SQL的查询,没有之一。本文针对Navicat和SQLynx做了SQL查询相关的性能测试,从测试结果来看,Navicat主要适合开发类的小型数据量需求,SQLynx可以适应大型数据量或小型数据量的需求,用户可…

自制HTML5游戏《开心消消乐》

1. 引言 游戏介绍 《开心消消乐》是一款基于HTML5技术开发的网页游戏,以其简单的操作方式、轻松的游戏体验和高度的互动性,迅速在社交平台上获得了广泛的关注和传播。玩家通过消除相同类型的元素来获得分数,游戏设计巧妙,易于上手…

Ubuntu系统使用快速入门实践(八)—— git 命令使用

Ubuntu系统使用快速入门实践系列文章 下面是Ubuntu系统使用系列文章的总链接,本人发表这个系列的文章链接均收录于此 Ubuntu系统使用快速入门实践系列文章总链接 下面是专栏地址: Ubuntu系统使用快速入门实践系列文章专栏 文章目录 Ubuntu系统使用快速…

锂锗磷硫(LGPS)是代表性硫化物固态电解质产品之一 技术研究不断深入

锂锗磷硫(LGPS)是代表性硫化物固态电解质产品之一 技术研究不断深入 锂锗磷硫,化学式为Li10GeP2S12,英文简称LGPS,是一种代表性硫化物固态电解质,外观为灰白色至深灰色固体粉末状。 全固态锂电池是传统液态…

网上的流量卡真的可以免费领取吗?

网上的流量卡真的可以免费领取吗?当然可以,目前运营商推出的流量卡都是可以免费领取的。 有很多朋友私信给小编,听说流量卡是免费领取的就觉得不太靠谱,其实这种想法是不对的,首先大家要换位思考一下,如果我…

[JS]数据类型

介绍 在计算中一切事物都是数据, 为了提高数据的存储和使用效率, 要对数据进行类型的分类 栈(操作系统): 由操作系统自动分配释放函数的参数值, 局部变量的值等, 其操作方式类似于数据结构中的栈; 基本数据类型存放在栈里面string, number, boolean, undefined, null 堆(操作…

设计模式1-简介

设计模式简介 专栏的目的什么是设计模式设计模式要学什么软件开发原则主流设计模式 学习建议经典面试题 资源 专栏的目的 1.理解松耦合的设计思想 2.掌握面向对象设计原则 3.掌握重构技法改善设计 4.掌握GOF核心设计模式 什么是设计模式 每一个模式描述了一个在我们周围不…

docker 环境部署

1.Redis部署 用docker拉取redis镜像 docker pull redis 用docker查看拉取的镜像版本号,这里查到的是 6.2.6 版本 docker inspect redis 通过wget指令下载对应版本的tar包,下载完成后解压 wget https://download.redis.io/releases/redis-6.2.6.tar.gz …

【2024.6.22】今日科技时事:科技前沿大事件

人不走空 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 目录 🌈个人主页:人不走空 💖系列专栏:算法专题 ⏰诗词歌…

Python抓取高考网图片

Python抓取高考网图片 一、项目介绍二、完整代码一、项目介绍 本次采集的目标是高考网(http://www.gaokao.com/gkpic/)的图片,实现图片自动下载。高考网主页如下图: 爬取的流程包括寻找数据接口,发送请求,解析图片链接,向图片链接发送请求获取数据,最后保存数据。 二…

Android设置页面Activity全屏(隐藏导航栏、状态栏)

3、代码中设置:在setContentView 之前调用 requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 注意: 当有全面屏手机可以显示虚拟…

geojson文件默认已有的style会导致webGL渲染错误处理办法

geojson文件默认已有的style会导致webGL渲染错误处理办法 相关链接: 功能示例(Vue版) | Mars3D三维可视化平台 | 火星科技 代码: export function showDraw(isFlyTo) {removeLayer()graphicLayer new mars3d.layer.GeoJsonLayer({data: {type: &quo…

vue3-openlayers 轨迹回放(历史轨迹),实时轨迹

vue3-openlayers 轨迹回放(历史轨迹),实时轨迹 本篇介绍一下使用vue3-openlayers轨迹回放(历史轨迹),实时轨迹 1 需求 轨迹回放(历史轨迹)实时轨迹 2 分析 可以使用和上一篇相同…

【一步一步了解Java系列】:认识异常类

看到这句话的时候证明:此刻你我都在努力 加油陌生人 个人主页:Gu Gu Study专栏:一步一步了解Java 喜欢的一句话: 常常会回顾努力的自己,所以要为自己的努力留下足迹 喜欢的话可以点个赞谢谢了。 作者:小闭…