zoukankan      html  css  js  c++  java
  • ElasticStack系列之十五 & query cache 引起性能问题思考

    问题描述

      一个线上集群,执行的 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 带来的性能问题。

  • 相关阅读:
    phpExcel中文帮助手册之常用功能指南
    linux下使用远程图形界面
    分支限界法 0-1背包问题-优先队列式
    分支限界法 0-1背包问题-队列式
    回溯法 0-1背包问题
    贪心算法 哈夫曼树编码
    贪心算法 二.活动安排问题
    贪心算法 一.部分背包问题
    动态规划 四.0-1背包问题
    动态规划 三.最大子段和
  • 原文地址:https://www.cnblogs.com/liang1101/p/7658931.html
Copyright © 2011-2022 走看看