Elasticsearch搜索調優

最近把搜索後端從AWS cloudsearch遷到了AWS ES和自建ES集羣。測試發現search latency高於以前的benchmark,可見模擬數據遠不如真實數據來的實在。此次在產線的backup ES上直接進行測試和優化,經過本文記錄search調優的主要過程。node

問題1:發現AWS ES shard級別的search latency是很是小的,符合指望,可是最終的查詢耗時卻很是大(ES response的took), 總體的耗時比預期要高出200ms~300ms。後端

troubleshooting過程:開始明顯看出問題在coordinator node收集數據排序及fetch階段。開始懷疑是由於AWS ES沒有dedicated coordinator節點,data node的資源不足致使這部分耗時較多,後來給全部data node進行來比較大的升級,排除了CPU,MEM, search thread_pool等瓶頸,而且經過cloud watch排除了EBS IOPS配額不夠的可能,可是,發現search latency並無減小。而後就懷疑是network的延時, 就把集羣從3個AV調整到1個AV,發現問題依舊。無奈,聯繫了AWS的support,AWS ES team拿咱們的數據和query語句作了benchmark,發現沒有某方面的資源瓶頸。這個開始讓咱們很疑惑,由於在自建ES集羣上search latency明顯小於AWS ES,兩個集羣的版本,規格,數據量都差很少。後來AWS回覆說是他們那邊的架構問題,比之自建集羣,AWS ES爲了適應公有云上的security, loadbalance要求,在整個請求鏈路上加了一些組件,致使了總體延時的增長。緩存

肯定方案:限於ES cluster不受控,咱們只能從自身的數據存儲和查詢語句上去優化。session

存儲優化:架構

1. index sort。咱們的查詢結果返回都是按時間(created_time)排序的,因此存儲的時候即按created_time進行有序存儲,方便單segment內的查詢提早中斷查詢,提高查詢效率。測試

2. segment merge。索引是按季度存儲的,把2019年以前的索引進行了force merge,進行段合併,2019年以前的索引肯定都是隻讀的。fetch

3. 索引優化。合併了一些小索引,2016,2017年的數據量比較少,把這兩年的索引進行合併,減小總shard數。經過建立原索引的別名指向新索引,保證search和index的邏輯不用改動。優化

查詢優化。ui

先經過profile API定位耗時的子查詢語句。spa

1. 合併查詢字段。一個比較耗時子查詢查詢以下,一般session_id的list size>100,receiver_id和sender_id也會匹配到n多條記錄。

{
    "minimum_should_match": "1"
    "should": [
        {
            "terms": {
                "session_id": [
                    "ab",
                    "cd"
                ],
                "boost": 1
            }
        },
        {
            "term": {
                "receiver_id": {
                    "value": "efg",
                    "boost": 1
                }
            }
        },
        {
            "term": {
                "sender_id": {
                    "value": "hij",
                    "boost": 1
                }
            }
        }
    ]
}

新開一個字段session_receiver_sender_id,經過copy_to把每條記錄的session_id,receiver_id, sender_id都放到這個字段上。把query語句改成

{
    "terms": {
        "session_receiver_sender_id": [
            "ab",
            "cd",
            "efg",
            "hij"
        ],
        "boost": 1
    }
}

不過,測試結論顯示,合併以後query耗時並無明顯縮短,感受改動意義不大。推測多是咱們的BoolQuery字段並很少(就3個),可是terms的size不少(100以上),由於不論是多個字段每一個對應一個termQuery,仍是一個terms query, 都是轉成BoolQuery,最終都是多個termQuery作or。

 

2. 優化date range查詢。另一個比較耗時的查詢是date range。Lucene會rewrite成一個DocValuesFieldExistsQuery。

"filter": [
    {
        "range": {
            "created_time": {
                "from": 1560993441118,
                "to": null,
                "include_lower": true,
                "include_upper": true,
                "boost": 1
            }
        }
    },
    ...
]

這裏匹配到的docId的確很是多,date range結果在構造docIdset與別的子查詢語句作conjunction耗時較大。

採用的一個解決方案是儘可能對這個子查詢進行緩存,把這個date range查詢拆成兩段,分爲3個月前到昨天,昨天到今天兩段,通常昨天的數據再也不變化,在沒有觸發segment merge的狀況下3個月前到昨天到查詢結果應該能緩存較長時間。

"constant_score": {
  "filter": {
    "bool": {
      "should": [
        {
          "range": {
            "created_time": {
              "gte": "now-3M/d",
              "lte": "now-1d/d"
            }
          }
        },
        {
          "range": {
            "created_time": {
              "gte": "now-1d/d",
              "lte": "now/d"
            }
          }
        }
      ]
    }
  }
}

相應的,在用戶可接受的前提下,調大索引的refresh_interval。

問題2: 在自建ES集羣上,發現某個索引500ms以上的搜索耗時佔比較多。

這個索引每日大概30w次查詢,落在100ms之內的查詢超過90%,可是依舊有1%的查詢落在500ms以上。發現一樣的query語句模版,但若是某些子查詢條件匹配到的數據比較多,查詢會變對特別慢。

troubleshooting過程:一樣是經過profile參數分析比較耗時的查詢子句。發現一個PointInSetQuery很是耗時,這個子查詢是對一個名爲user_type的Integer字段作terms查詢,子查詢內部又耗時在build_score階段。

經過查找lucene的代碼和相關文章,發現lucene把numeric類型的字段索引成BKD-tree,內部的docId是無序的,與其餘查詢結果作交集前構造Bitset比較耗時,從而把Integer類型改爲keyword,把這個查詢轉成TermQuery,這樣哪怕命中的數據不少,在build_score的時候由於倒排鏈的docId有序性,利用skiplist,能夠更快速的構建一個Bitset。在把這個字段改爲keyword後,50th的查詢耗時並無多大差別,可是90th、99th的search latency明顯小於以前。

 

另外一個優化,這個索引裏的每條數據都是一個非空的accout_id字段,accout_id在query語句裏會用於terms查詢。遂把這個accout_id字段做爲routing進行存儲。同時能夠對查詢語句進行修改:

#原query
"filter": [
    {
        "terms": {
            "account_id": [
                "abc123"
            ],
            "boost": 1
        }
    }
...
]
#改成
"filter": [
    {
        "terms": {
            "_routing": [
                "abc123"
            ]
        }
    }
...
]

查詢改成_routing以後,發現總體的search latency大幅下降。

通過這兩次改動,針對這個索引的search latency基本知足需求。

另外,還有一個小改動,經過preload docvalue, 能夠減小首次查詢的耗時。

相關文章
相關標籤/搜索