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