限制內(nèi)存使用
通常為了讓聚合(或者任何需要訪問字段值的請(qǐng)求)能夠快點(diǎn)厘托,訪問fielddata一定會(huì)快點(diǎn), 這就是為什么加載到內(nèi)存的原因。但是加載太多的數(shù)據(jù)到內(nèi)存會(huì)導(dǎo)致垃圾回收(gc)緩慢, 因?yàn)镴VM試著發(fā)現(xiàn)堆里面的額外空間,甚至導(dǎo)致OutOfMemory異常拓诸。
最讓你吃驚的是,你會(huì)發(fā)現(xiàn)Elaticsearch不是只把符合你的查詢的值加載到fielddata. 而是把index里的所document都加載到內(nèi)存麻昼,甚至是不同的?_type?的document恰响。
邏輯是這樣的,如果你在這個(gè)查詢需要訪問documents X涌献,Y和Z, 你可能在下一次查詢就需要訪問別documents首有。而一次把所有的值都加載并保存在內(nèi)存?燕垃, 比每次查詢都去掃描倒排索引要更方便。
JVM堆是一個(gè)有限制的資源需要聰明的使用井联。有許多現(xiàn)成的機(jī)制去限制fielddata對(duì)堆內(nèi)存使用的影響卜壕。這些限制非常重要,因?yàn)闉E用堆將會(huì)導(dǎo)致節(jié)點(diǎn)的不穩(wěn)定(多虧緩慢的垃圾回收)或者甚至節(jié)點(diǎn)死亡(因?yàn)镺utOfMemory異常)烙常;但是垃圾回收時(shí)間過長(zhǎng)轴捎,在垃圾回收期間鹤盒,ES節(jié)點(diǎn)的性能就會(huì)大打折扣,查詢就會(huì)非常緩慢侦副,直到最后超時(shí)侦锯。
如何設(shè)置堆大小
對(duì)于環(huán)境變量?$ES_HEAP_SIZE?在設(shè)置Elasticsearch堆大小的時(shí)候有2個(gè)法則可以運(yùn)用:
不超過RAM的50%
Lucene很好的利用了文件系統(tǒng)cache,文件系統(tǒng)cache是由內(nèi)核管理的秦驯。如果沒有足夠的文件系統(tǒng)cache空間尺碰,性能就會(huì)變差。
不超過32G
如果堆小于32GB译隘,JVM能夠使用壓縮的指針亲桥,這會(huì)節(jié)省許多內(nèi)存:每個(gè)指針就會(huì)使用4字節(jié)而不是8字節(jié)。
把對(duì)內(nèi)存從32GB增加到34GB將意味著你將有更少的內(nèi)存可用固耘,因?yàn)樗械闹羔樥加昧穗p倍的空間题篷。同樣,更大的堆厅目,垃圾回收變得代價(jià)更大并且可能導(dǎo)致節(jié)點(diǎn)不穩(wěn)定番枚;這個(gè)限制主要是大內(nèi)存對(duì)fielddata影響比較大。
Fielddata大小
參數(shù)?indices.fielddata.cache.size?控制有多少堆內(nèi)存是分配給fielddata璧瞬。當(dāng)你執(zhí)行一個(gè)查詢需要訪問新的字段值的時(shí)候户辫,將會(huì)把值加載到內(nèi)存,然后試著把它們加入到fielddata嗤锉。如果結(jié)果的fielddata大小超過指定的大小?渔欢,為了騰出空間,別的值就會(huì)被驅(qū)逐出去瘟忱。
默認(rèn)情況下奥额,這個(gè)參數(shù)設(shè)置的是無限制?—?Elasticsearch將永遠(yuǎn)不會(huì)把數(shù)據(jù)從fielddata里替換出去。
這個(gè)默認(rèn)值是故意選擇的:fielddata不是臨時(shí)的cache访诱。它是一個(gè)在內(nèi)存里為了快速執(zhí)行必須能被訪問的數(shù)據(jù)結(jié)構(gòu)垫挨,而且構(gòu)建它代價(jià)非常昂貴。如果你每個(gè)請(qǐng)求都要重新加載數(shù)據(jù)触菜,性能就會(huì)很差九榔。
一個(gè)有限的大小強(qiáng)迫數(shù)據(jù)結(jié)構(gòu)去替換數(shù)據(jù)。我們將看看什么時(shí)候去設(shè)置下面的值涡相,首先讓我們看一個(gè)警告:
【warning】
這個(gè)設(shè)置是一個(gè)保護(hù)措施哲泊,而不是一個(gè)內(nèi)存不足的解決方案
如果你沒有足夠的內(nèi)存區(qū)保存你的fielddata到內(nèi)存里,Elasticsearch將會(huì)經(jīng)常性的從磁盤重新加載數(shù)據(jù)催蝗,并且驅(qū)逐別的數(shù)據(jù)區(qū)騰出空間切威。這種數(shù)據(jù)的驅(qū)逐會(huì)導(dǎo)致嚴(yán)重的磁盤I/O,并且在內(nèi)存里產(chǎn)生大量的垃圾丙号,這個(gè)會(huì)在后面被垃圾回收先朦。
假設(shè)你在索引日志缰冤,每天使用給一個(gè)新的索引。通常情況下你只會(huì)對(duì)過去1天或者2天的數(shù)據(jù)感興趣喳魏。即使你把老的索引數(shù)據(jù)保留著棉浸,你也很少查詢它們。盡管如此截酷,使用默認(rèn)的設(shè)置涮拗, 來自老索引的fielddata也不會(huì)被清除出去!fielddata會(huì)一直增長(zhǎng)直到它觸發(fā)fielddata circuit breaker --參考斷路器--它將阻止你繼續(xù)加載fielddata迂苛。
在那個(gè)時(shí)候你被卡住了三热。即使你仍然能夠執(zhí)行訪問老的索引里的fielddata的查詢, 你再也不能加載任何新的值了三幻。相反就漾,我們應(yīng)該把老的值清除出去給新的值騰出空間。
為了防止這種情景念搬,通過在config/elasticsearch.yml文件里加上如下的配置給fielddata 設(shè)置一個(gè)上限:
indices.fielddata.cache.size: ?40%
當(dāng)然可以設(shè)置成堆大小的百分比抑堡,也可以是一個(gè)具體的值,比如 8gb朗徊;通過適當(dāng)?shù)脑O(shè)置這個(gè)值首妖,最近被訪問的fielddata將被清除出去,給新加載的數(shù)據(jù)騰出空間爷恳。
在網(wǎng)上你可能會(huì)看到另外一個(gè)設(shè)置參數(shù):?indices.fielddata.cache.expire?有缆。千萬不要使用這個(gè)設(shè)置!這個(gè)設(shè)置高版本已經(jīng)廢棄温亲。
這個(gè)設(shè)置告訴Elasticsearch把比過期時(shí)間老的數(shù)據(jù)從fielddata里驅(qū)逐出去棚壁,而不管這個(gè)值是否被用到。
這對(duì)性能是非痴恍椋可怕的?袖外。驅(qū)逐數(shù)據(jù)是有代價(jià)的,并且這個(gè)有目的的高效的安排驅(qū)逐數(shù)據(jù)并沒有任何真正的收獲魂务。
沒有任何理由去使用這個(gè)設(shè)置曼验;我們一點(diǎn)也不能從理論上制造一個(gè)假設(shè)的有用的情景。現(xiàn)階段存 在只是為了向后兼容粘姜。我們?cè)谶@個(gè)書里提到這個(gè)設(shè)置是因?yàn)檫@個(gè)設(shè)置曾經(jīng)在網(wǎng)絡(luò)上的各種文章里 被作為一個(gè) ``性能小竅門'' 被推薦過鬓照。
記住永遠(yuǎn)不要使用它,就ok相艇!
監(jiān)控fielddata
監(jiān)控fielddata使用了多少內(nèi)存以及是否有數(shù)據(jù)被驅(qū)逐是非常重要的。大量的數(shù)據(jù)被驅(qū)逐會(huì)導(dǎo)致嚴(yán)重的資源問題以及不好的性能纯陨。
Fielddata使用可以通過下面的方式來監(jiān)控:
對(duì)于單個(gè)索引使用 {ref}indices-stats.html[indices-stats?API]:
GET /_stats/fielddata?fields=*
對(duì)于單個(gè)節(jié)點(diǎn)使用 {ref}cluster-nodes-stats.html[nodes-stats?API]:
GET /_nodes/stats/indices/fielddata?fields=*
或者甚至單個(gè)節(jié)點(diǎn)單個(gè)索引
GET /_nodes/stats/indices/fielddata?level=indices&fields=*
通過設(shè)置??fields=*?內(nèi)存使用按照每個(gè)字段分解了.
斷路器(breaker)
聰明的讀者可能已經(jīng)注意到fielddata大小設(shè)置的一個(gè)問題坛芽。fielddata的大小是在數(shù)據(jù)被加載之后才校驗(yàn)的留储。如果一個(gè)查詢嘗試加載到fielddata的數(shù)據(jù)比可用的內(nèi)存大會(huì)發(fā)生什么情況?答案是不客觀的:你將會(huì)獲得一個(gè)OutOfMemory異常咙轩。
Elasticsearch包含了一個(gè)?fielddata斷路器?获讳,這個(gè)就是設(shè)計(jì)來處理這種情況的。斷路器通過檢查涉及的字段(它們的類型活喊,基數(shù)丐膝,大小等等)來估計(jì)查詢需要的內(nèi)存。然后檢查加 載需要的fielddata會(huì)不會(huì)導(dǎo)致總的fielddata大小超過設(shè)置的堆的百分比钾菊。
如果估計(jì)的查詢大小超過限制帅矗,斷路器就會(huì)觸發(fā)并且查詢會(huì)被拋棄返回一個(gè)異常。這個(gè)發(fā)生在數(shù)據(jù)被加載之前煞烫,這就意味著你不會(huì)遇到OutOfMemory異常浑此。
Elasticsearch擁有一系列的斷路器,所有的這些都是用來保證內(nèi)存限制不會(huì)被突破:
indices.breaker.fielddata.limit
這個(gè)?fielddata?斷路器限制fielddata的大小為堆大小的60%滞详,默認(rèn)情況下凛俱。
indices.breaker.request.limit
這個(gè)?request?斷路器估算完成查詢的其他部分要求的結(jié)構(gòu)的大小,比如創(chuàng)建一個(gè)聚集通料饥, 以及限制它們到堆大小的40%蒲犬,默認(rèn)情況下。
indices.breaker.total.limit
這個(gè)total斷路器封裝了?request?和?fielddata?斷路器去確保默認(rèn)情況下這2個(gè) 使用的總內(nèi)存不超過堆大小的70%岸啡。
斷路器限制可以通過文件?config/elasticsearch.yml?指定原叮,也可以在集群上動(dòng)態(tài)更新:
PUT /_cluster/settings
{
"persistent" : {
"indices.breaker.fielddata.limit" : 40% (1)
}
}
這個(gè)限制設(shè)置的是堆的百分比。
最好把斷路器設(shè)置成一個(gè)相對(duì)保守的值凰狞。記住fielddata需要和堆共享?request?斷路器篇裁, 索引內(nèi)存緩沖區(qū),過濾器緩存赡若,打開的索引的Lucene數(shù)據(jù)結(jié)構(gòu)达布,以及各種各樣別的臨時(shí)數(shù)據(jù) 結(jié)構(gòu)。所以默認(rèn)為相對(duì)保守的60%逾冬。過分樂觀的設(shè)置可能會(huì)導(dǎo)致潛在的OOM異常黍聂,從而導(dǎo)致整 個(gè)節(jié)點(diǎn)掛掉。
從另一方面來說身腻,一個(gè)過分保守的值將會(huì)簡(jiǎn)單的返回一個(gè)查詢異常产还,這個(gè)異常會(huì)被應(yīng)用處理。 異赤痔耍總比掛掉好脐区。這些異常也會(huì)促使你重新評(píng)估你的查詢:為什么單個(gè)的查詢需要超過60%的 堆空間。
斷路器和Fielddata大小
在?Fielddata大小部分我們談到了要給fielddata大小增加一個(gè)限制去保證老的不使用 的fielddata被驅(qū)逐出去她按。indices.fielddata.cache.size?和?indices.breaker.fielddata.limit?的關(guān)系是非常重要的牛隅。如果斷路器限制比緩沖區(qū)大小要小炕柔,就會(huì)沒有數(shù)據(jù)會(huì)被驅(qū)逐。為了能夠 讓它正確的工作媒佣,斷路器限制必須比緩沖區(qū)大小要大匕累。
我們注意到斷路器是和總共的堆大小對(duì)比查詢大小,而不是和真正已經(jīng)使用的堆內(nèi)存區(qū)比較默伍。 這樣做是有一系列技術(shù)原因的(比如欢嘿,堆可能看起來是滿的,但是實(shí)際上可能正在等待垃圾 回收也糊,這個(gè)很難準(zhǔn)確的估算)炼蹦。但是作為終端用戶,這意味著設(shè)置必須是保守的显设,因?yàn)樗?和整個(gè)堆大小比較框弛,而不是空閑的堆比較。
Elasticsearch權(quán)威指南筆記捕捂。