对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。能够极大下降内存消耗,提升聚合速度。
小结:缓存