對6.3:html
修改Elasticsearch中JVM配置文件jvm.options:node
Dlog4j2.enable.threadlocals=false
注: 本文主要針對ES 2.x。
「該給ES分配多少內存?」
「JVM參數如何優化?「
「爲什麼個人Heap佔用這麼高?」
「爲什麼常常有某個field的數據量超出內存限制的異常?「
「爲什麼感受上沒多少數據,也會常常Out Of Memory?」
以上問題,顯然沒有一個統一的數學公式可以給出答案。 和數據庫相似,ES對於內存的消耗,和不少因素相關,諸如數據總量、mapping設置、查詢方式、查詢頻度等等。默認的設置雖開箱即用,但不能適用每一種使用場景。做爲ES的開發、運維人員,若是不瞭解ES對內存使用的一些基本原理,就很難針對特有的應用場景,有效的測試、規劃和管理集羣,從而踩到各類坑,被各類問題挫敗。
要理解ES如何使用內存,先要理解下面兩個基本事實:
1. ES是JAVA應用
2. 底層存儲引擎是基於Lucene的
看似很普通是嗎?但其實沒多少人真正理解這意味着什麼。
首先,做爲一個JAVA應用,就脫離不開JVM和GC。不少人上手ES的時候,對GC一點概念都沒有就去網上抄各類JVM「優化」參數,卻仍然被heap不夠用,內存溢出這樣的問題搞得焦頭爛額。瞭解JVM GC的概念和基本工做機制是頗有必要的,本文不在此作過多探討,讀者能夠自行Google相關資料進行學習。如何知道ES heap是否真的有壓力了? 推薦閱讀這篇博客:Understanding Memory Pressure Indicator。 即便對於JVM GC機制不夠熟悉,頭腦裏仍是須要有這麼一個基本概念: 應用層面生成大量長生命週期的對象,是給heap形成壓力的主要緣由,例如讀取一大片數據在內存中進行排序,或者在heap內部建cache緩存大量數據。若是GC釋放的空間有限,而應用層面持續大量申請新對象,GC頻度就開始上升,同時會消耗掉不少CPU時間。嚴重時可能惡性循環,致使整個集羣停工。所以在使用ES的過程當中,要知道哪些設置和操做容易形成以上問題,有針對性的予以規避。
其次,Lucene的倒排索引(Inverted Index)是先在內存裏生成,而後按期以段文件(segment file)的形式刷到磁盤的。每一個段實際就是一個完整的倒排索引,而且一旦寫到磁盤上就不會作修改。 API層面的文檔更新和刪除其實是增量寫入的一種特殊文檔,會保存在新的段裏。不變的段文件易於被操做系統cache,熱數據幾乎等效於內存訪問。
基於以上2個基本事實,咱們不難理解,爲什麼官方建議的heap size不要超過系統可用內存的一半。heap之外的內存並不會被浪費,操做系統會很開心的利用他們來cache被用讀取過的段文件。
Heap分配多少合適?聽從官方建議就沒錯。 不要超過系統可用內存的一半,而且不要超過32GB。JVM參數呢?對於初級用戶來講,並不須要作特別調整,仍然聽從官方的建議,將xms和xmx設置成和heap同樣大小,避免動態分配heap size就行了。雖然有針對性的調整JVM參數能夠帶來些許GC效率的提高,當有一些「壞」用例的時候,這些調整並不會有什麼魔法效果幫你減輕heap壓力,甚至可能讓問題更糟糕。
那麼,ES的heap是如何被瓜分掉的? 說幾個我知道的內存消耗大戶並分別作解讀:
1. segment memory
2. filter cache
3. field data cache
4. bulk queue
5. indexing buffer
6. state buffer
7. 超大搜索聚合結果集的fetch
8. 對高cardinality字段作terms aggregation
Segment Memory
Segment不是file嗎?segment memory又是什麼?前面提到過,一個segment是一個完備的lucene倒排索引,而倒排索引是經過詞典 (Term Dictionary)到文檔列表(Postings List)的映射關係,快速作查詢的。 因爲詞典的size會很大,所有裝載到heap裏不現實,所以Lucene爲詞典作了一層前綴索引(Term Index),這個索引在Lucene4.0之後採用的數據結構是FST (Finite State Transducer)。 這種數據結構佔用空間很小,Lucene打開索引的時候將其全量裝載到內存中,加快磁盤上詞典查詢速度的同時減小隨機磁盤訪問次數。
下面是詞典索引和詞典主存儲之間的一個對應關係圖:
數據庫
Lucene file的完整數據結構參見Apache Lucene - Index File Formats
說了這麼多,要傳達的一個意思就是,ES的data node存儲數據並不是只是耗費磁盤空間的,爲了加速數據的訪問,每一個segment都有會一些索引數據駐留在heap裏。所以segment越多,瓜分掉的heap也越多,而且這部分heap是沒法被GC掉的! 理解這點對於監控和管理集羣容量很重要,當一個node的segment memory佔用過多的時候,就須要考慮刪除、歸檔數據,或者擴容了。
怎麼知道segment memory佔用狀況呢? CAT API能夠給出答案。
1. 查看一個索引全部segment的memory佔用狀況:
apache
2. 查看一個node上全部segment佔用的memory總和:
api
那麼有哪些途徑減小data node上的segment memory佔用呢? 總結起來有三種方法:
1. 刪除不用的索引
2. 關閉索引 (文件仍然存在於磁盤,只是釋放掉內存)。須要的時候能夠從新打開。
3. 按期對再也不更新的索引作optimize (ES2.0之後更改成force merge api)。這Optimze的實質是對segment file強制作合併,能夠節省大量的segment memory。
Filter Cache (5.x裏叫作Request cache)
Filter cache是用來緩存使用過的filter的結果集的,須要注意的是這個緩存也是常駐heap,在被evict掉以前,是沒法被GC的。個人經驗是默認的10% heap設置工做得夠好了,若是實際使用中heap沒什麼壓力的狀況下,才考慮加大這個設置。
Field Data cache
在有大量排序、數據聚合的應用場景,能夠說field data cache是性能和穩定性的殺手。 對搜索結果作排序或者聚合操做,須要將倒排索引裏的數據進行解析,按列構形成docid->value的形式纔可以作後續快速計算。 對於數據量很大的索引,這個構造過程會很是耗費時間,所以ES 2.0之前的版本會將構造好的數據緩存起來,提高性能。可是因爲heap空間有限,當遇到用戶對海量數據作計算的時候,就很容易致使heap吃緊,集羣頻繁GC,根本沒法完成計算過程。 ES2.0之後,正式默認啓用Doc Values特性(1.x須要手動更改mapping開啓),將field data在indexing time構建在磁盤上,通過一系列優化,能夠達到比以前採用field data cache機制更好的性能。所以須要限制對field data cache的使用,最好是徹底不用,能夠極大釋放heap壓力。 須要注意的是,不少同窗已經升級到ES2.0,或者1.0裏已經設置mapping啓用了doc values,在kibana裏仍然會遇到問題。 這裏一個陷阱就在於kibana的table panel能夠對全部字段排序。 設想若是有一個字段是analyzed過的,而用戶去點擊對應字段的排序表頭是什麼後果? 一來排序的結果並非用戶想要的,排序的對象實際是詞典; 二來analyzed過的字段沒法利用doc values,須要裝載到field data cache,數據量很大的狀況下可能集羣就在忙着GC或者根本出不來結果。
Bulk Queue
通常來講,Bulk queue不會消耗不少的heap,可是見過一些用戶爲了提升bulk的速度,客戶端設置了很大的併發量,而且將bulk Queue設置到難以想象的大,好比好幾千。 Bulk Queue是作什麼用的?當全部的bulk thread都在忙,沒法響應新的bulk request的時候,將request在內存裏排列起來,而後慢慢清掉。 這在應對短暫的請求爆發的時候有用,可是若是集羣自己索引速度一直跟不上,設置的好幾千的queue都滿了會是什麼情況呢? 取決於一個bulk的數據量大小,乘上queue的大小,heap頗有可能就不夠用,內存溢出了。通常來講官方默認的thread pool設置已經能很好的工做了,建議不要隨意去「調優」相關的設置,不少時候都是拔苗助長的效果。
Indexing Buffer
Indexing Buffer是用來緩存新數據,當其滿了或者refresh/flush interval到了,就會以segment file的形式寫入到磁盤。 這個參數的默認值是10% heap size。根據經驗,這個默認值也可以很好的工做,應對很大的索引吞吐量。 但有些用戶認爲這個buffer越大吞吐量越高,所以見過有用戶將其設置爲40%的。到了極端的狀況,寫入速度很高的時候,40%都被佔用,致使OOM。
Cluster State Buffer
ES被設計成每一個node均可以響應用戶的api請求,所以每一個node的內存裏都包含有一份集羣狀態的拷貝。這個cluster state包含諸如集羣有多少個node,多少個index,每一個index的mapping是什麼?有少shard,每一個shard的分配狀況等等 (ES有各種stats api獲取這類數據)。 在一個規模很大的集羣,這個狀態信息可能會很是大的,耗用的內存空間就不可忽視了。而且在ES2.0以前的版本,state的更新是由master node作完之後全量散播到其餘結點的。 頻繁的狀態更新就能夠給heap帶來很大的壓力。 在超大規模集羣的狀況下,能夠考慮分集羣並經過tribe node鏈接作到對用戶api的透明,這樣能夠保證每一個集羣裏的state信息不會膨脹得過大。
超大搜索聚合結果集的fetch
ES是分佈式搜索引擎,搜索和聚合計算除了在各個data node並行計算之外,還須要將結果返回給彙總節點進行彙總和排序後再返回。不管是搜索,仍是聚合,若是返回結果的size設置過大,都會給heap形成很大的壓力,特別是數據匯聚節點。超大的size多數狀況下都是用戶用例不對,好比原本是想計算cardinality,卻用了terms aggregation + size:0這樣的方式; 對大結果集作深度分頁;一次性拉取全量數據等等。
對高cardinality字段作terms aggregation
所謂高cardinality,就是該字段的惟一值比較多。 好比client ip,可能存在上千萬甚至上億的不一樣值。 對這種類型的字段作terms aggregation時,須要在內存裏生成海量的分桶,內存需求會很是高。若是內部再嵌套有其餘聚合,狀況會更糟糕。 在作日誌聚合分析時,一個典型的能夠引發性能問題的場景,就是對帶有參數的url字段作terms aggregation。 對於訪問量大的網站,帶有參數的url字段cardinality可能會到數億,作一次terms aggregation內存開銷巨大,然而對帶有參數的url字段作聚合一般沒有什麼意義。 對於這類問題,能夠額外索引一個url_stem字段,這個字段索引剝離掉參數部分的url。能夠極大下降內存消耗,提升聚合速度。
小結:緩存