原載于Elastic中文社區(qū): https://elasticsearch.cn/article/304
本文是針對(duì)社區(qū)問題question#2484 的分析和總結(jié)阱佛。
問題概述
一個(gè)線上集群惶楼,執(zhí)行的Query DSL都是一樣的狰腌,只是參數(shù)不同您宪。 統(tǒng)計(jì)數(shù)據(jù)顯示98-99%的查詢響應(yīng)速度都很快痕惋,只需要4 -6ms号俐, 但有1%左右的查詢響應(yīng)時(shí)間在100ms - 200ms。 集群硬件配置較高救欧,使用的SSD衰粹,系統(tǒng)可用內(nèi)存遠(yuǎn)高于索引文件大小總和,并且線上已經(jīng)運(yùn)行有一段時(shí)間笆怠,數(shù)據(jù)應(yīng)該已經(jīng)充分預(yù)熱铝耻。
診斷過程及結(jié)論
比較巧的是,問題提出者剛好是我們自家公司的開發(fā)者蹬刷,因此內(nèi)部聯(lián)系溝通了下田篇,為問題的快速診斷提供了不少便利。
首先用公司的監(jiān)控系統(tǒng)排查了一遍集群所有關(guān)鍵數(shù)據(jù)箍铭,未發(fā)現(xiàn)任何可能引起查詢耗時(shí)高的性能瓶頸問題。 因此初步懷疑就是有查詢本身比較慢椎镣。 幸好公司有應(yīng)用埋點(diǎn)系統(tǒng)和日志系統(tǒng)诈火,因此很方便的拿到了應(yīng)用端發(fā)出的一些慢查詢樣例,包括請(qǐng)求體以及耗時(shí)状答。
以下是埋點(diǎn)系統(tǒng)里記錄的一個(gè)耗時(shí)150ms的查詢 (隱去了敏感信息冷守,去掉了非關(guān)鍵部分):
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
}
}
}
拿到查詢后,自己手動(dòng)執(zhí)行了一下惊科,0 hits拍摇,耗時(shí)1ms。 心里明白馆截,命中了Query Cache充活,所以才會(huì)這么快。
于是用clear api清掉Query Cache蜡娶,然后再執(zhí)行幾次混卵,有以下發(fā)現(xiàn):
- 頭兩次查詢耗時(shí)38ms左右。 這是因?yàn)闆]有cache窖张,需要訪問倒排索引幕随,耗時(shí)符合預(yù)期。 之所以兩次同樣耗時(shí)宿接,是因?yàn)樗饕?個(gè)復(fù)制片赘淮,兩次查詢分別分配到主和副片上辕录。
- 接下來兩次查詢耗時(shí)150ms左右。 這里要打一個(gè)大大的問號(hào)梢卸?走诞??
- 之后不管再查詢多少次低剔, 耗時(shí)全部是1ms速梗,因?yàn)橛珠_始命中Cache。
至此襟齿,大致明白姻锁,埋點(diǎn)系統(tǒng)里記錄到的高耗時(shí)查詢,是步驟2的兩次操作猜欺。 什么操作耗時(shí)這么久呢位隶? 根據(jù)經(jīng)驗(yàn),我判斷主要是用于為range filter生成緩存开皿,也就生成生成文檔列表的bitmap涧黄,然后存放到Query Cache里。
這個(gè)集群版本是5.1.1赋荆, 而我記得ES某個(gè)5版本開始笋妥,去掉了對(duì)term filter的cache,理由是term filter速度足夠快窄潭,緩存term filter往往得不償失春宣。 查了官方release notes,證實(shí)這個(gè)改變正好是從5.1.1開始的#21566嫉你,因此上面查詢里的term filters被排除掉月帝,注意力集中到了查詢里唯一的一個(gè)range filter。
單獨(dú)執(zhí)行了一下這個(gè)range filter幽污,match的文檔是千萬數(shù)量級(jí)的嚷辅。 詢問用戶,為何這個(gè)range filter會(huì)hit這么多文檔距误,得知用戶主要就是查詢從當(dāng)前時(shí)間開始至過去1年的數(shù)據(jù)簸搞,類似于做了一個(gè)now-1y TO now
這樣的過濾。至此初步得出結(jié)論准潭,因?yàn)檫@個(gè)range filter匹配的文檔太多了攘乒,在Query Cache里為這個(gè)filter構(gòu)建bitmap耗時(shí)會(huì)有些高,應(yīng)該就是它帶來了那額外的100多個(gè)ms惋鹅。
但是還有一個(gè)待解釋的問題则酝,這種高耗時(shí)查詢比例為何這么高? 再仔細(xì)想想也就明白了:因?yàn)檫@個(gè)集群的搜索并發(fā)量還是有點(diǎn)高,300 -400/s的樣子沽讹,加上時(shí)間字段的精度是秒般卑,所以,在某一秒剛開始的時(shí)候爽雄,頭2次查詢因?yàn)闆]有cache蝠检,耗時(shí)可能在38ms左右,之后會(huì)有2次查詢因?yàn)樾枰彺鎟ange filter挚瘟,耗時(shí)會(huì)增加到150-200ms的樣子叹谁,之后這1秒里剩余的查詢都會(huì)命中cache,全部是幾個(gè)ms乘盖, 直到下一秒開始焰檩, 周而復(fù)始。 因?yàn)槊棵腌姸籍a(chǎn)生2個(gè)這樣需要構(gòu)建緩存的查詢订框,耗時(shí)較高析苫,對(duì)比每秒幾百次的查詢量,換算成百分比就有點(diǎn)高了穿扳。
那么怎么解決這個(gè)問題衩侥? 對(duì)于大量含有從now-xxx TO now
這樣的range查詢,實(shí)際上官方的文檔有對(duì)應(yīng)的加速技巧介紹:tune-for-search-speed.html#_search_rounded_dates 矛物。也就是說茫死,將查詢時(shí)間的上下限r(nóng)ound到整分鐘,或者整小時(shí)履羞,讓range filter可以緩存得更久峦萎,避免出現(xiàn)這種過于頻繁重建cache的情況。
{
"range": {
"my_date": {
"gte": "now-1y/h",
"lte": "now-1y/h"
}
}
}
在原始Query里吧雹,將range filter寫成上述形式,手動(dòng)測(cè)試證實(shí)可行涂身,range filter有效期延長(zhǎng)到1小時(shí)雄卷,從而每個(gè)小時(shí)里,只需要為range filter重建2次Cache蛤售,至此問題解決丁鹉。
總結(jié):
- Cache并非建得越多越好,因?yàn)镃ache的生成和Evict會(huì)帶來額外的開銷悴能。特別是結(jié)果集非常大的filter揣钦,緩存的代價(jià)相對(duì)查詢本身可能非常高。
- ES 5.1.1開始取消了Terms filter Cache漠酿,因?yàn)門erms filter執(zhí)行非撤氚迹快,取消緩存多數(shù)情況下反而可以提高性能炒嘲。
- 大量用到
Now-xxxd To Now
這樣的Range filter時(shí)宇姚,可以借助round date技巧匈庭,提高Cache的有效期,減輕頻繁重建Cache帶來的性能問題浑劳。