问题描述

  一个线上集群,执行的 Query DSL 都是一样的,只是参数不同。统计数据显示 98% ~ 99% 的查询相应速度都很快,只需要 4 ~ 6ms,但是有 1% 左右的查询响应时间在 100ms ~ 200ms 之间。集群硬件配置较高,使用的是 SSD 硬盘,系统可用内存远高于索引所使用内存总和的 2 倍,并且线上已经运行有一段时间了,数据也不存在是否已经预热的问题。

诊断过程

  首先,通过监控系统排出集群所有关键数据,未发现任何可能引起查询耗时高的性能瓶颈问题。因此初步怀疑就是有查询本身比较慢的原因。从日志系统里拿到记录的一个耗时 150ms 的查询(这里只把关键内容粘贴出来,隐掉了非关键部分):

POST /xxxindex/xxxdb/_search?routing=Mxxxxxxx
{
"from": 0,
"size": 100,
"query": {
"bool": {
"filter": [
{
"bool": {
"must": [
{
"bool": {
"must": [
{
"bool": {
"should": [
{
"match_phrase": {
"ord_orders_uid": {
"query": "Mxxxxxxx",
"slop": 0,
"boost": 1
}
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
},
{
"range": {
"ord_orders_orderdate": {
"from": "1405032032",
"to": "1504014193",
"include_lower": true,
"include_upper": true,
"boost": 1
}
}
},
{
"term": {
"ord_orders_ispackageorder": {
"value": 0,
"boost": 1
}
}
},
{
"bool": {
"must_not": [
{
"exists": {
"field": "ord_hideorder_orderid",
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
],
"disable_coord": false,
"adjust_pure_negative": true,
"boost": 1
}
}
}

  拿到查询后,自己手动执行了一下,0hits 共耗时 1ms,应该是命中了 query cache 所以才这么快。

  于是使用 clear api 清掉了 query cache,然后再执行几次,有以下发现归纳如下:

  1. 前两次查询耗时 36ms 左右,这是因为没有 cache 需要访问倒排索引,耗时是符合预期的。之所以两次同样耗时,是因为索引有一个副本,两次查询分别落在了主分片和副分片上导致。

  2. 接下来两次查询耗时 150ms 左右,这里此时是一头雾水留作思考?

  3. 之后不管再怎么查询,耗时全部都是在 1~5ms,这是因为又开始命中 cache 了。

  至此,大致明白日志中记录的高耗时是 步骤2 产生的。那么到底是什么操作会导致耗时这么久呢?根据以往的经验,我判断主要是用于为 range filter 生成缓存,也就是生成文档列表的 bitmap,然后存放到 query cache 中。

  我使用的是 ElasticSearch5.5.1 版本,而在 ElasticSearch5.1.1 版本开始,去掉了对 term filter 的 cache,理由是 term filter 已经足够快了,缓存 term filter 往往得不尝试反而会白白浪费掉内存空间。那么我就将注意力集中到了查询里唯一的 range filter 上。

  单独执行了一下这个 range filter,match 到的文档是千万量级的,为何这个 range filter 会 hit 到这么多的文档,通过了解得知,用户主要就是查询从当前时间开始至过去 1 年的数据,类似于做一个 [now - 1y TO now] 这样的过滤。至此初步得出结论,因为这个 range filter 匹配到的文档树太多了,在 query cache 里为这个 filter 构建 bitmap 耗时会有些高,应该就是它带来了那额外的 100 多毫秒。

  但是还有一个待解释的问题,这种高耗时查询比例为何这么高?

  再仔细想想也是能够想明白的:

    因为这个集群的搜索并发量还是挺高的,300 ~ 400/s 的样子,加上时间字段的精度是秒,所以,在某一秒刚开始的时候,前两次查询因为没有 cache,耗时可能在 36ms 左右,之后会有 2 次查询因为需要缓冲 range filter,耗时会增加到 150 ~ 200ms 的样子,之后这 1s 里剩余的查询都会命中 cache,全部是 几 ms,直到下一秒开始,周而复始。因为每秒钟都会产生 2 个这样需要构建缓存的查询,耗时较高,对比每秒几百词的查询量,换算成百分比就有点高了。

问题修复

  对于大量含有 [now - xxx TO now] 这样的 range 查询,实际上官方有对应的加速技巧介绍:Search rounded dates 也就是说,将查询时间的上下限 round 到整分钟 或者 整小时,让 range filter 可以换成的更久,避免出现这种过于频繁重建 cache 的情况。

{
"range": {
"my_date": {
"gte": "now-1y/h",
"lte": "now-1y/h"
}
}
}

  在原始 query 里,将 range filter 写成以上形式,手动测试验证是可行的。range filter 有效期延长到 1小时,从而每个小时里,只需要为 range filter 重建 2次 cache,至此问题得以解决。

总结

  1. cache 并非建的越多越好,因为 cache 的生成 和 销毁 会带来额外的开销,特别是结果集非常大的 filter,缓存的代价相对查询本身可能非常高。

  2. ElasticSearch5.1.1 开始取消了 terms filter cache,因为 terms filter 执行非常快,取消缓存多数情况下反而可以提高性能。

  3. 大量用到 [now - xxx TO now] 这样的 range filter 的时候,可以借助 round date 技巧,提高 cache 的有效期,减轻频繁重建 cache 带来的性能问题。

ElasticStack系列之十五 & query cache 引起性能问题思考的更多相关文章

  1. webpack4 系列教程(十五):开发模式与webpack-dev-server

    作者按:因为教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步<webpack4 系列教程(十五):开发模式与 webpack-dev-server>原文地址.更欢迎来我的 ...

  2. ElasticStack系列之十六 & ElasticSearch5.x index/create 和 update 源码分析

    开篇 在ElasticSearch 系列十四中提到的问题即 ElasticStack系列之十四 & ElasticSearch5.x bulk update 中重复 id 性能骤降,继续这个问 ...

  3. Java 设计模式系列(十五)观察者模式(Observer)

    Java 设计模式系列(十五)观察者模式(Observer) Java 设计模式系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Java ...

  4. Java 设计模式系列(十五)迭代器模式(Iterator)

    Java 设计模式系列(十五)迭代器模式(Iterator) 迭代器模式又叫游标(Cursor)模式,是对象的行为模式.迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象(interna ...

  5. Unity3D脚本中文系列教程(十五)

    http://dong2008hong.blog.163.com/blog/static/4696882720140322449780/ Unity3D脚本中文系列教程(十四) ◆ LightRend ...

  6. SQL注入之Sqli-labs系列第二十五关(过滤 OR & AND)和第二十五A关(过滤逻辑运算符注释符)

    开始挑战第二十五关(Trick with OR & AND) 第二十五关A(Trick with comments) 0x1先查看源码 (1)这里的or和and采用了i正则匹配,大小写都无法绕 ...

  7. SQL注入之Sqli-labs系列第十五关和第十六关(基于POST的时间盲注)

    开始挑战第十五关(Blind- Boolian Based- String)和 第十六关(Blind- Time Based- Double quotes- String) 访问地址,输入报错语句 ' ...

  8. Dubbo学习系列之十五(Seata分布式事务方案TCC模式)

    上篇的续集. 工具: Idea201902/JDK11/Gradle5.6.2/Mysql8.0.11/Lombok0.27/Postman7.5.0/SpringBoot2.1.9/Nacos1.1 ...

  9. ABP框架系列之十五:(Caching-缓存)

    Introduction ASP.NET Boilerplate provides an abstraction for caching. It internally uses this cache ...

随机推荐

  1. C++ 类 复制构造函数 The Copy Constructor

    一.复制构造函数的定义 复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性.复制构造函数创建一个新的对象,作为另一个对象的拷贝.复制构造函数只含有一个形参,而且其形参为本类对象的引用.复制构 ...

  2. 王者荣耀交流协会final发布-第一次scrum立会

    1.例会照片 成员王超,高远博,冉华,王磊,王玉玲,任思佳,袁玥全部到齐 master:袁玥 2.时间跨度 2017年12月1日 17:00 — 17:31,总计31分钟 3.地点 一食堂二楼沙发座椅 ...

  3. JSON toBean Timestamp To Date 时间戳转日期

    时间戳格式的时间从json转为date时 配置: import java.util.Date; import net.sf.ezmorph.object.AbstractObjectMorpher; ...

  4. Bing词典vs有道词典比对测试报告——功能篇之核心功能

    必应词典vs有道词典 核心功能对比 从应用的UI布局来看,这两款软件的功能如下: 相同 不同 必应词典 词典.例句.翻译 百科 有道词典 词典.例句.翻译 应用 就词典类软件来说,词典是最核心的功能. ...

  5. Chapter 5 软件工程中的形式化方法

    从广义上讲,形式化方法是指将离散数学的方法用于解决软件工程领域的问题,主要包括建立精确的数学模型以及对模型的分析活动.狭义的讲,形式化方法是运用形式化语言,进行形式化的规格描述.模型推理和验证的方法. ...

  6. KEIL C51 printf格式化输出特殊用法

    作者:dragoniye   发布:2014-02-15 12:44   分类:硬件     抢沙发   /*******************************************KEI ...

  7. paperOne基于java web的简易四则运算出题网站

    项目成员:张金生     张政 需求概要 1.运算数均为正整数 2.包含的运算符有+,-,*,/ 3.除法运算结果为整除运算 4.批量生成题目并判题 核心功能分析 1.题目生成——java后端 题目生 ...

  8. 关于.net服务启动注册到zookeeper,但是注册节点20分钟自动消失解决办法

        ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,作用简单描述就是相当于一个中介,服务提供者将服务注册到zk,服务调用者直接从zk获取,zk的作用就是协调     最近碰到公 ...

  9. 关于JEE web项目 Servlet中 “/” 的解释 ;

    1.关于"/" 可以代表web应用的根目录,也可以代表站点的根目录: 1>如果交给浏览器解析,则代表web站点的根目录,如果交给web服务器解析则代表项目的根目录: 2> ...

  10. 使用Ubuntu编译Linux内核

    1.下载内核并解压到 /usr/src 目录下 在终端执行以下命令即可下载 4.16.14版本(目前最新的稳定版)的内核到当前shell打开的目录下 wget https://cdn.kernel.o ...