Elasitcsearch索引优化

·  阅读 2296
本文主要介绍了Elasticsearch的索引优化和索引的settings、mappings,并且举例说明了在什么场景下应该使用什么参数来实现更细致的优化。
上篇文章回顾:Mac OS下用Homebrew安装自己写的开源工具


介绍

Elasticsearch(下面简称ES)本身的搜索性能已经非常优秀,默认参数也适用于大部分场景;但为了更高效地利用计算资源,或者防止出现一个请求消耗掉集群所有的资源情况,我们会对一些参数进行调优和限制。

有关于集群级别的优化,网上已经有很多相关的文章可以借鉴,而索引级别的优化则相对较少。ES的集群优化通常是通用性的优化,而索引优化则更加贴近于业务,更有针对性。本文主要将介绍索引的settings、mappings,举例说明什么场景下使用什么参数来实现更细致的优化。

设置(settings)

1、index.codec 存储压缩率

常用的参数,默认使用LZ4压缩算法,优点是压缩速度非常快,对于写入速度要求比较高,有充裕的存储空间和读写速度的存储介质的场景使用。
将该设置改为best_compression可使用DEFLATE算法来获得更高的压缩率,但会使用更多的CPU资源和时间来压缩/解压。通常都将该参数设为best_compression,除非写入时CPU成为瓶颈。
该设置可在索引创建时或者处于Close状态的索引上指定,新的压缩算法将在下一次merge后生效。

2、index.number_of_shards 索引分片数

另一个常用的参数,增加索引分片数可提高搜索的并发度。当一个Query分发到不同分片上时,ES会将充分利用多核心CPU的优势,每个分片使用不同的核心来处理搜索请求;通常来说,分配到一个节点上的主副分片之和等于CPU核心数将获得最大的CPU利用率和最短的搜索耗时。但分片数过多也会在合并搜索结果时带来额外的计算量,同时若其他的索引也在进行搜索的会造成上下文切换,因此搜索耗时不会随着分片数增长而线性减少。

在满足搜索延迟的前提下,设置最小的分片数是最佳的选择(暂时不考虑分片大小)。

3、index.refresh_interval 刷新间隔

也一个常用的参数,用于调整写入时的刷新间隔,刷新(refresh)既是将内存中的数据写到文件系统缓冲中;写入的数据在刷新后才能被搜索,这也是ES被称为近实时搜索的原因。其本质是Segment的生成间隔。增大刷新间隔可以减少Segment的生成,同时也能减少搜索耗时。通常将该值设为"30s",对实时性要求较高的索引可适当减少刷新间隔。

4、index.translog.durability 事务日志

因为Segment无法做到实时生成,如果在两次refresh间隔中节点宕掉,会导致数据丢失。Translog的存在则是为了解决这个问题。Translog中记录了每一次对ES的操作,默认是每个请求(bulk, delete, upadte, etc)完成后,或者达到同步间隔(sync_interval,默认5s)都会将日志持久化(fsync)到磁盘中,当主副分片都确认持久化完成后,客户端才会受到请求成功的响应;若节点出现异常,可以重放translog中在最后一次刷新后发生的变更操作来恢复数据。
但是每次执行fsync会带来少许的性能损失,若可以接受几秒钟的数据损失,可以将该参数设为"async",使用异步fsync来减小translog带来的影响。

5、index.unassigned.node_left.delayed_timeout 推迟分片分配

当某个节点离线后,默认情况下1m后就会执行重建副本。当我们预知短时间内离线的节点能够重新加入集群,可以延长重建副本的延时,来避免不必要的分片拷贝。
可使用PUT /_all/_settings来对集群中现有的所有索引应用该参数,该参数值视节点重启的时间而定,通常可设为"5m"。

6、index.merge.scheduler.max_thread_count merge线程数

ES默认会使用max(1, min(4, CPU核心数 / 2))的线程数来进行merge操作,这会降低机械硬盘的读写效率,如果索引保存在机械硬盘上,可将该值固定为"1"。

7、"mapper.dynamic"

动态映射一方面降低了用户的使用成本,另一方面也对集群管理造成了不便——由于不规范的稀疏的数据导致索引中的字段数大幅增长而影响搜索性能。如果对一份数据规范性要求比较高,可以通过将该参数设为"false"来关闭自动生成字段映射的特性。

8、index.routing.allocation.total_shards_per_node 限制节点分片数

以我们线上使用的经验来看,ES5.x(6.x的情况有待观察)的分片分配机制不够完善。当某个节点新加入集群的时候,会优先把大索引的分片和当天正在写入的索引的大部分分片分配到该节点上,导致该节点的负载飙高。通过设置该参数可限制索引在该节点上的最大分片数,一定程度上避免这种情况的发生。
但是这个参数不能刚好设置为( pri_shard_num + rep_shard_num) / data_node_num,如果恰好设置成这个值,可能会出现最后一个分配的副本和主分片在同一节点上,导致分配不上去。

9、index.sort 写入预排序

若您大部分的搜索请求是基于一定顺序取TopN,例如看最近的10条日志,耗时最长的100个请求,或者交易最频繁的5个用户等等,ES默认情况下会扫描全部数据后在返回结果。在ES6.x中加入了一个特性,能够大幅降低这类搜索的耗时:Index Sorting。若启用该特性,ES在写入时候就会按照一定顺序排列数据,例如按时间倒叙,这样在搜索的时候就不要扫描全量数据,只要获得每个Segment前TopN的数据即可返回结果。该特性需要在索引创建的时候启用,方法如下:

PUT log
{    "settings" : {
        "index" : {
                    "sort.field" : "timestamp",
                    "sort.order" : "desc"
        }
    },
    ...
}复制代码

注意,启用该特性会降低写入速度,因此需要在写入和搜索之间进行权衡。

映射(mapping)

1、"dynamic_templates" 动态模板

ES之所以具有不需要事先定义schema是因为dynamic mapping功能的存在,它大大降低了用户使用成本。
默认ES对字符串类型的字段会自动映射成text类型,同时再生成一个名为字段名.keyword的keyword类型的子字段。若大部分字段不需要对字段进行分词,可将默认的字符串类型映射为keyword来降低写入时的额外资源开销:

"mappings": {
  "doc": {
      "dynamic_templates": [
      {        "strings_as_keywords": {
                "match_mapping_type": "string", 
                "mapping": { "type": "keyword" }
        }
      }
    ]
  }
}复制代码

2、"dynamic"

效果同索引级别的"mapper.dynamic",可针对某个字段或者整个Type设置,通过将该参数设为"false"来关闭自动生成字段的特性,新添加的字段将被忽略;或者设为"strict",如果遇到新字段抛出异常。

3、"doc_value"

对于keyword类型的字段,如果我们只需要对该字段进行搜索,不需要进行聚合、排序或是使用脚本操作,可以将doc_value设为false,可大幅降节省磁盘空间,一定程度上提升索引速度。

4、"index"

如果我们只需要对某个字段进行聚合、排序或是使用脚本操作,不需要搜索该字段(通常是keyword或者numeric类型的字段),可以将index设为false,可释放该字段倒排索引占用的常驻内存。
索引的倒排索引占用的内存可通过GET /<INDEX_NAME>/_stats?human=true中的segments.terms_memory查看。

5、"norms"

norms参数对搜索评分很有用,若我们不需要计算字段的评分,将该参数设为false,特别是该字段仅用于过滤或聚合。若索引中存在某个字段启用了norms,无论文档中是否存在该字段,所有文档都会占用N bytes(N为文档数)磁盘空间,因此禁用norms可以释放大量的磁盘空间。
norms可以通过以下API禁用,将在生成新的Segment时生效,但不能被重新启用。

PUT my_index/_mapping/_doc
{  "properties": {
    "title": {
          "type": "text",
          "norms": false
    }
  }
}复制代码

6、"enabled"

在一些情况下,我们只需要保存某个字段,通过搜索其他的字段来定位到这条记录查看这个字段的内容,不需要对该字段进行搜索、排序、聚合等操作,可以将该字段的"enabled"设为false,同时节省大量内存和磁盘的空间。

7、"ignore_malformed"

我们经常碰到一些内容不规范或者格式不对的数据,例如某个IP字段的里出现"UNKNOWN",某个数字字段出现"-"。如果在这些字段上已经设置了明确的类型,比如"ip"或者"float",字段中出现了非该类型的值,ES会抛出异常并丢弃整条数据。
我们可以在该字段上设置"ignore_malformed": ture来忽略这个字段并保留该文档中的其他字段。

8、"_source"

该字段属于索引的元数据,其中存储了文档原始的JSON内容,会被存储但不会被索引,用于执行fetch请求时返回原始数据。
当我们不需要获得任何原始数据,只需要对数据进行排序,聚合等计算,或者写入时文档id是手动指定的,通过搜索取到文档id来进一步处理,可以将"_source"设为false来节约大量的磁盘空间。
注意,禁用"_source"后会导致无法使用update,update_by_query,reindex等需要获取原始文档的API,也无法使用高亮功能。

总结

一些比较常用的针对索引级别的设置就介绍到这里,某些在ES6.x以上的版本已被弃用的参数将不再赘述。通过灵活地配置参数可将ES的性能发挥到更高的水平。


本文首发于公众号“小米运维”,点击查看原文