Apache Lucene中的并发查询执行
Apache Lucene是一個出色的并發(fā)純Java搜索引擎,如果您愿意,它可以輕松地使服務(wù)器上的可用CPU或IO資源飽和。 “典型” Lucene應(yīng)用程序的并發(fā)模型在搜索時每個查詢一個線程,但是您知道Lucene還可以使用多個線程同時執(zhí)行一個查詢以大大減少最慢查詢的時間嗎?
Lucene的IndexSearcher類負(fù)責(zé)執(zhí)行傳入查詢以從索引中查找最匹配的匹配項,它接受一個可選
施工期間執(zhí)行器 (例如線程池)。 如果您通過Executor并且CPU足夠閑置(即,服務(wù)器遠(yuǎn)低于其紅線QPS吞吐能力),Lucene將使用多個并發(fā)線程來查找每個查詢的總點擊率最高。
它是如何做到的? Lucene索引是分段的 ,這使搜索成為一個棘手的并行問題:每個查詢都必須訪問索引中的所有細(xì)分,并收集其具有全球競爭力的點擊量。 當(dāng)查詢?yōu)閱尉€程查詢時,因為您沒有將Executor傳遞給IndexSearcher ,所以一個查詢線程必須按順序訪問所有段。 如果索引很大,并且您的查詢成本很高,那么這些查詢自然會需要較高的CPU成本和掛鐘時間才能找到熱門廣告。 即使您在遠(yuǎn)遠(yuǎn)低于其紅線QPS(吞吐量)容量的情況下運行服務(wù)器,這也會導(dǎo)致高長桿(P90 +)查詢延遲。
相反,當(dāng)您將Executor傳遞給IndexSearcher ,索引中的段首先被IndexSearcher分組為單個線程工作單元,稱為
螺紋片 。 默認(rèn)情況下 ,大段屬于它們自己的線程片,最多5個較小的段(最多250K總文檔)將合并為一個線程片,因為它們可以快速地按單個線程順序搜索。 通過子類化IndexSearcher并覆蓋其受保護(hù)的slices方法,可以輕松地自定義將段合并為線程片的方式。 只要服務(wù)器閑置到足以在一個查詢上花費多個CPU內(nèi)核,并且該查詢的每個線程片上都有一個線程,那么每個并發(fā)的查詢就會同時執(zhí)行。
這個強(qiáng)大的功能最初是由Jean-Fran?oisHalleux于16年前提出的 ,然后由Doug Cutting自己 (您好,Doug!)提出,并于大約9年前最終重構(gòu)為IndexSearcher ,此后進(jìn)行了許多迭代的改進(jìn),許多現(xiàn)在都在不斷發(fā)展。感謝Atri Sharma ,最近添加了新的Lucene / Solr提交者 。 這就是熱情的開源軟件開發(fā)的分布式力量!
并發(fā)查詢執(zhí)行是Lucene中很少有人知道的sleeper功能,因為它尚未在基于Lucene構(gòu)建的兩個流行的分布式搜索應(yīng)用程序Elasticsearch和Solr中公開。 他們的并發(fā)模型是跨索引分片(通常在不同的服務(wù)器上)針對單個查詢的并發(fā)搜索,但是在每個分片內(nèi)使用單線程搜索。
這意味著需要許多并發(fā)的獨立查詢才能使集群范圍的CPU或IO資源飽和。 直到群集至少看到最低最低QPS,才能使用全部硬件資源。 對于經(jīng)常看到高查詢率的用例,此限制是可以接受的。 但是,如果Elasticsearch或Solr使用此功能,則具有較大索引和較低查詢率的其他常見用例將從單個群集節(jié)點內(nèi)的并發(fā)查詢執(zhí)行中受益匪淺。
摩爾定律在現(xiàn)實世界中的影響已經(jīng)發(fā)生了變化:現(xiàn)代服務(wù)器級計算機(jī)是用驚人且Swift增長的并發(fā)硬件構(gòu)建的,不僅在其CPU中,我們現(xiàn)在在最新的c5.24xlarge AWS EC2實例中還可以看到96個內(nèi)核,圖形處理單元(GPU),內(nèi)存總線,DIMM和固態(tài)磁盤(SSD),實際上是底層的大型并發(fā)RAID 0陣列。 最近的趨勢是CPU和GPU獲得更多的并發(fā)(內(nèi)核),而每個單獨的內(nèi)核獲得的并發(fā)速度則更快。 為什么不使用所有這些增加的并發(fā)性來提高所有查詢的速度,甚至在低查詢負(fù)載時也使CPU / IO飽和?
棘手的權(quán)衡
不幸的是,盡管搜索Lucene索引是一個自然而尷尬的并行問題,但對一個查詢使用多個線程會產(chǎn)生固有的協(xié)調(diào)開銷。 要理解為什么,請考慮一個簡單的類比:假設(shè)您需要蘋果,那么您將孩子送到當(dāng)?shù)氐碾s貨店購買。 如果您只有一個孩子,則將其送給她,她會在整個農(nóng)產(chǎn)品區(qū)域四處走走,挑選十個最好的蘋果,然后帶回家。
但是,如果您有五個孩子,然后將所有孩子都送到商店,他們會不會再快五倍,而忽略了他們往返商店的“聯(lián)網(wǎng)”時間? 他們?nèi)绾斡行У胤指罟ぷ?#xff1f;
也許您的孩子很聰明,他們首先將商店中的所有蘋果部分(現(xiàn)在有很多蘋果選擇 !)分成五個大致相等的部分。 每個人都圍繞著自己的蘋果區(qū)運行,挑選她能找到的十個最好的蘋果,然后他們都在結(jié)帳柜臺集合,密切合作,從現(xiàn)在擁有的五十個蘋果中選出十個最好的蘋果? 這有點浪費,因為孩子們總共收集了五十個蘋果,只是為了最終選擇實際的十個最佳蘋果,但確實比一個孩子選出十個最佳蘋果要快。
這實際上是Lucene今天實現(xiàn)并發(fā)搜索的方式:每個搜索器線程單獨工作以從一個線程片中找到自己的前N個最佳匹配(“映射”階段),然后,一旦所有查詢線程完成并重新加入主線程在主線程中,主線程使用部分合并排序從為每個線程切片收集的匹配中找到總前N個最佳匹配(“減少”階段)。 Lucene的CollectorManager , Collector和LeafCollector抽象都協(xié)同工作以實現(xiàn)此目的。 從現(xiàn)在開始,這意味著與單線程情況相比,完成了更多的工作
收集了M * N總匹配,然后最后減少到前N ,其中M是并發(fā)搜索線程的數(shù)量, N是請求檢索的頂級匹配的數(shù)量。
并發(fā)運行每個查詢時,增加的協(xié)調(diào)成本必然會損害搜索節(jié)點的紅線QPS容量(吞吐量),因為Lucene會花費更多的總CPU周期來查找最熱門。 但是,與此同時,當(dāng)搜索節(jié)點具有大量備用CPU資源時,它可以大大提高長桿查詢的等待時間,因為最困難的查詢現(xiàn)在可以同時運行。 此外,收集更多匹配并最終合并它們的額外成本通常對總體影響不大,因為通常是每個匹配的匹配和排名決定了總查詢成本,尤其是隨著索引變大,并且該成本是有效地跨線程拆分。
您可以通過限制可以同時運行的查詢數(shù)來進(jìn)一步“擴(kuò)大”這種折衷,從而最大化每個查詢將使用多少個CPU內(nèi)核。 您還可以預(yù)先估算每個查詢的成本,并僅在其成本足夠大時并發(fā)執(zhí)行該查詢,以便可以在單個線程中快速運行的簡單查詢不會支付在多個線程之間進(jìn)行同步的開銷。
這種吞吐量與延遲之間的權(quán)衡令人沮喪,這意味著在您的Lucene應(yīng)用程序中使用模式方法可能很有意義。 集群負(fù)載較輕時,通過限制可以同時運行的查詢數(shù)來減少每個查詢的多個線程,從而減少長桿延遲。 但是,當(dāng)群集正在運行時,接近其紅線容量時,每個查詢將轉(zhuǎn)移到單個線程以最大化吞吐量。 確保您正確地測量了等待時間,并且負(fù)載測試客戶端沒有遭受普遍常見的協(xié)調(diào)遺漏錯誤 ! 確認(rèn)您的負(fù)載測試客戶端正在使用開環(huán)測試,以便您看到真正的延遲影響,例如長時間的垃圾收集暫停,I / O打ic或交換。
持續(xù)的和未來的改進(jìn)
幸運的是,最近進(jìn)行了一些激動人心的改進(jìn),以減少多線程查詢的額外開銷。 Lucene現(xiàn)在還使用傳入(調(diào)用)線程來幫助并發(fā)搜索 。 用于將小段分組為切片(線程工作單元)的算法已得到改進(jìn) 。 現(xiàn)在,提前終止現(xiàn)在可以在多個搜索線程中使用一個共享的全局命中計數(shù)器來查詢一個查詢,從而降低了查詢的總成本。 查詢緩存將很快使用Executor進(jìn)行并發(fā)緩存,并且在某些情況下使用Executor時甚至可以更高效。 他們應(yīng)該在共享信息的同時共享信息,例如到目前為止收集的最差得分的最高命中 ,甚至在所有線程中使用單個共享優(yōu)先級隊列,而不是每個搜索線程都完全獨立地工作并僅在最后合并熱門命中。 共享優(yōu)先級隊列可能會導(dǎo)致過多的鎖定,因此,作為一種折衷,現(xiàn)在搜索可以高效地共享搜索器線程中收集到的最差命中值中的最好值 ,這顯示了令人印象深刻的luceneutil 基準(zhǔn)測試結(jié)果 。
這些改進(jìn)減少了并發(fā)搜索的額外成本,但是該成本永遠(yuǎn)不會為零,因為更頻繁的線程上下文切換,共享優(yōu)先級隊列的鎖爭用,命中計數(shù)器和優(yōu)先級隊列底部以及潛在的困難后果都會帶來固有的自然成本。現(xiàn)代非均勻內(nèi)存架構(gòu)(NUMA) 。
Lucene并發(fā)搜索的一個令人驚訝且令人失望的局限性在于,完全合并的索引(直至單個段)會丟失所有并發(fā)性! 這就是Bizarro World ,因為通常可以將其索引合并到一個段中以提高查詢性能! 但是,當(dāng)您查看長桿查詢延遲時,不幸的是,完全合并的索引會變慢,因為即使將Executor傳遞給IndexSearcher所有查詢現(xiàn)在都將再次成為單線程。 即使單個新近完成的大型合并也會在您的長極點延遲中導(dǎo)致鋸齒狀的模式,因為這會減少凈查詢并發(fā),盡管通過這種合并紅線群集的吞吐量仍會提高。 解決這個問題的一個簡單方法是允許多個線程搜索一個大的段 ,這很有效,因為Lucene具有自然的API,可以在段的“ docid空間”中搜索單獨的區(qū)域。
自讓-弗朗索瓦·哈勒克斯(Jean-Fran?oisHalleux)首次為Lucene提出并行搜索以來,并發(fā)搜索已經(jīng)走了很長一段路,我希望它還有很長的路要走,以使我們真正減少使用多線程進(jìn)行昂貴查詢的額外開銷。 隨著Lucene改進(jìn)其查詢計劃和優(yōu)化,我們將達(dá)到一個容易查詢運行單線程但代價高昂的查詢同時高效運行的地步。 這些改進(jìn)必須歸功于Lucene:現(xiàn)代服務(wù)器繼續(xù)添加越來越多的內(nèi)核,但并沒有使這些內(nèi)核變得更快,因此,包括Lucene在內(nèi)的現(xiàn)代軟件不可避免地必須找到有效利用所有這些并發(fā)性的方法。
[我在亞馬遜工作,并且本網(wǎng)站上的帖子屬于我自己,不一定代表亞馬遜的職位]
翻譯自: https://www.javacodegeeks.com/2019/10/concurrent-query-execution-apache-lucene.html
總結(jié)
以上是生活随笔為你收集整理的Apache Lucene中的并发查询执行的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux查看文件的内容命令(linux
- 下一篇: office工作日志文件_风暴事件处理器