- Filter VS Query
- 尽量减少不需要的字段
- 查询优化经验
- GC调优
- 路由优化
- Cache的设置及使用
- 写调优
- elasticsearch.yml
- 已经索引好的文档会先存放在内存缓存中,等待被写到到段(segment)中。
- 缓存满的时候会触发段刷盘(吃i/o和cpu的操作)。默认最小缓存大小为48m,不太够,最大为堆内存的10%。
- 对于大量写入的场景也显得有点小
- 设置index、merge、bulk、search的线程数和队列数
- Search pool
- 这个参数慎用!强制修改cpu核数,以突破写线程数限制
- processors: 16
- Bulk pool
- thread_pool.bulk.size: 16
- Index pool
- thread_pool.index.size: 16
- 设置filedata cache大小
- filedata cache的使用场景是一些聚合操作(包括排序),构建filedata cache是个相对昂贵的操作。所以尽量能让他保留在内存中
- 设置节点之间的故障检测配置
- 设置段合并的线程数量
- 响应数据压缩功能
- 锁定elasticsearch内存
- 内存配置
Filter VS Query
尽可能使用过滤器上下文(Filter)替代查询上下文(Query)
- Query:此文档与此查询子句的匹配程度如何?
- Filter:此文档和查询子句匹配吗?
Elasticsearch 针对 Filter 查询只需要回答「是」或者「否」,不需要像 Query 查询一样计算相关性分数,同时Filter结果可以缓存。
尽量减少不需要的字段
如果 Elasticsearch 用于业务搜索服务,一些不需要用于搜索的字段最好不存到 ES 中,这样即节省空间,同时在相同的数据量下,也能提高搜索性能。
避免使用动态值作字段,动态递增的 mapping,会导致集群崩溃;同样,也需要控制字段的数量,业务中不使用的字段,就不要索引。控制索引的字段数量、mapping 深度、索引字段的类型,对于 ES 的性能优化是重中之重。
以下是 ES 关于字段数、mapping 深度的一些默认设置:
index.mapping.nested_objects.limit: 10000
index.mapping.total_fields.limit: 1000
index.mapping.depth.limit: 20
查询优化经验
- query_string 或 multi_match的查询字段越多, 查询越慢。可以在mapping阶段,利用copy_to属性将多字段的值索引到一个新字段,multi_match时,用新的字段查询。
- 日期字段的查询, 尤其是用now 的查询实际上是不存在缓存的,因此, 可以从业务的角度来考虑是否一定要用now, 毕竟利用query cache 是能够大大提高查询效率的。
- 查询结果集的大小不能随意设置成大得离谱的值, 如query.setSize不能设置成 Integer.MAX_VALUE, 因为ES内部需要建立一个数据结构来放指定大小的结果集数据。
- 避免层级过深的聚合查询, 层级过深的aggregation , 会导致内存、CPU消耗,建议在服务层通过程序来组装业务,也可以通过pipeline的方式来优化。
- 复用预索引数据方式来提高AGG性能:
如通过 terms aggregations 替代 range aggregations, 如要根据年龄来分组,分组目标是: 少年(14岁以下) 青年(14-28) 中年(29-50) 老年(51以上), 可以在索引的时候设置一个age_group字段,预先将数据进行分类。从而不用按age来做range aggregations, 通过age_group字段就可以了。
GC调优
老的版本中官方文档中推荐默认设置为:Concurrent-Mark and Sweep(CMS),给的理由是当时G1 还有很多 BUG。
原因是:已知JDK 8附带的HotSpot JVM的早期版本存在一些问题,当启用G1GC收集器时,这些问题可能导致索引损坏。受影响的版本早于JDK 8u40随附的HotSpot版本。来源于官方说明
实际上如果你使用的JDK8较高版本,或者JDK9+,我推荐你使用G1 GC; 因为我们目前的项目使用的就是G1 GC,运行效果良好,对Heap大对象优化尤为明显。修改jvm.options文件,将下面几行:
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
改为
-XX:+UseG1GC
-XX:MaxGCPauseMillis=50
其中 -XX:MaxGCPauseMillis是控制预期的最高GC时长,默认值为200ms,如果线上业务特性对于GC停顿非常敏感,可以适当设置低一些。但是 这个值如果设置过小,可能会带来比较高的cpu消耗。
G1对于集群正常运作的情况下减轻G1停顿对服务时延的影响还是很有效的,但是如果是你描述的GC导致集群卡死,那么很有可能换G1也无法根本上解决问题。 通常都是集群的数据模型或者Query需要优化。
路由优化
不带 routing 查询
在查询的时候因为不知道要查询的数据具体在哪个分片上,所以整个过程分为2个步骤:
- 分发:请求到达协调节点后,协调节点将查询请求分发到每个分片上。
聚合:协调节点搜集到每个分片上查询结果,再将查询的结果进行排序,之后给用户返回结果。
带 routing 查询
查询的时候,可以直接根据 routing 信息定位到某个分配查询,不需要查询所有的分配,经过协调节点排序。
向上面自定义的用户查询,如果 routing 设置为 userid 的话,就可以直接查询出数据来,效率提升很多。Cache的设置及使用
QueryCache: ES查询的时候,使用filter查询会使用query cache, 如果业务场景中的过滤查询比较多,建议将querycache设置大一些,以提高查询速度。
indices.queries.cache.size: 10%(默认),可设置成百分比,也可设置成具体值,如256mb。
当然也可以禁用查询缓存(默认是开启), 通过index.queries.cache.enabled:false设置。
- FieldDataCache: 在聚类或排序时,field data cache会使用频繁,因此,设置字段数据缓存的大小,在聚类或排序场景较多的情形下很有必要,可通过indices.fielddata.cache.size:30% 或具体值10GB来设置。但是如果场景或数据变更比较频繁,设置cache并不是好的做法,因为缓存加载的开销也是特别大的。
- ShardRequestCache: 查询请求发起后,每个分片会将结果返回给协调节点(Coordinating Node), 由协调节点将结果整合。 如果有需求,可以设置开启; 通过设置index.requests.cache.enable: true来开启。 不过,shard request cache只缓存hits.total, aggregations, suggestions类型的数据,并不会缓存hits的内容。也可以通过设置indices.requests.cache.size: 1%(默认)来控制缓存空间大小。
写调优
副本数置0
如果是集群首次灌入数据,可以将副本数设置为0,写入完毕再调整回去,这样副本分片只需要拷 贝,节省了索引过程。
PUT /my_temp_index/_settings
{
"number_of_replicas": 0
}
# 顺便先关闭refresh, 暂时搜不到
PUT /my_logs/_settings
{ "refresh_interval":-1 }
PUT /my_logs/_settings
{ "refresh_interval": "1s" }
自动生成doc ID
通过Elasticsearch写入流程可以看出,如果写入doc时如果外部指定了id,则Elasticsearch会先尝试 读取原来doc的版本号,以判断是否需要更新。这会涉及一次读取磁盘的操作,通过自动生成doc ID可 以避免这个环节。
合理设置mappings
- 将不需要建立索引的字段index属性设置为not_analyzed或no。对字段不分词,或者不索引,可以减少很多运算操作,降低CPU占用。
尤其是binary(二进制)类型,默认情况下占用CPU非常高,而这种类型进行分词通常没有什么意义。
- 减少字段内容长度,如果原始数据的大段内容无须全部建立索引,则可以尽量减少不必要的内容。
使用不同的分析器(analyzer),不同的分析器在索引过程中运算复杂度也有较大的差异
调整_source字段
source 字段用于存储 doc 原始数据,对于部分不需要存储的字段,可以通过 includes excludes过滤,或者将source禁用, 一般用于索引和数据分离,这样可以降低 I/O 的压力,不过实际场景中大多不 会禁用_source。
对analyzed的字段禁用norms
Norms用于在搜索时计算doc的评分,如果不需要评分,则可以将其禁用:
"title": {
"type": "string",
"norms": {
"enabled": false
}
调整索引的刷新间隔
该参数缺省是1s,强制ES每秒创建一个新segment,从而保证新写入的数据近实时的可见、可被搜索 到。比如该参数被调整为30s,降低了刷新的次数,把刷新操作消耗的系统资源释放出来给index操作使用。
PUT /my_index/_settings {
"index" : {
"refresh_interval": "30s"
}
}
这种方案以牺牲可见性的方式,提高了index操作的性能。 :::info 加载大量数据的时候可以将 refresh 次数临时关闭,即 index.refresh_interval 设置为-1,数据导入成功后再打开到正常模式,比如30s。
在加载大量数据时候可以暂时不用 refresh 和 repliccas,index.refresh_interval 设置为-1,index.number_of_replicas 设置为0 :::增加index buffer size
索引缓冲的设置可以控制多少内存分配给索引进程。这是一个全局配置,会应用于一个节点上所有不同的分片上
增大 index buffer size,参数为 indices. memory index_buffer_size(静态参数,需要设定在 elasticsearch ym中),默认为10%indices.memory.index_buffer_size: 10%
indices.memory.min_index_buffer_size: 48mb
indices.memory.index_buffer_size 接受一个百分比或者一个表示字节大小的值。默认是10%,意味着分配给节点的总内存的10%用来做索引缓冲的大小。这个数值被分到不同的分片(shards)上。如果设置的是百分比,还可以设置 min_index_buffer_size (默认 48mb)和 max_index_buffer_size(默认没有上限)。
translog写入优化
目标是降低 translog写磁盘的频率,从而提高写效率,但会降低容灾能力
index.translog.durability 设置为 async异步
- index.translog. sync_interval 设置需要的大小,比如120s,那么 translog会改为每120s写一次磁盘
- index.translog.flush_threshold_size 默认为512mb,即 translog超过该大小时会触发一次fush,那么调大该大小可以避免fush的发生
elasticsearch.yml
```scala已经索引好的文档会先存放在内存缓存中,等待被写到到段(segment)中。
缓存满的时候会触发段刷盘(吃i/o和cpu的操作)。默认最小缓存大小为48m,不太够,最大为堆内存的10%。
对于大量写入的场景也显得有点小
indices.memory.index_buffer_size: 20% indices.memory.min_index_buffer_size: 96mb
设置index、merge、bulk、search的线程数和队列数
Search pool
thread_pool.search.size: 5 thread_pool.search.queue_size: 100
这个参数慎用!强制修改cpu核数,以突破写线程数限制
processors: 16
Bulk pool
thread_pool.bulk.size: 16
thread_pool.bulk.queue_size: 300
Index pool
thread_pool.index.size: 16
thread_pool.index.queue_size: 300
设置filedata cache大小
filedata cache的使用场景是一些聚合操作(包括排序),构建filedata cache是个相对昂贵的操作。所以尽量能让他保留在内存中
indices.fielddata.cache.size: 40%
设置节点之间的故障检测配置
discovery.zen.fd.ping_timeout: 120s discovery.zen.fd.ping_retries: 6 discovery.zen.fd.ping_interval: 30s
索引优化配置:
```scala
PUT /_template/elk
{
"order": 6,
"template": "logstash-*", #这里配置模板匹配的Index名称
"settings": {
"number_of_replicas" : 0, #副本数为0,需要查询性能高可以设置为1
"number_of_shards" : 6, #分片数为6, 副本为1时可以设置成5
"refresh_interval": "30s",
"index.translog.durability": "async",
"index.translog.sync_interval": "30s"
}
}
设置段合并的线程数量
curl -XPUT 'your-es-host:9200/nginx_log-2018-03-20/_settings' -d '{
"index.merge.scheduler.max_thread_count" : 1
}'
段合并的计算量庞大,而且还要吃掉大量磁盘I/O。合并在后台定期操作,因为他们可能要很长时间才能完成,尤其是比较大的段
机械磁盘在并发I/O支持方面比较差,所以我们需要降低每个索引并发访问磁盘的线程数。这个设置允许max_thread_count + 2个线程同时进行磁盘操作,也就是设置为1允许三个线程
响应数据压缩功能
ES使得修改HTTP压缩非常容易,仅仅在 elasticsearch.yml文件中提供下面属性即可:
http.compression: true
http.compression_level: 1
TCP压缩使用下面属性:
transport.compress: true
锁定elasticsearch内存
在Linux/Unix系统上使用mlockall,尝试将进程地址空间锁定到RAM中,以防止任何Elasticsearch内存被换出。可以在config/elasticsearch.yml文件中增加此配置:
bootstrap.memory_lock: true
在启动项目后,通过此API验证是否配置成功
GET _nodes?filter_path=**.mlockall
内存配置
如果有一种资源是最先被耗尽的,它可能是内存。排序和聚合都很耗内存,所以有足够的堆空间来应付它们是很重要的。即使堆空间是比较小的时候,也能为操作系统文件缓存提供额外的内存。因为 Lucene 使用的许多数据结构是基于磁盘的格式,Elasticsearch 利用操作系统缓存能产生很大效果。
64 GB 内存的机器是非常理想的,但是 32 GB 和 16 GB 机器也是很常见的。少于8 GB 会适得其反(你最终需要很多很多的小机器),大于 64 GB 的机器也会有问题。
由于 ES 构建基于 lucene,而 lucene 设计强大之处在于 lucene 能够很好的利用操作系统内存来缓存索引数据,以提供快速的查询性能。lucene 的索引文件 segements 是存储在单文件中的,并且不可变,对于 OS 来说,能够很友好地将索引文件保持在 cache 中,以便快速访问;因此,我们很有必要将一半的物理内存留给 lucene;另一半的物理内存留给 ES(JVM heap)。
内存分配
当机器内存小于 64G 时,遵循通用的原则,50% 给 ES,50% 留给 lucene。
当机器内存大于 64G 时,遵循以下原则:
- 如果主要的使用场景是全文检索,那么建议给 ES Heap 分配 4~32G 的内存即可;其它内存留给操作系统,供 lucene 使用(segments cache),以提供更快的查询性能。
- 如果主要的使用场景是聚合或排序,并且大多数是 numerics,dates,geo_points 以及 not_analyzed 的字符类型,建议分配给 ES Heap 分配 4~32G 的内存即可,其它内存留给操作系统,供 lucene 使用,提供快速的基于文档的聚类、排序性能。
- 如果使用场景是聚合或排序,并且都是基于 analyzed 字符数据,这时需要更多的 heap size,建议机器上运行多 ES 实例,每个实例保持不超过 50% 的 ES heap 设置(但不超过 32 G,堆内存设置 32 G 以下时,JVM 使用对象指标压缩技巧节省空间),50% 以上留给 lucene