ElasticSearch 讀寫原理和性能優化
1 寫入原理
-
ES 每次數據寫入會先存放在內存 buffer 中,然後默認每隔 1s 將數據 refresh 進入 segment 文件內(此時寫入的數據即可被搜索到),並隨之同步到 FileSystem Cache 中。
-
爲了持久化數據,每次寫入操作也會同步至磁盤的 translog 中,當 translog 文件大小超過某個閾值(默認 512M)或者達到某個時間週期(默認 30min),系統會將內存 buffer 數據寫入新的 segment,並將 FileSystem Cache 中的緩存數據 flush 進入磁盤,清空 translog 當前數據。
-
若服務器宕機,ES 會從磁盤恢復 commit point 指向的 segment 數據,並且將 translog 裏的操作全部重新執行。
注:ES 爲了支持全文檢索,使用倒排索引來存儲數據。寫入磁盤後的倒排索引是不會進行更新和修改的,這樣有以下幾點好處:
-
不用擔心併發時鎖的問題。
-
由於數據不會變更,將數據讀入操作系統緩存後,可以避免去磁盤請求,大大提升了檢索效率。
-
一些附帶的緩存數據都持續生效。
-
單獨寫一個大的倒排索引時可以進行數據壓縮。
ES 通過使用多個倒排索引來進行數據的寫入操作,如果有數據新增,則寫入新的倒排索引中,如果有更新或者刪除,將舊數據放入 .del 文件,並通過增加版本號產生一條新數據。
寫請求首先會被打到協調節點,協調節點決定數據寫入的主分片位置,並在節點上執行寫入操作。
成功後通知副分片所在的節點執行,所有分片執行成功後反饋給協調節點,之後返回成功給客戶端。
2 搜索原理
-
客戶端發送請求到協調節點
-
協調節點將搜索請求轉發到所有分片對應的主分片或副本分片
-
query phase:每個 shard 將自己的搜索結果(其實就是一些 doc id)返回給協調節點,由協調節點進行數據的合併、排序、分頁等操作,產出最終結果
-
fetch phase:接着由協調節點根據 doc id 去各個節點上拉取實際的 document 數據,最終返回給客戶端
3 性能優化
讀優化
-
搜索性能:term、match > nested > parent-child
-
避免使用腳本。如果一定要用,則應該優先考慮 painless 和 expressions
-
爲不再更新的只讀索引執行 force merge,將 Lucene 索引合併爲單個分段,可以提升查詢速度
-
利用自適應副本選擇(ARS)提升 ES 響應速度
爲了充分利用計算資源和負載均衡,協調節點將搜索請求輪詢轉發到分片的每個副本,輪詢策略是負載均衡過程中最簡單的策略,任何一個負載均衡器都具備這種基礎的策略,缺點是不考慮後端實際系統壓力和健康水平。例如,一個分片的三個副本分佈在三個節點上,其中 Node2 可能因爲長時間 GC、磁盤 I/O 過高、網絡帶寬跑滿等原因處於忙碌狀態,如下圖所示。
如果搜索請求被轉發到副本 2,則會看到相對於其他分片來說,副本 2 有更高的延遲。
ES 希望這個過程足夠智能,能夠將請求路由到其他數據副本,直到該節點恢復到足以處理更多搜索請求的程度。在 ES 中,此過程稱爲 “自適應副本選擇”,從 ES 7.0 開始默認開啓。
-
不關注搜索召回匹配得分時,使用 filter 代替 must
-
使用滾動查詢代替深度分頁
-
帶用 routing 查詢
寫優化
- translog flush 間隔調整
從 ES 2.x 開始,在默認設置下,translog 的持久化策略爲:每個請求都 flush。對應配置項如下:index.translog.durability: request。
這是影響 ES 寫入速度的最大因素。但是隻有這樣,寫操作纔有可能是可靠的。
如果系統可以接受一定概率的數據丟失,則調整 translog 持久化策略爲週期性和一定大小的時候 flush。如下:
##translog的刷盤策略按sync_interval配置指定的時間週期進行
index.translog.durability: async
##加大translog刷盤間隔時間。默認爲5s,不可低於100ms
index.translog.sync_interval: 120s
##超過這個大小會導致refresh操作,產生新的Lucene分段。默認值爲512MB
index.translog.flush_threshold_size: 1024mb
- 索引刷新間隔 refresh_interval
默認情況下索引的 refresh_interval 爲 1 秒,這意味着數據寫 1 秒後就可以被搜索到,每次索引的 refresh 會產生一個新的 Lucene 段,這會導致頻繁的 segmentmerge 行爲。
如果不需要這麼高的搜索實時性,應該降低索引 refresh 週期,例如:index.refresh_interval: 120s
- 使用 bulk 請求
批量寫比一個索引請求只寫單個文檔的效率高得多,但是要注意 bulk 請求的整體字節數不要太大,太大的請求可能會給集羣帶來內存壓力,因此每個請求最好避免超過幾十兆字節。
- 自動生成 doc ID
寫入 doc 時如果外部指定了 id,則 ES 會先嚐試讀取原來 doc 的版本號,以判斷是否需要更新。這會涉及一次讀取磁盤的操作,通過自動生成 doc ID 可以避免這個環節
- 調整字段 Mappings
a. 減少字段數量,對於不需要建立索引的字段,不寫入 ES。
b. 將不需要建立索引的字段 index 屬性設置爲 not_analyzed 或 no。對字段不分詞,或者不索引,可以減少很多運算操作,降低 CPU 佔用。尤其是 binary 類型,默認情況下佔用 CPU 非常高,而這種類型進行分詞通常沒有什麼意義。
c. 減少字段內容長度,如果原始數據的大段內容無須全部建立索引,則可以儘量減少不必要的內容。
d. 使用不同的分析器(analyzer),不同的分析器在索引過程中運算複雜度也有較大的差異。
- 減少副本數量
ES 索引多個副本可以提高搜索併發度,但是也會影響 ES 的寫入效率,針對日誌等對數據一致性要求不高的業務,可以將副本數減少爲 1。
- 使用 reindex 同步數據
存儲優化
- 禁用 doc values
所有支持 doc value 的字段都默認啓用了 doc value。如果確定不需要對字段進行排序或聚合,或者從腳本訪問字段值,則可以禁用 doc value 以節省磁盤空間:
- 不要使用默認的動態字符串映射
默認的動態字符串映射會把字符串類型的字段同時索引爲 text 和 keyword。如果只需要其中之一,則顯然是一種浪費。通常,id 字段只需作爲 keyword 類型進行索引,而 body 字段只需作爲 text 類型進行索引。
- 數值類型長度夠用就好
爲數值類型選擇的字段類型也可能會對磁盤使用空間產生較大影響,整型可以選擇 byte、short、integer 或 long,浮點型可以選擇 scaled_float、float、double、half_float。
每個數據類型的字節長度是不同的,爲業務選擇夠用的最小數據類型,可以節省磁盤空間。
參考文章
- https://www.elastic.co/guide/en/elasticsearch/guide
2.《ElasticSearch 源碼解析與優化實戰》
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Mf06R8KFY4jgOCNC9gEG0A