2017-1-9 更新:
https://elasticsearch.cn/article/446
why this happened? 攜程的兄弟給出了答案,看來結(jié)論是一致的就是number在構(gòu)造scorer時辙芍,實際上是構(gòu)造一個巨大的bitset并在上面生成一個迭代器系瓢。而keyword 在做鏈表時有跳表查詢顯然找docid要快好多训裆。
根源就在于Lucene 6.0 對于存儲number類型是用k-d tree 造成的田轧。
看來攜程的高手看Lucene還是看的很深推沸,需要努力學(xué)習(xí)涩蜘!
最后貼出作者給出的結(jié)論刮萌,如果一定要用number建索引而又不用range操作的驮配,趕緊升級到5.4 吧:
小結(jié):
在ES5.x里,一定要注意數(shù)值類型是否需要做范圍查詢,看似數(shù)值壮锻,但其實都是做Term或者Terms匹配的琐旁,應(yīng)該定義為keyword字段。
如果RangeQuery的結(jié)果集很大猜绣,并且還需要和其他更加selective的查詢條件做AND的灰殴,應(yīng)該升級到ES5.4+,該版本在底層引入的indexOrDocValuesQuery掰邢,可以極大提升該場景下RangeQuery的查詢速度牺陶。
最近因為一個索引的數(shù)據(jù)量日益暴增,達到8辣之,9億掰伸,因此相應(yīng)的慢查詢也開始顯現(xiàn),針對其中幾種查詢更是慢的離譜怀估。
查看了一下這個慢查詢的搜索條件其實也很簡單狮鸭,而且,簡單到離譜多搀,其中有這么兩個條件查詢歧蕉,這兩個字段的mapping
都是short
類型,在沒有這兩個條件的查詢康铭,延遲是可以接受的惯退,仍然能保持200ms級以內(nèi),而 is_deleted
則去到數(shù)百毫秒从藤,is_warmup
則是暴增到1~3s 不等蒸痹。
這就有點奇怪了,首先docs 數(shù)一樣呛哟,這兩個type
的mapping
都是short,而且值都是只有0匿沛,1扫责,2這樣數(shù)種枚舉值,那為什么這種條件的查詢這么慢呢逃呼?而且還不一樣鳖孤?
當(dāng)時自己這個問題看了大半天是無果, 做了一些假設(shè)性的猜測也就作罷抡笼, 今天好基友“聶風(fēng)”同學(xué)找上門苏揣,說這幾天也遇到同類的問題,也是百思不得其解推姻,突然吊起胃口平匈,又回過頭來思考這個疑難雜癥,想一看究竟。
網(wǎng)上有好一些有共同病患的增炭,可惜還沒有官方回答忍燥,先mark下來說不定我寫完我的猜測官方就給解答了 -_-
Elastic對類似枚舉數(shù)據(jù)的搜索性能優(yōu)化
Why my search slow?
線索一:這兩個term其實和Query的order無關(guān)
我們首先肯定會覺得這兩個term的查詢肯定是結(jié)果集太大了,所以并沒有基于其他更小范圍的filter之后做隙姿,而是并行來做梅垄,并且因為結(jié)果集很大,所以撈的時間非常大输玷;是的队丝,我首先就是這個想法,后來當(dāng)我打開了profile:true看到了結(jié)果欲鹏,表示我的猜測錯了机久!
下面是我打開了profile 的分析結(jié)果:
從上面的分析得出,其實耗時并不是因為它并行走了索引去撈了超大量的數(shù)據(jù)導(dǎo)致貌虾,也不是消耗在打分上吞加,更不是消耗在什么合并運算上,偏偏是消耗在一個
build_scorer
的過程里尽狠。
這里的build_scorer
究竟是什么咚咚呢衔憨?
忘了說,我的這個慢查詢都是塞在 filter里的袄膏,不是不打分的么践图,沒看到Result的score都是0? 那和score 有半毛錢關(guān)系沉馆?
那就先挖出這個scorer的解釋先:
https://www.elastic.co/guide/en/elasticsearch/reference/master/_profiling_queries.html
build_scorer
This parameter shows how long it takes to build a Scorer for the query. A Scorer is the mechanism that iterates over matching documents generates a score per-document (e.g. how well does "foo" match the document?). Note, this records the time required to generate the Scorer object, not actually score the documents. Some queries have faster or slower initialization of the Scorer, depending on optimizations, complexity, etc. This may also showing timing associated with caching, if enabled and/or applicable for the query
留意一下高亮那兩句码党,就是說耗時是耗在了構(gòu)建 一個叫Scorer Object 的東西上,并且這個東西的耗時程度取決于優(yōu)化的程度(這里我理解就是建完index之后有沒有做一些index的優(yōu)化斥黑,比如force merge揖盘, blablabla 。锌奴。兽狭。)
好了,這個Scorer Object
一看就是Lucene的東西鹿蜀,所以現(xiàn)在暫時先不深入箕慧,再挖一下其他有用的東西。
線索二: number 字段的term操作其實都是轉(zhuǎn)換成range查詢
不知道上面的截圖大家留意到一個細(xì)節(jié)沒有茴恰,[0-0] [1-1] ,對颠焦,其實這是一個range查詢
這個線索只是讓我覺得有點點意外,其實本身并無太大的價值往枣,不過也是一個思路吧伐庭,就是以后什么樣的值采用number來保存粉渠。換句話說,就是當(dāng)對number的操作很慢的時候似忧,首先得想如何提高range的性能渣叛。
關(guān)于這個課題我最后再講。
線索三:沒有線索了盯捌,老老實實看代碼吧淳衙!
首先看看這個時間是怎么來的:
從上面的說明結(jié)合得出,時間的跨度就是來生成這個scorer上饺著,Weight
類型是一個基類箫攀,有多個實現(xiàn),由于我還未曾系統(tǒng)的去讀完整個Lucene的源碼幼衰,所以這里我也只能猜了靴跛,大家都知道,Lucene 把所有的查詢會轉(zhuǎn)換成一顆格式化的包含各種類型查詢的樹渡嚣,而這個Weight
樹我理解就是用來做合并和打分用的梢睛。
那由于這個是一個term查詢,那我們就去看看TermWeight
的scorer
方法
從代碼上看识椰,也就是說绝葡,當(dāng)做某個field的檢索的時候,其實Lucene會初始化一個這個field 的
termEnum
這種東西腹鹉,并且會調(diào)用postings 獲取PostingsEnum
那這個
PostingsEnum
又是什么鬼藏畅,我大概了解就是一種結(jié)果docs 的迭代器的抽象,已被到時在Lucene的結(jié)果樹里可以快速做合并功咒,運算之用那么再對比一下愉阎,PostingsEnum的 普通term的倒排和number的倒排的實現(xiàn)類應(yīng)該是完全不同的邏輯實現(xiàn)的
好,那我猜測構(gòu)造慢因該就是做這個postings 慢了力奋,因為它有各自的實現(xiàn)榜旦,如果涉及到docs 的指針的迭代,那么大家都知道景殷,Lucene的倒排指向的docs 映射是用BitSet去實現(xiàn)的章办,那么到現(xiàn)在為止就可以猜測,要初始化一個類似LongBitSet這種東西滨彻,跟我這個number的本身的稀疏程度是否有關(guān)?
我的一種猜測
至此挪蹭,我的一個猜測就是如果number類型的條件結(jié)果集很大的時候亭饵,要構(gòu)造一個某種BitSet的scorer 其實是挺費勁的(像官方文檔說說,還和其他很多因素相關(guān))但是如何解釋同樣是number梁厉,為什么 is_warmup
要比is_deleted
還要再慢幾倍呢辜羊?我猜測是不同docs的這個值的稀疏程度有關(guān)踏兜,當(dāng)然要回答這個還要繼續(xù)深入Lucene的代碼,我這里就不再繼續(xù)drill down了
結(jié)論
好了八秃,其實要解決這個問題很簡單碱妆,如果是枚舉類型的數(shù)字的話,mapping 用keyword 就好了昔驱,其實回顧官方文檔的時候疹尾,官方文檔也清楚列明了的,如果你是不做range操作的可枚舉出的數(shù)字的話,比如map 的keyset骤肛,最好還是用keyword纳本。
后話
恰巧看到一篇blog有提到官方其實一直在努力加快 number的range搜索,具體大家可以讀讀, 貌似這個優(yōu)化增加到 ES5.4 以后的版本了
https://www.elastic.co/blog/better-query-planning-for-range-queries-in-elasticsearch
接下來看有機會debug一下Lucene的部分代碼腋颠,希望大家提出自己的看法繁成,各抒己見,就拍哪里錯了誤導(dǎo)了大家淑玫;好了巾腕,洗洗睡了!
參考文獻:
https://www.compose.com/articles/how-scoring-works-in-elasticsearch/
https://qbox.io/blog/optimizing-search-results-in-elasticsearch-with-scoring-and-boosting
https://www.elastic.co/guide/cn/elasticsearch/guide/current/scoring-theory.html
https://toutiao.io/posts/4mwfeo/preview
http://www.scienjus.com/elasticsearch-function-score-query/