from:http://www.infoq.com/cn/articles/anatomy-of-an-elasticsearch-cluster-part03
剖析Elasticsearch集群系列涵蓋了當今最流行的分布式搜索引擎Elasticsearch的底層架構和原型實例。本文是這個系列的第三篇,我們將討論Elasticsearch是如何提供近實時搜索并權衡搜索相關性計算的。
本系列已經得到原文著者Ronak Nathani的授權
在本系列的前一篇中,我們討論了Elastisearch如何解決分布式系統中的一些基本挑戰。在本文中,我們將探討Elasticsearch在近實時搜索及其權衡計算搜索相關性方面的內容,Insight Data的工程師們已經在使用Elasticsearch構建的數據平臺之上,對此有所實踐。我將在本文中主要講述:
近實時搜索
雖然Elasticsearch中的變更不能立即可見,它還是提供了一個近實時的搜索引擎。如前一篇中所述,提交Lucene的變更到磁盤是一個代價昂貴的操作。為了避免在文檔對查詢依然有效的時候,提交變更到磁盤,Elasticsearch在內存緩沖和磁盤之間提供了一個文件系統緩存。內存緩存(默認情況下)每1秒刷新一次,在文件系統緩存中使用倒排索引創建一個新的段。這個段是開放的并對搜索有效。
文件系統緩存可以擁有文件句柄,文件可以是開放的、可讀的或者是關閉的,但是它存在于內存之中。因為刷新間隔默認是1秒,變更不能立即可見,所以說是近實時的。因為translog是尚未落盤的變更持久化記錄,它能有助于CRUD操作方面的近實時性。對于每次請求來說,在查找相關段之前,任何最近的變更都能從translog搜索到,因此客戶端可以訪問到所有的近實時變更。
你可以在創建/更新/刪除操作后顯式地刷新索引,使變更立即可見,但我并不推薦你這樣做,因為這樣會創建出來非常多的小segment而影響搜索性能。對于每次搜索請求來說,給定Elasticsearch索引分片中的全部Lucene段都會被搜索到,但是,對于Elasticsearch來說,獲取全部匹配的文檔或者很深結果頁的文檔是有害的。讓我們來一起看看為什么是這樣。
為什么深層分頁在分布式搜索中是有害的?
當我們的一次搜索請求在Elasticsearch中匹配了很多的文檔,默認情況下,返回的第一頁只包含前10條結果。search API提供了from和size參數,用于指定對于匹配搜索的全部文檔,要返回多深的結果。舉例來說,如果我們想看到匹配搜索的文檔中,排名為50到60之間的文檔,可以設置from=50,size=10。當每個分片接收到這個搜索請求后,各自會創建一個容量為from+size的優先隊列來存儲該分片上的搜索結果,然后將結果返回給協調節點。

如果我們想看到排名為50,000到50,010的結果,那么每個分片要創建一個容量為50,010的優先隊列來存儲結果,而協調節點要在內存中對數量為shards * 50,010的結果進行排序。這個級別的分頁有可能得到結果,也有可以無法實現,這取決于我們的硬件資源,但是這足以說明,我們得非常小心地使用深分頁,因為這非常容易使我們的集群崩潰。
一種獲取全部匹配結果文檔的可行性方案是使用scroll API,它的角色更像關系數據庫中的游標。使用scroll API無法進行排序,每個分片只要有匹配搜索的文檔,就會持續發送結果給協調節點。
獲取大量文檔的時候,對結果進行得分排序會非常昂貴。并且由于Elasticsearch是分布式系統,為每個文檔計算搜索相關性得分是非常昂貴的。現在,讓我們一起看看計算搜索相關性的諸多權衡中的一種。
計算搜索相關性中的權衡
Elasticsearch使用tf-idf來計算搜索相關性。由于其分布式的性質,計算全局的idf(inverse document frequency,逆文檔頻率)非常昂貴。反之可以這樣,每個分片計算本地的idf并將相關性得分分配給結果文檔,返回的結果只關乎該分片上的文檔。同樣地,所有分片使用本地idf計算的相關性得分,返回結果文檔,協調節點對所有結果排序并返回前幾條。這樣做在大多數情況下是沒有問題的,除非索引的關鍵字詞項有傾斜或者單個分片上沒有代表全局的足夠數據。
比如說,如果我們搜索“insight”這個詞,但包含"insight"這個詞項的大多數文檔都存放在一個分片上,這樣以來匹配查詢的文檔將不能公平地在每個分片上進行排序,因為每個分片上的本地idf的值非常不同,得到的搜索結果可能不會非常相關。同樣地,如果沒有足夠的數據,那么對于某些搜索而言,本地idf的值可能大有不同,結果也會不如預期相關。在有足夠數據的真實場景中,本地idf值一般會趨于均等,搜索結果是相關的,因為文檔得到了公平的得分。
這里有2種應對本地idf得分的辦法,但都不建議真正在生產環境中使用。
- 一種辦法是一索引一分片,本地idf即是全局idf,但這沒有為并行計算/水平伸縮留有余地,對于大型索引并不實用。
- 另一種辦法是在搜索請求中使用dfs_query_then_search (dfs = distributed frequency search,分布式頻率搜索) 參數,這樣以來,會首先計算每個分片的本地idf,然后綜合這些本地idf的值來計算整個索引的全局idf值,最后使用全局idf計算相關性得分來返回結果。這種方式不為生產環境推薦,因為有足夠的數據確保詞項頻率分布均勻。
在本系列的過去幾篇中,我們回顧了一些Elasticsearch的基本原則,對于我們理解并上手Elasticsearch,這些內容非常重要。在接下來的一篇中,我將使用Apache Spark來研究Elasticsearch中的索引數據。
查看英文原文:Anatomy of an Elasticsearch Cluster: Part III