一起學Elasticsearch的寫入和檢索調優
當涉及到大規模數據存儲和檢索時,Elasticsearch以其快速、高效和強大的搜索能力而聞名,并被廣泛應用于各種場景,例如日志分析、全文搜索和實時數據分析。
然而,并不是只要將數據存入ES就可以立即獲得最佳性能和查詢效率。正如任何強大的工具一樣,ES也需要進行調優,以充分發揮其潛力并滿足特定業務需求。
在這篇文章中,我們將探討ES寫入調優和查詢調優的關鍵方面,并提供一些實用的技巧和建議,幫助您優化ES集群的性能和響應速度。
寫入調優
基本原則
寫入性能調優是建立在 Elasticsearch 的寫入原理之上的。
ES 數據寫入具有一定的延時性,這是為了減少頻繁的索引文件產生。默認情況下 ES 每秒生成一個 Segment 文件,當達到一定閾值的時候會執行merge,merge 過程發生在 JVM中,頻繁的生成 Segmen 文件可能會導致頻繁的觸發 FGC,導致 OOM。
為了避免這種情況,通常采取的手段是降低 Segment 文件的生成頻率,辦法有兩個:一個是增加時間閾值,另一個是增大Buffer的空間閾值,因為緩沖區寫滿也會生成 Segment 文件。
生產經常面臨的寫入可以分為兩種情況:
高頻低量:高頻的創建或更新索引或文檔,一般發生在 C 端業務場景下。
低頻高量:一般情況為定期重建索引或批量更新文檔數據。
在搜索引擎的業務場景下,用戶一般并不需要那么高的寫入實時性。比如你在網站發布一條征婚信息,或者二手交易平臺發布一個商品信息。其他人并不是馬上能搜索到的,這其實也是正常的處理邏輯。
這個延時的過程需要處理很多事情,比如:你的信息需要后臺審核。
你發布的內容在搜索服務中需要建立索引,而且你的數據可能并不會馬上被寫入索引,而是等待要寫入的數據達到一定數量之后,批量寫入。
這種操作優點類似于我們快遞物流的場景,只有當快遞數量達到一定量級的時候,比如能裝滿整個車的時候,快遞車才會發車。因為反正是要跑一趟,裝的越多,平均成本越低。
這和我們數據寫入到磁盤的過程是非常相似的,我們可以把一條文檔數據看做是一個快遞,而快遞車每次發車就是向磁盤寫入數據的一個過程,這個過程不宜太多,太多只會降低性能,就是體現在運輸成本上面,而對于我們數據寫入而言就是體現在我們硬件性能損耗上面。
優化手段
以下為常見數據寫入的調優手段,寫入調優均以提升寫入吞吐量和并發能力為目標,而非提升寫入實時性。
增加 flush 時間間隔
flush的過程是非常消耗資源的。增加flush的時間間隔目的是減小數據寫入磁盤的頻率,降低磁盤IO頻率。
增加 refresh_interval 參數的值
增加 refresh_interval 參數的值,目的是減少segment文件的創建,降低merge次數,因為merge是發生在jvm中的,有可能導致full GC。
ES的 refresh 行為非常昂貴,并且在正在進行的索引活動時經常調用,會降低索引速度。
默認情況下,Elasticsearch 每秒定期刷新索引,如果沒有搜索流量或搜索流量很少(例如每 5 分鐘不到一個搜索請求),可以適當調大此參數的值。
增加Buffer大小
本質也是減小refresh的時間間隔,因為導致segment文件創建的原因不僅有時間閾值,還有buffer空間大小,寫滿了也會創建。默認值為JVM 空間的10%。
關閉副本
當需要單次寫入大量數據的時候,建議關閉副本,暫停搜索服務,或選擇在檢索請求量谷值區間時間段來完成。
關閉副本可以帶來如下好處:
- 減小讀寫之間的資源搶占,讀寫分離。
- 當檢索請求數量很少的時候,可以減少甚至完全刪除副本分片,關閉segment的自動創建以達到高效利用內存的目的,因為副本的存在會導致主從之間頻繁的進行數據同步,大大增加服務器的資源占用。
具體可通過設置index.number_of_replicas 為0以加快索引速度。沒有副本意味著丟失單個節點可能會導致數據丟失,因此數據保存在其他地方很重要,以便在出現問題時可以重試初始加載。初始加載完成后,可以設置index.number_of_replicas改回其原始值。
禁用swap
大多數操作系統嘗試將盡可能多的內存用于文件系統緩存,并急切地換掉未使用的應用程序內存。這可能導致部分 JVM 堆甚至其可執行頁面被換出到磁盤。
交換對性能和節點穩定性非常不利,應該不惜一切代價避免。它可能導致垃圾收集持續幾分鐘而不是幾毫秒,并且可能導致節點響應緩慢甚至與集群斷開連接。在Elastic分布式系統中,讓操作系統殺死節點更有效。
使用多個工作線程
發送批量請求的單個線程不太可能最大化 Elasticsearch 集群的索引容量。為了使用集群的所有資源,應該從多個線程或進程發送數據。除了更好地利用集群的資源外,還有助于降低每個 fsync 的成本。
確保注意 TOO_MANY_REQUESTS 響應代碼:429。(EsRejectedExecutionException使用 Java 客戶端),這是 Elasticsearch 告訴我們它無法跟上當前索引速度的方式。發生這種情況時,應該在重試之前暫停索引,最好使用隨機指數退避。
與調整批量請求的大小類似,只有測試才能確定最佳工作線程數量是多少。這可以通過逐漸增加線程數量來測試,直到集群上的 I/O 或 CPU 飽和。
max_result_window參數
max_result_window是分頁返回的最大數值,默認值為10000。max_result_window本身是對JVM的一種保護機制,通過設定一個合理的閾值,避免初學者分頁查詢時由于單頁數據過大而導致OOM。
設置一個合理的大小是需要通過你的各項指標參數來衡量確定的,比如你用戶量、數據量、物理內存的大小、分片的數量等等。通過監控數據和分析各項指標從而確定一個最佳值,并非越大越好。
查詢調優
讀寫性能不可兼得
首先要明確一點:魚和熊掌不可兼得。讀寫性能調優在很多場景下是只能二選一的。犧牲 A 換 B 的行為非常常見。索引本質上也是通過空間換取時間。犧牲寫入實時性就是為了提高檢索的性能。
當你在二手平臺或者某垂直信息網站發布信息之后,是允許有信息寫入的延時性的。但是檢索不行,甚至 1 秒的等待時間對用戶來說都是無法接受的。滿足用戶的要求甚至必須做到10 ms以內。
優化手段
避免單次召回大量數據
搜索引擎最擅長的事情是從海量數據中查詢少量相關文檔,而非單次檢索大量文檔。非常不建議動輒查詢上萬數據。如果有這樣的需求,建議使用滾動查詢
避免單個文檔過大
鑒于默認http.max_content_length設置為 100MB,Elasticsearch 將拒絕索引任何大于該值的文檔。您可能決定增加該特定設置,但 Lucene 仍然有大約 2GB 的限制。
即使不考慮硬性限制,大型文檔通常也不實用。大型文檔對網絡、內存使用和磁盤造成了更大的壓力,即使對于不請求的搜索請求也是如此。
有時重新考慮信息單元應該是什么是有用的。例如,您想讓書籍可搜索的事實并不一定意味著文檔應該包含整本書。使用章節甚至段落作為文檔可能是一個更好的主意,然后在這些文檔中擁有一個屬性來標識它們屬于哪本書。這不僅避免了大文檔的問題,還使搜索體驗更好。例如,如果用戶搜索兩個單詞 fooand bar,則不同章節之間的匹配可能很差,而同一段落中的匹配可能很好。
單次查詢10條文檔 好于 10次查詢每次一條
批量請求將產生比單文檔索引請求更好的性能。但是每次查詢多少文檔最佳,不同的集群最佳值可能不同,為了獲得批量請求的最佳閾值,建議在具有單個分片的單個節點上運行基準測試。
首先嘗試一次索引 100 個文檔,然后是 200 個,然后是 400 個等。在每次基準測試運行中,批量請求中的文檔數量翻倍。當索引速度開始趨于平穩時,就可以獲得已達到數據批量請求的最佳大小。在相同性能的情況下,當大量請求同時發送時,太大的批量請求可能會使集群承受內存壓力,因此建議避免每個請求超過幾十兆字節。
數據建模
很多人會忽略對 Elasticsearch 數據建模的重要性。
nested屬于object類型的一種,是Elasticsearch中用于復雜類型對象數組的索引操作。Elasticsearch沒有內部對象的概念,因此,ES在存儲復雜類型的時候會把對象的復雜層次結果扁平化為一個鍵值對列表。
特別是,應避免Join連接。Nested 可以使查詢慢幾倍,Join 會使查詢慢數百倍。兩種類型的使用場景應該是:Nested針對字段值為非基本數據類型的時候,而Join則用于當子文檔數量級非常大的時候。
給系統留足夠的內存
Lucene的數據的fsync是發生在OS cache的,要給OS cache預留足夠的內存大小。
預索引
利用查詢中的模式來優化數據的索引方式。例如,如果所有文檔都有一個price字段,并且大多數查詢 range 在固定的范圍列表上運行聚合,可以通過將范圍預先索引到索引中并使用聚合來加快聚合速度。
使用 filter 代替 query
query和filter的主要區別在:filter是結果導向的而query是過程導向。query傾向于“當前文檔和查詢的語句的相關度”,而filter傾向于“當前文檔和查詢的條件是不是相符”。即在查詢過程中,query是要對查詢的每個結果計算相關性得分的,而filter不會。另外filter有相應的緩存機制,可以提高查詢效率。
避免深度分頁
避免單頁數據過大,可以參考百度或者淘寶的做法。es提供兩種解決方案 scroll search 和 search after。
使用 Keyword 類型
并非所有數值數據都應映射為數值字段數據類型。Elasticsearch為查詢優化數字字段,例如integeror long。如果不需要范圍查找,對于 term查詢而言,keyword 比 integer 性能更好。
避免使用腳本
Scripting是Elasticsearch支持的一種專門用于復雜場景下支持自定義編程的強大的腳本功能。相對于 DSL 而言,腳本的性能更差,DSL能解決 80% 以上的查詢需求,如非必須,盡量避免使用 Script。