ElasticSearch 優化
ES優化的幾個方面
- Filesystem Cache
- 資料預熱
- 冷熱分離
- ElasticSearch 中的關聯查詢
- Document 模型設計
- 分頁性能優化*
Filesystem Cache
你往 ES 裡寫的資料,實際上都寫到磁盤檔案裡去了,查詢的時候,作業系統會将磁盤檔案裡的資料自動緩存到 Filesystem Cache 裡面去。

ES 的搜尋引擎嚴重依賴于底層的 Filesystem Cache,你如果給 Filesystem Cache 更多的記憶體,盡量讓記憶體可以容納所有的 IDX Segment File 索引資料檔案,那麼你搜尋的時候就基本都是走記憶體的,性能會非常高。
歸根結底,你要讓 ES 性能好:
- 最佳的情況下,就是你的機器的記憶體,至少可以容納你的總資料量的一半。
- 減少不必要的字段寫入ES
資料預熱
就是對于那些你覺得比較熱的、經常會有人通路的資料,最好做一個專門的緩存預熱子系統。
然後對熱資料每隔一段時間,就提前通路一下,讓資料進入 Filesystem Cache 裡面去。這樣下次别人通路的時候,性能一定會好很多。
冷熱分離
ES 可以做類似于 MySQL 的水準拆分,就是說将大量的通路很少、頻率很低的資料,單獨寫一個索引,然後将通路很頻繁的熱資料單獨寫一個索引。
最好是将冷資料寫入一個索引中,然後熱資料寫入另外一個索引中,這樣可以確定熱資料在被預熱之後,盡量都讓他們留在 Filesystem OS Cache 裡,别讓冷資料給沖刷掉。
ES中的關聯查詢
對于 MySQL,我們經常有一些複雜的關聯查詢,在 ES 裡該怎麼玩兒?
ES 裡面的複雜的關聯查詢盡量别用,一旦用了性能一般都不太好。最好是先在 Java 系統裡就完成關聯,将關聯好的資料直接寫入 ES 中。搜尋的時候,就不需要利用 ES 的搜尋文法來完成 Join 之類的關聯搜尋了。
Document模型操作
Document 模型設計是非常重要的,很多操作,不要在搜尋的時候才想去執行各種複雜的亂七八糟的操作。
ES 能支援的操作就那麼多,不要考慮用 ES 做一些它不好操作的事情。如果真的有那種操作,盡量在 Document 模型設計的時候,寫入的時候就完成。
另外對于一些太複雜的操作,比如 join/nested/parent-child 搜尋都要盡量避免,性能都很差的。
分頁性能優化*
ES 的分頁是較坑的,為啥呢?舉個例子吧,假如你每頁是 10 條資料,你現在要查詢第 100 頁,實際上是會把每個 Shard 上存儲的前 1000 條資料都查到一個協調節點上。
如果你有 5 個 Shard,那麼就有 5000 條資料,接着協調節點對這 5000 條資料進行一些合并、處理,再擷取到最終第 100 頁的 10 條資料。
由于是分布式的,你要查第 100 頁的 10 條資料,不可能說從 5 個 Shard,每個 Shard 就查 2 條資料,最後到協調節點合并成 10 條資料吧?
你必須得從每個 Shard 都查 1000 條資料過來,然後根據你的需求進行排序、篩選等等操作,最後再次分頁,拿到裡面第 100 頁的資料。
你翻頁的時候,翻的越深,每個 Shard 傳回的資料就越多,而且協調節點處理的時間越長,非常坑爹。是以用 ES 做分頁的時候,你會發現越翻到後面,就越是慢。
解決方案
-
不允許深度查詢
跟産品經理說,你系統不允許翻那麼深的頁,預設翻的越深,性能就越差。
- Scroll API
scroll 查詢 可以用來對 Elasticsearch 有效地執行大批量的文檔查詢,而又不用付出深度分頁那種代價。
深度分頁的代價根源是結果集全局排序,如果請求的頁數較少時,假設每頁10個docs——即pageSize=10, 此時Elasticsearch不會有什麼問題。但若取的頁數較大時(深分頁),如請求第20頁,Elasticsearch不得不取出所有分片上的第1頁到第20頁的所有docs,假設你有16個分片,則需要在coordinate node 彙總到 shards* (from+size)條記錄,即需要 16*(20+10)記錄後做一次全局排序,再最終取出 from後的size條結果作為最終的響應。是以,當索引非常非常大(千萬或億),是無法按照 from + size 做深分頁的,分頁越深則越容易OOM,即便不OOM,也是很消耗CPU和記憶體資源的。如果去掉全局排序的特性的話查詢結果的成本就會很低。 遊标查詢用字段 _doc 來排序。 這個指令讓 Elasticsearch 僅僅從還有結果的分片傳回下一批結果。
啟用遊标查詢可以通過在查詢的時候設定參數 scroll 的值為我們期望的遊标查詢的過期時間。 遊标查詢的過期時間會在每次做查詢的時候重新整理,是以這個時間隻需要足夠處理目前批的結果就可以了,而不是處理查詢結果的所有文檔的所需時間。 這個過期時間的參數很重要,因為保持這個遊标查詢視窗需要消耗資源,是以我們期望如果不再需要維護這種資源就該早點兒釋放掉。 設定這個逾時能夠讓 Elasticsearch 在稍後空閑的時候自動釋放這部分資源。
GET /old_index/_search?scroll=1m 1⃣️ { "query": { "match_all": {}}, "sort" : ["_doc"], 2⃣️ "size": 1000 }
1⃣️保持遊标查詢視窗1分鐘(數字+機關)。
2⃣️關鍵字
_doc
是最有效的排序順序。
這個查詢的傳回結果包括一個字段
, 它是一個base64編碼的長字元串 (“scroll_id”) 。 現在我們能傳遞字段_scroll_id
到_scroll_id
查詢接口擷取下一批結果:_search/scroll
GET /_search/scroll { "scroll": "1m", 1⃣️ "scroll_id" : "cXVlcnlUaGVuRmV0Y2g7NTsxMDk5NDpkUmpiR2FjOFNhNnlCM1ZDMWpWYnRROzEwOTk1OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MTA5OTM6ZFJqYkdhYzhTYTZ5QjNWQzFqVmJ0UTsxMTE5MDpBVUtwN2lxc1FLZV8yRGVjWlI2QUVBOzEwOTk2OmRSamJHYWM4U2E2eUIzVkMxalZidFE7MDs=" }
1⃣️注意再次設定遊标查詢過期時間為一分鐘。
這個遊标查詢傳回的下一批結果。 盡管我們指定字段
的值為1000,我們有可能取到超過這個值數量的文檔。 當查詢的時候, 字段size
作用于單個分片,是以每個批次實際傳回的文檔數量最大為size
*size
number_of_primary_shards
。
注:注意遊标查詢每次傳回一個新字段
。每次我們做下一次遊标查詢, 我們必須把前一次查詢傳回的字段__scroll_id
scroll_id
傳遞進去。 當沒有更多的結果傳回的時候,我們就處理完所有比對的文檔了。
注:
以上版本的這兩個查詢接口為ElasticSearch5.0
POST
請求。
異常:
SearchContextMissingException
原因:SearchContextMissingException[No search context found for id [568]]
設定的時間太短已經逾時了,或者上次的請求傳回結果中沒有scroll
字段。_scroll_id