Elasticesearch内存详解——2.可回收内存

社区Elasticsearch
1.segment

1.1 segment介绍

一个segment是一个完备的lucene倒排索引,而倒排索引是通过词典 (Term Dictionary)到文档列表(Postings List)的映射关系,快速做查询的。 由于词典的size会很大,全部装载到heap里不现实,因此Lucene为词典做了一层前缀索引(Term Index),这个索引在Lucene4.0以后采用的数据结构是FST (Finite State Transducer)。 这种数据结构占用空间很小,Lucene打开索引的时候将其全量装载到内存中,加快磁盘上词典查询速度的同时减少随机磁盘访问次数。

segments FST数据的缓存,为了加速查询,FST 永驻堆内内存,无法被 GC 回收。该部分内存无法设置大小,长期占用 50% ~ 70% 的堆内存,只能通过delete index,close index以及force-merge index释放内存。

解释下FST:

ES 底层存储采用 Lucene(搜索引擎),写入时会根据原始数据的内容,分词,然后生成倒排索引。查询时,先通过  查询倒排索引找到数据地址(DocID)),再读取原始数据(行存数据、列存数据)。但由于 Lucene 会为原始数据中的每个词都生成倒排索引,数据量较大。所以倒排索引对应的倒排表被存放在磁盘上。这样如果每次查询都直接读取磁盘上的倒排表,再查询目标关键词,会有很多次磁盘 IO,严重影响查询性能。为了解磁盘 IO 问题,Lucene 引入排索引的二级索引 FST [Finite State Transducer] 。原理上可以理解为前缀树,加速查询。

ES的data node存储数据并非只是耗费磁盘空间的,为了加速数据的访问,每个segment都有会一些索引数据驻留在heap里。因此segment越多,瓜分掉的heap也越多,并且这部分heap是无法被GC掉的! 理解这点对于监控和管理集群容量很重要,当一个node的segment memory占用过多的时候,就需要考虑删除、归档数据,或者扩容了。

picture.image

1.2 查看segment

  1. 查看一个索引所有segment的memory占用情况:

curl -X GET "http://127.0.0.1:9200/_cat/segments/index?v&h=shard,segment,size,size.menory"

picture.image

  1. 查看一个node上所有segment占用的memory总和:

curl -X GET "http://127.0.0.1:9200/_cat/nodes?h=name,hp,hm,rp,rm,qcm,rcm,fm,sm&v"

picture.image

1.3 segment合并

我们已经知道在elasticsearch中每个shard每隔1秒都会refresh一次,每次refresh都会生成一个新的segment,按照这个速度过不了多久segment的数量就会爆炸,所以存在太多的segment是一个大问题,因为每一个segment都会占用文件句柄,内存资源,cpu资源,更加重要的是每一个搜索请求都必须访问每一个segment,这就意味着存在的segment越多,搜索请求就会变的更慢。

那么elaticsearch是如何解决这个问题呢? 实际上elasticsearch有一个后台进程专门负责segment的合并,它会把小segments合并成更大的segments,然后反复这样。在合并segments的时候标记删除的document不会被合并到新的更大的segment里面,所有的过程都不需要我们干涉,es会自动在索引和搜索的过程中完成,合并的segment可以是磁盘上已经commit过的索引,也可以在内存中还未commit的segment:

(1)在索引时refresh进程每秒会创建一个新的segment并且打开它使得搜索可见

(2)merge进程会在后台选择一些小体积的segments,然后将其合并成一个更大的segment,这个过程不会打断当前的索引和搜索功能。

picture.image

(3)一旦merge完成,旧的segments就会被删除,流程如下:

3.1 新的segment会被flush到磁盘

3.2 然后会生成新的commit point文件,包含新的segment名称,并排除掉旧的segment和那些被合并过的小的segment

3.3 接着新的segment会被打开用于搜索

3.4 最后旧的segment会被删除掉

picture.image

至此原来标记伪删除的document都会被清理掉,如果不加控制,合并一个大的segment会消耗比较多的io和cpu资源,同时也会搜索性能造成影响,所以默认情况下es已经对合并线程做了资源限额以便于它不会搜索性能造成太大影响。

api如下:

PUT /_cluster/settings

{

   "persistent" : {

       "indices.store.throttle.max_bytes_per_sec" : "100mb"

   }

}

或者不限制:

PUT /_cluster/settings

{

   "transient" : {

       "indices.store.throttle.type" : "none"

   }

}

es的api也提供了我们外部发送命令来强制合并segment,这个命令就是optimize,它可以强制一个shard合并成指定数量的segment,这个参数是:max_num_segments ,一个索引它的segment数量越少,它的搜索性能就越高,通常会optimize成一个segment。需要注意的是optimize命令不要用在一个频繁更新的索引上面,针对频繁更新的索引es默认的合并进程就是最优的策略,optimize命令通常用在一个静态索引上,也就是说这份索引没有写入操作只有查询操作的时候是非常适合用optimize来优化的,比如说我们的一些日志索引,基本都是按天,周,或者月来索引的,只要过了今天,这周或这个月就基本没有写入操作了,这个时候我们就可以通过optimize命令,来强制合并每个shard上索引只有一个segment,这样查询性能就能大大提升,api如下:

POST /logstash-2014-10/_optimize?max_num_segments=1

注意,由外部发送的optimize命令是没有限制资源的,也就是你系统有多少IO资源就会使用多少IO资源,这样可能导致某一段时间内搜索没有任何响应,所以如果你计划要optimize一个超大的索引,你应该使用shard allocation功能将这份索引给移动到一个指定的node机器上,以确保合并操作不会影响其他的业务或者es本身的性能。

1.4 减少内存的办法

  • 删除不用的索引。delete index
  • 关闭索引(文件仍然存在于磁盘,只是释放掉内存),需要的时候可重新打开。close index
  • 定期对不再更新的索引做force merge
2. Indexing buffer

2.1 Indexing buffer(索引写入缓冲区)

索引写入缓冲区,用于存储新写入的文档,当其被填满时,缓冲区中的文档被写入磁盘中的 segments 中。节点上所有 shard 共享。这部分空间是可以通过GC被反复利用的。

索引写入缓冲区用于存储新建索引的文档。 填满时,缓冲区中的文档将写入磁盘上的segments。 它在节点上的所有分片之间划分。

2.2 indexing buffer配置

以下设置是静态的,必须在群集中的每个数据节点上进行配置:

indices.memory.index_buffer_size 接受百分比或字节大小的值。 它默认为10%,这意味着分配给一个节点的总堆栈的10%将用作所有分片共享的索引缓冲区大小。

indices.memory.min_index_buffer_size 如果将index_buffer_size指定为百分比,则可以使用此设置指定绝对最小值。 默认值为48mb。

ndices.memory.max_index_buffer_size 如果index_buffer_size被指定为百分比,则可以使用此设置来指定绝对最大值。 默认为限制

picture.image

2.3 indexing buffer注意事项

由于可以GC,有flush操作,不需要特殊的关注

Indexing Buffer是用来缓存新数据,当其满了或者refresh/flush interval到了,就会以segment file的形式写入到磁盘。 这个参数的默认值是10% heap size。根据经验,这个默认值也能够很好的工作,应对很大的索引吞吐量。 但有些用户认为这个buffer越大吞吐量越高,因此见过有用户将其设置为40%的。到了极端的情况,写入速度很高的时候,40%都被占用,导致OOM。

3.Node Query Cache

3.1 Node Query Cache介绍

node级别的filter过滤器结果缓存

Elasticsearch 集群中的每个节点包含一个 Node Query Cache,作用域是Node实例,由该节点的所有 shard 共享,Cache 采用 LRU 算法,Node Query Cache 只缓存 filter 部分耗时高的查询类型。

注:LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

3.2 什么情况下会产生NodeQueryCache

1)只有Filter下的子Query才能参与Cache。

2)不能参与Cache的Query有TermQuery/MatchAllDocsQuery/MatchNoDocsQuery/BooleanQuery/DisjunnctionMaxQuery。

3)MultiTermQuery/MultiTermQueryConstantScoreWrapper/TermInSetQuery/Point*Query的Query查询超过2次会被Cache,其它Query要5次。

4)默认每个段大于10000个doc或每个段的doc数大于总doc数的30%时才允许参与cache。

5)结果集比较大的Query在Cache时尽量增加使用周期以免频繁Cache构建DocIdset。

6)Segment被合并或者删除,那么也会清理掉对应的缓存。

  1. 内存无法被GC。

3.3 Cache相关配置

1)indices.queries.cache.size:为每个节点配置缓存的内存大小,默认是10%,支持两种格式,一种是百分数,占节点heap的百分比,另一种是精确的值,如512mb,这个参数是静态的配置后需要重启节点生效。

2)indices.queries.cache.count: 配置缓存的总数量。

3)indices.queries.cache.all_segments: 用于是否在所有 Segment上启用缓存,默认是false,对文档数小于10000或者小于整个索引Doc的30%的Segment进行缓存。

4)index.queries.cache.enabled:属于index级别的配置,用来控制是否启用缓存,默认是开启的。

3.4 查看当前queryCache

curl -X GET "http://127.0.0.1:9200/_stats/query_cache?pretty&human"

4.Shard Request Cach

4.1 简介

Shard Request Cache 简称 Request Cache,他是分片级别的查询缓存,每个分片有自己的缓存。该缓存采用 LRU 机制,缓存的 key 是整个客户端请求,缓存内容为单个分片的查询结果。如果一个客户端请求被数据节点缓存了,下次查询的时候可以直接从缓存获取,无需对分片进行查询。

结构:

final Key key =  new Key(cacheEntity, reader.getReaderCacheHelper().getKey(), cacheKey);

cacheEntity, 主要是 shard信息,代表该缓存是哪个 shard上的查询结果。

readerCacheKey, 主要用于区分不同的 IndexReader。

cacheKey, 主要是整个客户端请求的请求体(source)和请求参数(preference、indexRoutings、requestCache等)。由于客户端请求信息直接序列化为二进制作为缓存 key 的一部分,所以客户端请求的 json 顺序,聚合名称等变化都会导致 cache 无法命中。

缓存的 value比较简单,就是将查询结果序列化之后的二进制数据。

4.2 用途

Request Cache 的主要作用是对聚合的缓存,聚合过程是实时计算,通常会消耗很多资源,缓存对聚合来说意义重大。

只有客户端查询请求中 size=0的情况下才会被缓存,其他不被缓存的条件还包括 scroll、设置了 profile属性,查询类型不是 QUERY_THEN_FETCH,以及设置了 requestCache=false等。另外一些存在不确定性的查询例如:范围查询带有now,由于它是毫秒级别的,缓存下来没有意义,类似的还有在脚本查询中使用了 Math.random() 等函数的查询也不会进行缓存。

该缓存使用LRU淘汰策略,[内存]无法被GC。

1)默认被开启,ES6.71版本。

2)RequestCache作用域为Node,在Node中的Shard共享这个Cache空间。。

3)RequestCache 是以查询的DSL整个串为key的,修改一个字符和顺序都需要重新生成Cache。

4)缓存失效是索引的refresh操作,也可以设置失效时间。

5)缓存的默认大小是JVM堆内存的1%,可以通过手动设置。

首先,我们按照 汽车的颜色color来划分桶

picture.image

size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率

aggs:声明这是一个聚合查询,是aggregations的缩写

popular_colors:给这次聚合起一个名字,任意。

terms:划分桶的方式,这里是根据词条划分

field:划分桶的字段

4.3 设置

ES 默认情况下最多使用堆内存的 1% 用作 Request Cache,这是一个节点级别的配置:

indices.requests.cache.size

Request Cache 默认是开启的。你可以为某个索引动态启用或禁用缓存:

PUT /my-index/_settings
{ "index.requests.cache.enable": true }

或者在查询请求级别通过 request_cache 参数来控制本次查询是否使用 cache,他会覆盖索引级别的设置。

GET /my-index/_search?request_cache=true
0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论