Elasticsearch内存使用分析

UPATE: 内容已更新到Elasticsearch v5.6

内存对Elasticsearch而言,非常重要,因为它为了提升性能使用了大量的in-memory数据结构。

官网推荐给Elasticsearch分配的内存不能超过32GB(小于32GB时会启用compressed oops,节省很多内存)
并且还必须是小于物理内存的50%,以便为Lucene利用Cached Memory提供更多的剩余内存。

Elasticsearch内部是如何使用这些内存的呢?下面这张图说明了Elasticsearch和Lucene对内存的使用情况。

  • Lucene对内存的使用
    Elasticsearch是基于Lucene实现。Lucene的segments(包括用于全文检索的inverted index以及用于聚合的doc values)都存储在单个文件中,这些文件都是不可变的,被经常访问的segments会常驻在OS的内存中,从而提升Lucene的性能。

Lucene使用off heap,官方建议至少预留50%的物理内存给Luncene。

  • Node Query Cache
    每个节点都有一个Node Query Cache,被所有shards共享。它负责缓存使用了filter的query结果,因为filter query的结果要么是yes要么是no,不涉及到scores的计算,非常使用于Cache的场景。

集群中的每个data节点必须配置
默认10%,也可以设置为绝对值,比如512mb

1
2
3
Node Query Cache的默认大小:
indices.queries.cache.size:10%
index.queries.cache.enabled:true
  • Indexing Buffer
    用于存储最近被索引的文档,所有shards共享。当该buffer满了之后,buffer里面的文档会被写入一个segment。
1
2
3
4
Indexing Buffer的默认大小:
indices.memory.index_buffer_size:10%
indices.memory.min_index_buffer_size:48mb
indices.memory.max_index_buffer_size:unbounded
  • Shard Request Cache
    只有reqeust size是0的才会被cache,比如aggregations、counts和suggestions。

不建议将它用于更新频繁的index,因为shard被更新时,该缓存会自动失效

1
2
Shard Request Cache的默认大小:
indices.requests.cache.size:1%
  • Field Data Cache
    在analyzed字符串上对field进行聚合计算时,Elastisearch会加载该field的所有值到内存中,这些值缓存在Field Data Cache里面。
    所以Fielddata是懒加载,并且是在query过程中生成的。
    indices.fielddata.cache.size控制了分配给fielddata的heap大小。它的默认值是unbounded,这么设计的原因是fielddata不是临时性的cache,它能够极大地提升性能,而且构建fielddata又比较耗时的操作,所以需要一直cache。
    如果没有足够的内存保存fielddata时,Elastisearch会不断地从磁盘加载数据到内存,并剔除掉旧的内存数据。剔除操作会造成严重的磁盘I/O,并且引发大量的GC,会严重影响Elastisearch的性能。
1
2
Field Data Cache的默认大小:
indices.fielddata.cache.size:unbounded

如果不在analyzed string fields上使用聚合,就不会产生Field Data Cache,也就不会使用大量的内存,所以可以考虑分配较小的heap给Elasticsearch。因为heap越小意味着Elasticsearch的GC会比较快,并且预留给Lucene的内存也会比较大。

  • 查看内存使用情况

    • 查看segments使用的内存
      通过查看cat segments查看index的segments使用内存的情况

      1
      GET /_cat/segments?v
    • 查看Node Query Cache、Indexing Buffer和Field Data Cache使用的内存
      通过cat nodes可以查看他们使用内存的情况

      1
      2
      3
      4
      GET /_cat/nodes?v&h=id,ip,port,v,master,name,heap.current,heap.percent,heap.max,
      ram.current,ram.percent,ram.max,
      fielddata.memory_size,fielddata.evictions,query_cache.memory_size,query_cache.evictions,
      request_cache.memory_size,request_cache.evictions,request_cache.hit_count,request_cache.miss_count
  • 谨慎对待unbounded的内存
    unbounded内存是不可控的,会占用大量的heap(Field Data Cache)或者off heap(segments),从而会导致Elasticsearch OOM
    或者因segments占用大量内存导致swap。
    segments和Field Data Cache都属于这类unbounded。

    • segments
      segments会长期占用内存,其初衷就是利用OS的cache提升性能。只有在Merge之后,才会释放掉标记为Delete的segments,释放部分内存。

    • Field Data Cache
      默认情况下Fielddata会不断占用内存,直到它触发了fielddata circuit breaker
      fielddata circuit breaker会根据查询条件评估这次查询会使用多少内存,从而计算加载这部分内存之后,Field Data Cache所占用的内存是否会超过indices.breaker.fielddata.limit。如果超过这个值,就会触发fielddata circuit breaker,abort这次查询并且抛出异常,防止OOM。

      1
      indices.breaker.fielddata.limit:60% (默认heap的60%)

      如果设置了indices.fielddata.cache.size,当达到size时,cache会剔除旧的fielddata。

    indices.breaker.fielddata.limit 必须大于 indices.fielddata.cache.size,否则只会触发fielddata circuit breaker,而不会剔除旧的fielddata。