常规建议
不要一次返回太大量的搜索结果集
Elasticsearch设计作为一个搜索引擎,非常擅长返回匹配的查询结果。但是,它并不合适像数据库一样,把整个document作为查询结果返回。如果非要这样做,最好还是使用Scroll这个接口来。
避免索引稀疏
Elasticsearch是基于Lucene进行索引和存储数据的,最佳的工作方式是密集的数据,即是所有的document拥有相同的字段。特别是启用了norms(通常是text字段是默认开启)或者启用了doc_values(通常是numerics, date, ip 或 keyword 是默认开启)的字段。
原因是Lucene内部是通过由0到索引 document 总数的 doc_id 来识别 document 。doc_id 用于Lucene api内部之间的通讯:例如使用关键词 match 查询得到的doc_id,然后这些doc_id用于检索 norms 的值去计算 score(匹配得分)。这个 norms 查找方式实现目前是通过为每个 document 保留一个字节。doc_id 可以直接读取该 document 的 norms 值。这种方式好处是可以帮助Lucene快速访问每个 document ,缺点是每个 document 还需要额外占多一个字节的存储。
实际上,这就意味着如果一个index里面有M个document,每个字段的norms就需要M个字节的存储,就算某些字段只是包含在小部份 document 。虽然复杂的类型字段存储使用doc_values,但是一样还会占用存储的。众所周知,fielddata 类型在 pre-2.0之后被替换成 doc_values ,除非 fielddata已经写在硬盘上了,在内存里面的话,也是有这个问题。
需要注意的是,稀疏存储会对索引和搜索速度有明显的影响,额外的存储字节虽然不是字段,也是需要花费时间去索引和搜索的。
当然,index是允许存在少数稀疏情况的,但是如果稀疏数量教大,则会影响整个index的效率。
本章节主要集中在 norms 和 doc_values 这两个影响较大的特性。稀疏情况会影响倒排索引(用于索引 text/keyword 字段)和坐标点类型字段(用于索引 geo_point 和 numeric),影响的程度不一样。
下面有几点推荐能避免稀疏:
避免把无关联的数据放在同一个index
不要把完全不同的数据结构 document 放在同一个 index 里面。最好是将这些 document 放到不同的index里面,可以考虑创建一些较小的index, 用较少的shard去存储。
注意,这个建议不适用于有使用 parent/child 这种带关系的 document 放在同一个 index 的情况。
标准化 document 结构
如果必需把不同类型的 document 放在同一个 index里面,也是有机会减少稀疏情况的。例如,在 index 内所有的 document 都添加一个时间戳字段,通常叫"timestamp"或者"creation_date",它将会有助于把所有的 document 重命成相同的字段。
避免不同的 type 放在同一个index
多个 type 放在单个 index 看起来是个简单的方法。但是Elasticsearch并不是基于 type来存储的,不同的 type 在单个 index 也会影响效率。如果 type 没有非常相似的 mapping,建议考虑放到一个单独的 index 上面。
对于不同的字段禁用 norms 和 doc_values
如果以上建议适用,还需要检查字段是否启用了 norms 和 doc_values。通常只用于过滤而不需要进行打分(匹配度打分)的字段,可以直接禁用 norms 。不用于排序或者聚合的字段可以禁用 doc_values 。注意,如果在已有的 index 做这些变更,是需要对 index 做 reindex的动作。
调优索引速度
使用 bulk 请求
Bulk 批量请求比单次 document 索引请求性能更好。为了验证最优批量请求的大小,可以做一个基准测试,用一个单节点跑一个单 shard 。先尝试索引100个document,然后 200, 然后400,等等。每次运行基准测试就相应加倍 document 的数量。取得索引速度最高的数值,就是最佳的bulk批量请求数。当然,批量请求也不是越多 document 越好。如果并发同时请求,太大的 bulk 请求会使集群内存压力变大,所以建议避免每次请次超过几十M,这样会获得较好的性能。
使用多进程/多线程去发送数据到Elasticsearch
一个单线程发送 bulk 请求似乎不能够发挥一个集群的索引能力。为了更好地利用集群的资源,应该使用多线程或多进程来发送数据,同时这将有助于减少每次 fsync 的成本。
一定要留意系统是否返回 TOO_MANY_REQUESTS (429) 代码。(通常Java client返回是EsRejectedExecutionException),这是表示Elasticsearch无法跟上当前的索引速度。发生这种情况时可以暂停一下索引一会再试。尝试更换 bulk 一个随机值或理想值。
对于相同大小的 bulk 请求,通过测试可以得到最优的线程数量。可以逐步增加线程数量直至到集群中的机器IO或CPU饱和。
增加 refresh_interval 刷新的间隔时间
index.refresh_interval的默认值是 1s,这迫使Elasticsearch集群每秒创建一个新的 segment (可以理解为Lucene 的索引文件)。增加这个值,例如30s,可以允许更大的segment写入,减后以后的segment合并压力。
在初始化索引时,可以禁用 refresh 和 replicas 数量
如果需要一次加载较大的数据量进 index 里面时,可以先禁用 refresh ,把 index.refresh_interval 设置成为 -1 ,把 index.number_of_replicas 设置成 0。暂时把多个shard副本关闭(即如果当前index发生损坏便用丢失数据),但是这样做可以大大加快索引速度。当初始化索引完成,可以将 index.refresh_interval 和 index.number_of_replicas 设置回原来的值。
禁用 swapping
把操作系统的虚拟内存交换区关闭。sysctl 里面添加 vm.swappiness = 1
确保有空闲的内存给文件系统缓存
文件系统缓存是为了缓冲磁盘的IO操作。至少确保有一半机器的内存保留给操作系统,而不是JAVA VM占用了全部内存。
使用更快的硬件
当然这个不用说了上SSD是最好的了。如果有多个SSD硬盘,可以配置成 RAID 0阵列取得更佳的IO性能。但是任何一个SSD损坏都有可能弄坏 index。通常正确的权衡是优化单的shard存储性能,然后添加 replicas 放在不同的节点。同时使用 snapshot 快照和 restore 功能去备份 index。
索引 buffer 大小
如果节点在做非常大的索引动作,需要确保 indices.memory.index_buffer_size足够大,最多可以设置为512M的buffer。除此之外增加这个值,性能通常不会得到改善。
Elasticsearch的活跃shard需要使用java的heap内存的百份比或者绝对值去作为一个共享缓冲区。非常活跃的shard自然会使用较频繁。
这个默认值是通常是10%,例如,如果JVM设置为10GB内存为heap,那么就会有1GB的索引缓冲区提供给大量的索引shard。
1.启用内存锁检查来禁用交互
Swapping 是将一页内容复制到硬盘预先分配的空间的过程,称为交互空间,以释放内存页。物理内存和交互空间的组合大小是可用的虚拟内存值。
交换对性能和节点稳定十分不利,因不惜一切禁止。它会导致持续几分钟而不是几毫米的gc,并可能导致节点响应缓慢,甚至是断开与集群的连接。与内容相比,磁盘是很慢的。内存是纳秒级而磁盘是毫秒级;所以访问磁盘的速度可能比访问物理内存要慢数万倍。发生的交换越多,进程就会越慢,因为应该避免交换。
当JVM执行一个major gc时会涉及堆中的每一页。如果这些页面中的任何一个被交换到磁盘,它们不得不交换回内存。这会导致大量的磁盘抖动而ES更愿意使用服务请求。禁止交换的几种方法之一是设置ES的bootstrap.mlockall请求JVM通过mlockall将堆锁在内存中。然而,在有些情况下ES无法锁定堆(例如ES没有无限的memlock )。内存锁定检查可用于验证bootstrap.mlockall 是否开启,也可确认JVM是否成功的锁定了堆。
以下是三种禁用交换的方法:
开启bootstrap.mlockall
mlockall 属性允许ES节点禁止交换内存(注意这这对Linux/Unix系统适用)。这个属性可以在 config/elasticsearch.yml 文件中加入以下这行:
bootstrap.mlockall:true
在5.x releases中,这个变量改成了bootstrap.memory_lock: true.
mlockall 默认是关闭的,意味着ES节点允许交换。一旦添加了该字段,需要重启节点。重启ES之后可以通过以下请求返回的mlockall 值来确认是否应用成功。
curl -XGET localhost:9200/_nodes?filter_path=**.mlockall
正确的返回应该是
{"nodes":{"VyKDGurkQiygV-of4B1ZAQ":{"process":{"mlockall":true}}}}
如果我们发现mlockall是false,那意味着mlockall 请求失败。我们将会看到无法锁定JVM内存的提示信息。Linux/Unix最可能的原因是ES用户没有权限锁定内存,可以通过下面的方式授权:
·
启动ES前设置ulimit -l unlimited 为root,或者在 /etc/security/limits.conf设置 memlock 为 unlimited
·
RPM or Debian 在系统配置文件中设置MAX_LOCKED_MEMORY 为 unlimited (或者参阅下面的系统使用systemd)
·
如果使用systemd Systems设置LimitMEMLOCK to infinity in the systemd configuration, .
·
禁用所有的交换文件
可以完全禁用交换。一般ES是作为一个服务部署在资源上,而它的内容是由JVM变量控制的。这不需要进行交换。在Linux系统中我们可以通过运行sudo swapoff -a临时禁用交换。而要永久禁用的话需要编辑 /etc/fstab文件,并注释掉包含单词swap的任何行。
配置减少交换
另外一个Linux系统可选的操纵是确保sysctl 中vm.swappiness 值设为1。这降低了内核交换的倾向,并且在正常情况下不该出现交换,但在紧急情况下任然运行交换。