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 带来的性能问题。

 

posted @ 2017-10-12 23:31  星火燎原智勇  阅读(891)  评论(0编辑  收藏  举报