限制內存使用
通常為了讓聚合(或者任何需要訪問字段值的請求)能夠快點,訪問fielddata一定會快點, 這就是為什么加載到內存的原因。但是加載太多的數據到內存會導致垃圾回收(gc)緩慢, 因為JVM試着發現堆里面的額外空間,甚至導致OutOfMemory異常。
最讓你吃驚的是,你會發現Elaticsearch不是只把符合你的查詢的值加載到fielddata. 而是把index里的所document都加載到內存,甚至是不同的 _type 的document。
邏輯是這樣的,如果你在這個查詢需要訪問documents X,Y和Z, 你可能在下一次查詢就需要訪問別documents。而一次把所有的值都加載並保存在內存 , 比每次查詢都去掃描倒排索引要更方便。
JVM堆是一個有限制的資源需要聰明的使用。有許多現成的機制去限制fielddata對堆內存使用的影響。這些限制非常重要,因為濫用堆將會導致節點的不穩定(多虧緩慢的垃圾回收)或者甚至節點死亡(因為OutOfMemory異常);但是垃圾回收時間過長,在垃圾回收期間,ES節點的性能就會大打折扣,查詢就會非常緩慢,直到最后超時。
如何設置堆大小
對於環境變量 $ES_HEAP_SIZE 在設置Elasticsearch堆大小的時候有2個法則可以運用:
-
不超過RAM的50%
Lucene很好的利用了文件系統cache,文件系統cache是由內核管理的。如果沒有足夠的文件系統cache空間,性能就會變差。
-
不超過32G
如果堆小於32GB,JVM能夠使用壓縮的指針,這會節省許多內存:每個指針就會使用4字節而不是8字節。
把對內存從32GB增加到34GB將意味着你將有更少的內存可用,因為所有的指針占用了雙倍的空間。同樣,更大的堆,垃圾回收變得代價更大並且可能導致節點不穩定;這個限制主要是大內存對fielddata影響比較大。
Fielddata大小
參數 indices.fielddata.cache.size 控制有多少堆內存是分配給fielddata。當你執行一個查詢需要訪問新的字段值的時候,將會把值加載到內存,然后試着把它們加入到fielddata。如果結果的fielddata大小超過指定的大小 ,為了騰出空間,別的值就會被驅逐出去。
默認情況下,這個參數設置的是無限制 — Elasticsearch將永遠不會把數據從fielddata里替換出去。
這個默認值是故意選擇的:fielddata不是臨時的cache。它是一個在內存里為了快速執行必須能被訪問的數據結構,而且構建它代價非常昂貴。如果你每個請求都要重新加載數據,性能就會很差。
一個有限的大小強迫數據結構去替換數據。我們將看看什么時候去設置下面的值,首先讓我們看一個警告:
【warning】
這個設置是一個保護措施,而不是一個內存不足的解決方案
如果你沒有足夠的內存區保存你的fielddata到內存里,Elasticsearch將會經常性的從磁盤重新加載數據,並且驅逐別的數據區騰出空間。這種數據的驅逐會導致嚴重的磁盤I/O,並且在內存里產生大量的垃圾,這個會在后面被垃圾回收。
假設你在索引日志,每天使用給一個新的索引。通常情況下你只會對過去1天或者2天的數據感興趣。即使你把老的索引數據保留着,你也很少查詢它們。盡管如此,使用默認的設置, 來自老索引的fielddata也不會被清除出去!fielddata會一直增長直到它觸發fielddata circuit breaker --參考斷路器--它將阻止你繼續加載fielddata。
在那個時候你被卡住了。即使你仍然能夠執行訪問老的索引里的fielddata的查詢, 你再也不能加載任何新的值了。相反,我們應該把老的值清除出去給新的值騰出空間。
為了防止這種情景,通過在 config/elasticsearch.yml 文件里加上如下的配置給fielddata 設置一個上限:
indices.fielddata.cache.size: 40%
當然可以設置成堆大小的百分比,也可以是一個具體的值,比如 8gb;通過適當的設置這個值,最近被訪問的fielddata將被清除出去,給新加載的數據騰出空間。
在網上你可能會看到另外一個設置參數: indices.fielddata.cache.expire 。千萬不要使用這個設置!這個設置高版本已經廢棄。
這個設置告訴Elasticsearch把比過期時間老的數據從fielddata里驅逐出去,而不管這個值是否被用到。
這對性能是非常可怕的 。驅逐數據是有代價的,並且這個有目的的高效的安排驅逐數據並沒有任何真正的收獲。
沒有任何理由去使用這個設置;我們一點也不能從理論上制造一個假設的有用的情景。現階段存 在只是為了向后兼容。我們在這個書里提到這個設置是因為這個設置曾經在網絡上的各種文章里 被作為一個 ``性能小竅門'' 被推薦過。
記住永遠不要使用它,就ok!
監控fielddata
監控fielddata使用了多少內存以及是否有數據被驅逐是非常重要的。大量的數據被驅逐會導致嚴重的資源問題以及不好的性能。
Fielddata使用可以通過下面的方式來監控:
-
對於單個索引使用 {ref}indices-stats.html[indices-stats API]:
GET /_stats/fielddata?fields=*
-
對於單個節點使用 {ref}cluster-nodes-stats.html[nodes-stats API]:
GET /_nodes/stats/indices/fielddata?fields=*
-
或者甚至單個節點單個索引
GET /_nodes/stats/indices/fielddata?level=indices&fields=*
通過設置 ?fields=* 內存使用按照每個字段分解了.
斷路器(breaker)
聰明的讀者可能已經注意到fielddata大小設置的一個問題。fielddata的大小是在數據被加載之后才校驗的。如果一個查詢嘗試加載到fielddata的數據比可用的內存大會發生什么情況?答案是不客觀的:你將會獲得一個OutOfMemory異常。
Elasticsearch包含了一個 fielddata斷路器 ,這個就是設計來處理這種情況的。斷路器通過檢查涉及的字段(它們的類型,基數,大小等等)來估計查詢需要的內存。然后檢查加 載需要的fielddata會不會導致總的fielddata大小超過設置的堆的百分比。
如果估計的查詢大小超過限制,斷路器就會觸發並且查詢會被拋棄返回一個異常。這個發生在數據被加載之前,這就意味着你不會遇到OutOfMemory異常。
Elasticsearch擁有一系列的斷路器,所有的這些都是用來保證內存限制不會被突破:
indices.breaker.fielddata.limit
這個 fielddata 斷路器限制fielddata的大小為堆大小的60%,默認情況下。
indices.breaker.request.limit
這個 request 斷路器估算完成查詢的其他部分要求的結構的大小,比如創建一個聚集通, 以及限制它們到堆大小的40%,默認情況下。
indices.breaker.total.limit
這個total斷路器封裝了 request 和 fielddata 斷路器去確保默認情況下這2個 使用的總內存不超過堆大小的70%。
斷路器限制可以通過文件 config/elasticsearch.yml 指定,也可以在集群上動態更新:
PUT /_cluster/settings { "persistent" : { "indices.breaker.fielddata.limit" : 40% (1) } }
這個限制設置的是堆的百分比。
最好把斷路器設置成一個相對保守的值。記住fielddata需要和堆共享 request 斷路器, 索引內存緩沖區,過濾器緩存,打開的索引的Lucene數據結構,以及各種各樣別的臨時數據 結構。所以默認為相對保守的60%。過分樂觀的設置可能會導致潛在的OOM異常,從而導致整 個節點掛掉。
從另一方面來說,一個過分保守的值將會簡單的返回一個查詢異常,這個異常會被應用處理。 異常總比掛掉好。這些異常也會促使你重新評估你的查詢:為什么單個的查詢需要超過60%的 堆空間。
斷路器和Fielddata大小
在 Fielddata大小部分我們談到了要給fielddata大小增加一個限制去保證老的不使用 的fielddata被驅逐出去。indices.fielddata.cache.size 和 indices.breaker.fielddata.limit 的關系是非常重要的。如果斷路器限制比緩沖區大小要小,就會沒有數據會被驅逐。為了能夠 讓它正確的工作,斷路器限制必須比緩沖區大小要大。
我們注意到斷路器是和總共的堆大小對比查詢大小,而不是和真正已經使用的堆內存區比較。 這樣做是有一系列技術原因的(比如,堆可能看起來是滿的,但是實際上可能正在等待垃圾 回收,這個很難准確的估算)。但是作為終端用戶,這意味着設置必須是保守的,因為它是 和整個堆大小比較,而不是空閑的堆比較。
from:http://mt.sohu.com/20160915/n468474691.shtml