Elasitcsearch索引優化

小米運維發表於2018-11-15
本文主要介紹了Elasticsearch的索引優化和索引的settings、mappings,並且舉例說明了在什麼場景下應該使用什麼引數來實現更細緻的優化。
上篇文章回顧:Mac OS下用Homebrew安裝自己寫的開源工具


介紹

Elasticsearch(下面簡稱ES)本身的搜尋效能已經非常優秀,預設引數也適用於大部分場景;但為了更高效地利用計算資源,或者防止出現一個請求消耗掉叢集所有的資源情況,我們會對一些引數進行調優和限制。

有關於叢集級別的優化,網上已經有很多相關的文章可以借鑑,而索引級別的優化則相對較少。ES的叢集優化通常是通用性的優化,而索引優化則更加貼近於業務,更有針對性。本文主要將介紹索引的settings、mappings,舉例說明什麼場景下使用什麼引數來實現更細緻的優化。

設定(settings)

1、index.codec 儲存壓縮率

常用的引數,預設使用LZ4壓縮演算法,優點是壓縮速度非常快,對於寫入速度要求比較高,有充裕的儲存空間和讀寫速度的儲存介質的場景使用。
將該設定改為best_compression可使用DEFLATE演算法來獲得更高的壓縮率,但會使用更多的CPU資源和時間來壓縮/解壓。通常都將該引數設為best_compression,除非寫入時CPU成為瓶頸。
該設定可在索引建立時或者處於Close狀態的索引上指定,新的壓縮演算法將在下一次merge後生效。

2、index.number_of_shards 索引分片數

另一個常用的引數,增加索引分片數可提高搜尋的併發度。當一個Query分發到不同分片上時,ES會將充分利用多核心CPU的優勢,每個分片使用不同的核心來處理搜尋請求;通常來說,分配到一個節點上的主副分片之和等於CPU核心數將獲得最大的CPU利用率和最短的搜尋耗時。但分片數過多也會在合併搜尋結果時帶來額外的計算量,同時若其他的索引也在進行搜尋的會造成上下文切換,因此搜尋耗時不會隨著分片數增長而線性減少。

在滿足搜尋延遲的前提下,設定最小的分片數是最佳的選擇(暫時不考慮分片大小)。

3、index.refresh_interval 重新整理間隔

也一個常用的引數,用於調整寫入時的重新整理間隔,重新整理(refresh)既是將記憶體中的資料寫到檔案系統緩衝中;寫入的資料在重新整理後才能被搜尋,這也是ES被稱為近實時搜尋的原因。其本質是Segment的生成間隔。增大重新整理間隔可以減少Segment的生成,同時也能減少搜尋耗時。通常將該值設為"30s",對實時性要求較高的索引可適當減少重新整理間隔。

4、index.translog.durability 事務日誌

因為Segment無法做到實時生成,如果在兩次refresh間隔中節點宕掉,會導致資料丟失。Translog的存在則是為了解決這個問題。Translog中記錄了每一次對ES的操作,預設是每個請求(bulk, delete, upadte, etc)完成後,或者達到同步間隔(sync_interval,預設5s)都會將日誌持久化(fsync)到磁碟中,當主副分片都確認持久化完成後,客戶端才會受到請求成功的響應;若節點出現異常,可以重放translog中在最後一次重新整理後發生的變更操作來恢復資料。
但是每次執行fsync會帶來少許的效能損失,若可以接受幾秒鐘的資料損失,可以將該引數設為"async",使用非同步fsync來減小translog帶來的影響。

5、index.unassigned.node_left.delayed_timeout 推遲分片分配

當某個節點離線後,預設情況下1m後就會執行重建副本。當我們預知短時間內離線的節點能夠重新加入叢集,可以延長重建副本的延時,來避免不必要的分片拷貝。
可使用PUT /_all/_settings來對叢集中現有的所有索引應用該引數,該引數值視節點重啟的時間而定,通常可設為"5m"。

6、index.merge.scheduler.max_thread_count merge執行緒數

ES預設會使用max(1, min(4, CPU核心數 / 2))的執行緒數來進行merge操作,這會降低機械硬碟的讀寫效率,如果索引儲存在機械硬碟上,可將該值固定為"1"。

7、"mapper.dynamic"

動態對映一方面降低了使用者的使用成本,另一方面也對叢集管理造成了不便——由於不規範的稀疏的資料導致索引中的欄位數大幅增長而影響搜尋效能。如果對一份資料規範性要求比較高,可以通過將該引數設為"false"來關閉自動生成欄位對映的特性。

8、index.routing.allocation.total_shards_per_node 限制節點分片數

以我們線上使用的經驗來看,ES5.x(6.x的情況有待觀察)的分片分配機制不夠完善。當某個節點新加入叢集的時候,會優先把大索引的分片和當天正在寫入的索引的大部分分片分配到該節點上,導致該節點的負載飆高。通過設定該引數可限制索引在該節點上的最大分片數,一定程度上避免這種情況的發生。
但是這個引數不能剛好設定為( pri_shard_num + rep_shard_num) / data_node_num,如果恰好設定成這個值,可能會出現最後一個分配的副本和主分片在同一節點上,導致分配不上去。

9、index.sort 寫入預排序

若您大部分的搜尋請求是基於一定順序取TopN,例如看最近的10條日誌,耗時最長的100個請求,或者交易最頻繁的5個使用者等等,ES預設情況下會掃描全部資料後在返回結果。在ES6.x中加入了一個特性,能夠大幅降低這類搜尋的耗時:Index Sorting。若啟用該特性,ES在寫入時候就會按照一定順序排列資料,例如按時間倒敘,這樣在搜尋的時候就不要掃描全量資料,只要獲得每個Segment前TopN的資料即可返回結果。該特性需要在索引建立的時候啟用,方法如下:

PUT log
{    "settings" : {
        "index" : {
                    "sort.field" : "timestamp",
                    "sort.order" : "desc"
        }
    },
    ...
}複製程式碼

注意,啟用該特性會降低寫入速度,因此需要在寫入和搜尋之間進行權衡。

對映(mapping)

1、"dynamic_templates" 動態模板

ES之所以具有不需要事先定義schema是因為dynamic mapping功能的存在,它大大降低了使用者使用成本。
預設ES對字串型別的欄位會自動對映成text型別,同時再生成一個名為欄位名.keyword的keyword型別的子欄位。若大部分欄位不需要對欄位進行分詞,可將預設的字串型別對映為keyword來降低寫入時的額外資源開銷:

"mappings": {
  "doc": {
      "dynamic_templates": [
      {        "strings_as_keywords": {
                "match_mapping_type": "string", 
                "mapping": { "type": "keyword" }
        }
      }
    ]
  }
}複製程式碼

2、"dynamic"

效果同索引級別的"mapper.dynamic",可針對某個欄位或者整個Type設定,通過將該引數設為"false"來關閉自動生成欄位的特性,新新增的欄位將被忽略;或者設為"strict",如果遇到新欄位丟擲異常。

3、"doc_value"

對於keyword型別的欄位,如果我們只需要對該欄位進行搜尋,不需要進行聚合、排序或是使用指令碼操作,可以將doc_value設為false,可大幅降節省磁碟空間,一定程度上提升索引速度。

4、"index"

如果我們只需要對某個欄位進行聚合、排序或是使用指令碼操作,不需要搜尋該欄位(通常是keyword或者numeric型別的欄位),可以將index設為false,可釋放該欄位倒排索引佔用的常駐記憶體。
索引的倒排索引佔用的記憶體可通過GET /<INDEX_NAME>/_stats?human=true中的segments.terms_memory檢視。

5、"norms"

norms引數對搜尋評分很有用,若我們不需要計算欄位的評分,將該引數設為false,特別是該欄位僅用於過濾或聚合。若索引中存在某個欄位啟用了norms,無論文件中是否存在該欄位,所有文件都會佔用N bytes(N為文件數)磁碟空間,因此禁用norms可以釋放大量的磁碟空間。
norms可以通過以下API禁用,將在生成新的Segment時生效,但不能被重新啟用。

PUT my_index/_mapping/_doc
{  "properties": {
    "title": {
          "type": "text",
          "norms": false
    }
  }
}複製程式碼

6、"enabled"

在一些情況下,我們只需要儲存某個欄位,通過搜尋其他的欄位來定位到這條記錄檢視這個欄位的內容,不需要對該欄位進行搜尋、排序、聚合等操作,可以將該欄位的"enabled"設為false,同時節省大量記憶體和磁碟的空間。

7、"ignore_malformed"

我們經常碰到一些內容不規範或者格式不對的資料,例如某個IP欄位的裡出現"UNKNOWN",某個數字欄位出現"-"。如果在這些欄位上已經設定了明確的型別,比如"ip"或者"float",欄位中出現了非該型別的值,ES會丟擲異常並丟棄整條資料。
我們可以在該欄位上設定"ignore_malformed": ture來忽略這個欄位並保留該文件中的其他欄位。

8、"_source"

該欄位屬於索引的後設資料,其中儲存了文件原始的JSON內容,會被儲存但不會被索引,用於執行fetch請求時返回原始資料。
當我們不需要獲得任何原始資料,只需要對資料進行排序,聚合等計算,或者寫入時文件id是手動指定的,通過搜尋取到文件id來進一步處理,可以將"_source"設為false來節約大量的磁碟空間。
注意,禁用"_source"後會導致無法使用update,update_by_query,reindex等需要獲取原始文件的API,也無法使用高亮功能。

總結

一些比較常用的針對索引級別的設定就介紹到這裡,某些在ES6.x以上的版本已被棄用的引數將不再贅述。通過靈活地配置引數可將ES的效能發揮到更高的水平。


本文首發於公眾號“小米運維”,點選檢視原文


相關文章