问题描述

  一个线上集群,执行的 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. DB2分页查询简单示例

    select * from ( select a.* ,rownumber() over(order by create_time desc) as rowid from ( select * fro ...

  2. Microsoft Visual Studio 2013 的安装及单元测试

    题目:练习教科书第22~25页单元测试练习,要求自行安装Visual Studio开发平台,版本至少在2010以上,要求把程序安装过程和练习过程写到博客上,越详细越好,要图文并茂. 安装过程: 1.下 ...

  3. 20135231 JAVA实验报告三:敏捷开发与XP实践

    ---恢复内容开始--- JAVA实验报告三:敏捷开发与XP实践 20135231 何佳 实验内容 1. XP基础 2. XP核心实践 3. 相关工具 实验要求 1.没有Linux基础的同学建议先学习 ...

  4. 20135234mqy 实验二 Java面向对象程序设计

      北京电子科技学院(BESTI) 实     验    报     告 课程:Java程序设计  班级:1352  姓名:mqy  学号:20135234 成绩:             指导教师: ...

  5. 20172329 2018-2019-2 《Java软件结构与数据结构》实验二报告

    20172329 2018-2019-2 <Java软件结构与数据结构>实验二报告 课程:<Java软件结构与数据结构> 班级: 1723 姓名: 王文彬 学号:2017232 ...

  6. Javascript实现大整数加法

    记得之前面试还被问到过用两个字符串实现两个大整数相加,当时还特别好奇好好的整数相加,为什么要用字符串去执行.哈哈,感觉当时自己还是很无知的,面试官肯定特别的无奈.今天在刷算法的时候,无意中看到了为什么 ...

  7. Ubuntu登录界面添加root用户登录选项

    1.普通用户登录系统并打开终端 配置root密码 $sudo passwd 切换至root用户 $su root 输入密码 修改以下配置文件 $nano /usr/share/lightdm/ligh ...

  8. mysql密码忘记解决方案

    方法:在忘记root密码的时候,可以这样 以windows为例: 1. 关闭正在运行的MySQL服务. 2. 打开DOS窗口,转到mysql\bin目录. 3. 输入mysqld --skip-gra ...

  9. 【CSAPP笔记】7. 汇编语言——过程调用

    一个过程调用包括将数据(以参数和返回值的形式)与控制从代码的一部分传递到另一部分.除此之外,在进入时为过程的局部变量分配空间,在退出的时候释放这些空间.数据传递.局部变量的分配和释放通过操纵程序栈来实 ...

  10. just_sort

    ★实验任务 给定两个序列 a b,序列 a 原先是一个单调递增的正数序列,但是由于某些 原因,使得序列乱序了,并且一些数丢失了(用 0 表示).经过数据恢复后,找 到了正数序列 b ,且序列 a 中 ...