1. 程式人生 > >Elasticsearch聚合限制記憶體使用

Elasticsearch聚合限制記憶體使用

限制記憶體使用

通常為了讓聚合(或者任何需要訪問欄位值的請求)能夠快點,訪問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 的關係是非常重要的。如果斷路器限制比緩衝區大小要小,就會沒有資料會被驅逐。為了能夠 讓它正確的工作,斷路器限制必須比緩衝區大小要大。

我們注意到斷路器是和總共的堆大小對比查詢大小,而不是和真正已經使用的堆記憶體區比較。 這樣做是有一系列技術原因的(比如,堆可能看起來是滿的,但是實際上可能正在等待垃圾 回收,這個很難準確的估算)。但是作為終端使用者,這意味著設定必須是保守的,因為它是 和整個堆大小比較,而不是空閒的堆比較。

原文地址:http://www.sohu.com/a/114404361_465922