ES 分頁(yè)查詢緩慢或全量查詢慢問(wèn)題
背景
前段時(shí)間因?yàn)?strong>數(shù)據(jù)量越來(lái)越大,導(dǎo)致數(shù)據(jù)庫(kù)的查詢壓力越來(lái)越大。所以決定將數(shù)據(jù)刷入到ES中進(jìn)行查詢,以提高查詢速度。想法是好的,測(cè)試環(huán)境也沒(méi)有仔細(xì)測(cè)。心想ES查詢總不會(huì)慢了。再慢能慢到哪里去。放心大膽的上了生產(chǎn)環(huán)境,結(jié)果給我好好的上了一課。
因?yàn)橛腥坎樵償?shù)據(jù)的業(yè)務(wù),而在我們自己封裝的包里只有封裝好的分頁(yè)查詢方法。按寫(xiě)SQL的思維形式來(lái)想,既然封裝好的ES的基礎(chǔ)包中沒(méi)有全量查詢數(shù)據(jù)的方式,那就分頁(yè)查詢唄,直到全部查詢完畢。應(yīng)該也不會(huì)太慢。再慢能慢過(guò)數(shù)據(jù)庫(kù)嗎?
一旦喜歡上誰(shuí)就別無(wú)所求,只要每天能見(jiàn)到他就已經(jīng)覺(jué)得很慶幸,一輩子很短,如白駒過(guò)隙,轉(zhuǎn)瞬即逝。可這種心情很長(zhǎng),如高山大川,綿延不絕。 ——《武林外傳》
發(fā)現(xiàn)問(wèn)題
就在上線之后的第二天。突然有人反饋說(shuō)用到了全量查詢的業(yè)務(wù)特別的慢。創(chuàng)建好的任務(wù)要兩三個(gè)小時(shí)以后才執(zhí)行。而在此之前,這個(gè)任務(wù)最多也就是5分鐘也開(kāi)始執(zhí)行了,我先是心頭一緊,心想完了,難道是做了個(gè)負(fù)優(yōu)化。難道查詢ES 真沒(méi)有數(shù)據(jù)庫(kù)快嗎?是不是網(wǎng)絡(luò)問(wèn)題?是不是查詢語(yǔ)句我寫(xiě)的有問(wèn)題影響效率了?
定位問(wèn)題
帶著關(guān)鍵字去生產(chǎn)上查了日志。先查到的是一個(gè)查詢語(yǔ)句的日志??吹揭院蟪泽@了。語(yǔ)句之大,五六屏放不下。吃驚之后就想,會(huì)不會(huì)是這個(gè)查詢條件放的太多了?導(dǎo)致查詢速率瞬間下降。一方面反思查詢的時(shí)候不應(yīng)該這么查詢,代碼不能這么寫(xiě)。一方面將查詢語(yǔ)句拿出來(lái),放到ES Head 里面查一下,看看到底有多慢。
查了以后,結(jié)果還是很出乎我的意料的。盡管條件賊老長(zhǎng),可是ES不愧是ES,還是匹配的很快。200ms左右。這結(jié)果讓我瞬間麻爪,不知該如何繼續(xù)了??墒侨罩纠锩娲蛴〉拇_實(shí)是兩三分鐘之后才返回了查詢結(jié)果。
突然沒(méi)了方向。那就先查查ES如何來(lái)實(shí)現(xiàn)分頁(yè)或者全量數(shù)據(jù)的查詢。通過(guò) 這篇文章 查到了三種方式:
- from size 查詢方式
- scroll 深分頁(yè)查詢
- searchAfter 深分頁(yè)查詢
三種方式簡(jiǎn)單來(lái)說(shuō)一下區(qū)別:
分頁(yè)方式 | 特性 |
---|---|
from size 查詢 | 適合小數(shù)據(jù)量的情況(10000-50000的數(shù)據(jù)左右)越往后時(shí)間越長(zhǎng),性能越差 |
scroll 查詢 | 能解決深分頁(yè)問(wèn)題,但是生成了數(shù)據(jù)快照,比較耗費(fèi)資源。由于快照,不支持增量數(shù)據(jù)查詢,不支持跳頁(yè) |
searchAfter查詢 | 能解決深分頁(yè)問(wèn)題,且能實(shí)時(shí)反應(yīng)增刪的數(shù)據(jù),不支持跳頁(yè),數(shù)據(jù)需要有唯一的標(biāo)識(shí) |
你是藏在云層里的月亮??,也是我窮極一生尋找的寶藏
三種方式的區(qū)別大致如此,詳細(xì)一些,上面的文章 或者 這篇文章 寫(xiě)的還是蠻詳細(xì)的。
看到這里已經(jīng)看到了實(shí)現(xiàn)方式以及區(qū)別,接下來(lái)就是看包里封裝的分頁(yè)查詢邏輯是如何實(shí)現(xiàn)的。
從圖中可以看到,參數(shù)中的from參數(shù) 并沒(méi)有使用,查詢的時(shí)候只是設(shè)置了 offset參數(shù)。那猜測(cè)一下,應(yīng)該是用了 第一種 from size 的查詢方式 不過(guò)默認(rèn)from 應(yīng)該設(shè)置了0。這樣看的話,選擇了性能最差的一種,確實(shí)是慢的應(yīng)該了。繼續(xù)往下看,
如果有searchAfter 那么,會(huì)使用searchAfter查詢。否則使用了Scroll查詢。看來(lái)使用from size 方式 的同時(shí)就已經(jīng)在為scroll方式打基礎(chǔ)了。那么我們傳入的from參數(shù) 是什么時(shí)候使用的呢?
繼續(xù)讀代碼,發(fā)現(xiàn)邏輯是,如果第一次 使用 from size 方式 查詢的數(shù)據(jù)滿足偏移量,那么就直接返回,否則使用scroll方式繼續(xù)查詢,直到滿足條件為止。
也許是不懂想要的愛(ài),才會(huì)一再受到傷害。也許是背負(fù)很多次失敗,才能妥善的窺見(jiàn)未來(lái)。那些被心酸辜負(fù)劫走的小幸福,希望他還能認(rèn)識(shí)路,早點(diǎn)回來(lái)
破案了
通過(guò)上面的分析來(lái)看,雖然包里封裝的確實(shí)是,能通過(guò)分頁(yè)查詢到我們需要的數(shù)據(jù),但是再來(lái)看一下我調(diào)用的地方:
為了查詢到全部的數(shù)據(jù),我是每次查詢5000條,然后通過(guò)第一次返回的總數(shù),來(lái)計(jì)算總共要查詢多少次,接著就是循環(huán)修改 頁(yè)碼并且調(diào)用包封裝好的方法。結(jié)果可想而知,慢的原因就出來(lái)了。
- 當(dāng)我查詢第二頁(yè)的時(shí)候,由于第一頁(yè)的數(shù)據(jù)已經(jīng)不滿足了,也就是用from size 方式查詢的數(shù)據(jù)不夠了,那么就會(huì)使用scroll的方式來(lái)查詢第二頁(yè)。查到之后滿足返回?cái)?shù)據(jù)
- 當(dāng)我查詢第三頁(yè)的時(shí)候,還是第一頁(yè)的數(shù)據(jù)無(wú)法滿足,那么會(huì)使用scroll的方式來(lái)查詢第二頁(yè),第二頁(yè)不是想要的,也丟棄了,則用scroll的形式查詢第三頁(yè)。
- 當(dāng)我查詢第四頁(yè)的時(shí)候 …
所以時(shí)間就是這么變長(zhǎng)的。假如說(shuō)我查詢第10頁(yè)的數(shù)據(jù),那么前9次的查詢都是無(wú)效的,但是確實(shí)真實(shí)的查詢了。而且越到后面無(wú)效查詢就越多。如果我分頁(yè)分了幾萬(wàn)次,想象一下,那得浪費(fèi)多少次查詢。根本就是指數(shù)級(jí)的增長(zhǎng)浪費(fèi)。這么來(lái)看ES沒(méi)有掛掉已經(jīng)是萬(wàn)幸了。
你像風(fēng)來(lái)了又走,我心滿了又空?!栋肷墶?/p>
解決辦法
既然找到了問(wèn)題所在,那就是打開(kāi)的方式不對(duì)了。所以不能直接使用這樣的分頁(yè)方法。既然想獲取全量的數(shù)據(jù),那就要么直接使用scroll的方式查詢,要么直接使用searchAfter的形式查詢。經(jīng)過(guò)上面的對(duì)比,我決定使用searchAfter的方式來(lái)實(shí)現(xiàn)全量數(shù)據(jù)查詢。因?yàn)檫@個(gè)看起來(lái)性能更好一些。
當(dāng)使用searchAfter的時(shí)候,就不需要傳遞from屬性了,只需要傳入偏移量。這樣當(dāng)達(dá)到偏移量的時(shí)候,返回對(duì)應(yīng)偏移量的ID
if (offset == curHit) {
searchAfterStr = myGson.toJson(hit.getSortValues());
result.setSearchAfter(searchAfterStr);
break;
}
使用searchAftere需要注意的點(diǎn):
- 查詢的數(shù)據(jù)要有唯一的主鍵,如果沒(méi)有業(yè)務(wù)主鍵,用ES自己生成的主鍵也可以。
- 查詢的時(shí)候要指定排序規(guī)則
- 不支持跳頁(yè)查詢
- 查詢要返回最后一條記錄的排序字段的值
- 下次查詢的searchAfter 要帶上上次查到的最后一條記錄的排序字段的值
示例:(示例并非是我的代碼,是從 此文章 復(fù)制而來(lái))
TermQueryBuilder queryBuilder = QueryBuilders.termQuery("age", 24);
Object[] objects= new Object[]{"14"};
//第二次請(qǐng)求,攜帶sort字段的值進(jìn)行查詢。
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(queryBuilder).sort("id",SortOrder.ASC).searchAfter(objects);
searchSourceBuilder.from(0).size(3);
searchRequest.source(searchSourceBuilder);
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
寫(xiě)在最后
遇到生產(chǎn)問(wèn)題還是要一步一步的排查。其實(shí)最難的是在定位問(wèn)題,找到問(wèn)題所在,問(wèn)題也就自然有了解決的辦法。 最后一句話:一般離奇的問(wèn)題,都是由于小的問(wèn)題導(dǎo)致的
你知道什么東西最燦爛嗎?是你的笑容。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-744099.html
參考文章文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-744099.html
- ES(elasticsearch) - 三種姿勢(shì)進(jìn)行分頁(yè)查詢
- java實(shí)現(xiàn)es的search after查詢(三種方式詳解)
- ElasticSearch分頁(yè)search_after和scroll的區(qū)別以及用法
- elasticsearch restHighLevelClient 游標(biāo)查詢?nèi)繑?shù)據(jù)示例
- Elasticsearch Search Scroll API 查詢?nèi)繑?shù)據(jù)
到了這里,關(guān)于ElasticSearch分頁(yè)查詢緩慢問(wèn)題記錄的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!