索引性能技巧edit

如果你是在一个索引负载很重的环境,比如索引的是基础设施日志,你可能愿意牺牲一些搜索性能换取更快的索引速率。 在这些场景里,搜索常常是很少见的操作,而且一般是由你公司内部的人发起的。 他们也愿意为一个搜索等上几秒钟,而不像普通消费者,要求一个搜索必须毫秒级返回。

基于这种特殊的场景,我们可以有几种权衡办法来提高你的索引性能。

科学的测试性能edit

性能测试总是困难的,所以在你的方法中尽量做到科学。 随机摆弄旋钮(修改配置)以及写入开关可不是做性能调优的好办法。 如果有太多种原因,我们就无法判断到底哪一种有最好的 效果 。 合理的测试方法如下:

  1. 在单个节点上,对单个分片、无副本的场景进行性能测试。
  2. 在 100% 默认配置的情况下记录性能结果,这样你就有了一个对比基线。
  3. 确保性能测试运行足够长的时间(30 分钟以上),这样你可以评估长期性能,而不是短期的峰值或延迟。 一些事件(比如段合并,GC)不会立刻发生,所以性能概况会随着时间的推移而改变。
  4. 开始对基线默认值逐一修改。 严格测试它们,如果性能提升可以接受,保留这个配置,继续下一项。

使用批量请求并调整其大小edit

这应该是显而易见的,但是使用批量索引请求可以获得最佳性能。 批量的大小则取决于你的数据、分析和集群配置,但一个很好的起点是每批次 5 - 15 MB。 注意这里说的是物理字节数大小。 对批量大小来说,文档数量不是一个好指标。 比如说,如果你每次批量索引 1000 个文档,记住以下几点:

  • 1000 个 1 KB 大小的文档加起来是 1 MB。
  • 1000 个 100 KB 大小的文档加起来是 100 MB。

这可是完完全全不一样的批量大小了。 批量请求需要在协调节点上加载进内存,所以批量请求的物理大小比文档数量重要得多。

从 5–15 MB 大小的容量开始进行批量请求,慢慢增加这个数字,直到你看不到性能提升为止。 然后开始增加批量写入的并发度(多线程等等办法)。

用 Marvel 以及诸如iostattopps等工具监控你的节点,观察资源何时出现瓶颈。 如果你开始收到EsRejectedExecutionException,你的集群没办法再继续了:至少有一种资源到容量限制了。 要么减少并发数,要么提供更多的受限资源(比如从机械磁盘换成 SSD),要么添加更多节点。

写数据的时候,确保批量请求在所有数据节点之间循环执行。。 不要把所有请求都发给单个节点,因为这个节点需要在处理的时候把所有批量请求都存到内存里。

存储edit

磁盘在任何现代服务器上通常都是瓶颈。 Elasticsearch 重度使用磁盘,你的磁盘能处理的吞吐量越大,你的节点就越稳定。 这里有一些优化磁盘 I/O 的技巧:

  • 使用 SSD。正如其他地方提到过的,他们优于旋转介质(如:机械硬盘)。
  • 使用 RAID 0。 条带化 RAID 会提高磁盘 I/O,代价显然就是当一块硬盘故障时整个就挂了。 不要使用镜像(raid 1)或者奇偶校验 RAID (raid 5), 因为副本已经提供了这个功能。
  • 另外,使用多块硬盘,并允许 Elasticsearch 通过多个 path.data 目录配置把数据条带化分配到它们上面。
  • 不要使用远程挂载的存储,比如 NFS 或者 SMB/CIFS。 这种设备引入的延迟与性能是背道而驰的。
  • 如果你是在EC2上,当心 EBS。即便是基于 SSD 的 EBS,通常也比本地实例存储要慢。

段与合并edit

段合并的计算量庞大,而且还要吃掉大量磁盘 I/O。 合并在后台定期操作,因为他们可能要很长时间才能完成,尤其是比较大的段。 这个通常来说都没问题,因为大段合并的概率相对很小。

不过有时候合并会落后于写入速率。 如果发生这种情况,Elasticsearch 会自动将索引写入请求限制到单个线程里。 这可以防止出现 段爆炸 问题,即在合并之前生成了数百个段。 如果 Elasticsearch 发现合并落后于索引写入,它会记录一个声明now throttling indexingINFO 级别的信息。

Elasticsearch 默认设置在这块比较保守:你不希望搜索性能被后台合并影响。 不过有时候(尤其是 SSD,或者日志场景)限流阈值太低了。

默认值是 20 MB/s,对旋转介质(如: 机械磁盘)应该是个不错的设置。 如果你用的是 SSD,可以考虑提高到 100–200 MB/s。测试验证哪个值合适你的系统:

PUT /_cluster/settings
{
    "persistent" : {
        "indices.store.throttle.max_bytes_per_sec" : "100mb"
    }
}

如果你在做批量导入,完全不在意搜索,你可以禁用合并限流。 这将允许索引以磁盘的最大速度运行:

PUT /_cluster/settings
{
    "transient" : {
        "indices.store.throttle.type" : "none" 
    }
}

设置限流类型为none来禁用合并限流。等你完成了导入,记得改回merge以重新打开限流。

如果你使用的是旋转介质(如:机械磁盘)而非 SSD,你需要添加下面这个配置到你的 elasticsearch.yml 里:

index.merge.scheduler.max_thread_count: 1

旋转介质(如:机械磁盘)在并发 I/O 支持方面比较差,所以我们需要降低每个索引并发访问磁盘的线程数。 这个设置允许 max_thread_count + 2 个线程同时进行磁盘操作,也就是设置为 1 允许三个线程。

对于 SSD,你可以忽略这个设置,默认是 Math.min(3, Runtime.getRuntime().availableProcessors() / 2) ,对 SSD 来说运行的很好。

最后,你可以增加 index.translog.flush_threshold_size 设置,从默认的 512 MB 到更大一些的值,比如 1 GB。 这允许在发生 刷新(flush) 之前在 事务日志(translog) 中积累更大的段。 而通过构建更大的段,刷新(flush) 的频率变低,大段合并的频率也变低。 这一切合起来导致更少的磁盘 I/O 开销和更好的索引写入速率。 当然,你需要响应数量的堆内存用以积累额外的缓冲空间,调整这个设置的时候请记住这一点。

其他edit

最后,还有一些需要注意的事项:

  • 如果你不需要搜索结果的实时精确性,考虑把每个索引的 index.refresh_interval调大到30s(译者注: 默认是 1s)。 如果你是在做大批量导入,导入期间你可以通过设置这个值为 -1 关掉刷新(refresh)。别忘记在完工的时候重新开启它。
  • 如果你在做大批量导入,考虑通过设置 index.number_of_replicas: 0关闭副本。 文档在复制的时候,整个文档内容都被发往副本节点,然后逐字的把索引过程重复一遍。 这意味着每个副本都会执行分析、索引以及可能的合并过程。

    相反,如果你的索引是零副本,然后在写入完成后再开启副本,恢复过程本质上只是一个字节到字节的网络传输。 相比重复索引过程,这个算是相当高效的了。

  • 如果你没有给每个文档自带 ID,使用 Elasticsearch 的自动 ID 功能。 这个为避免版本查找做了优化,因为自动生成的 ID 是唯一的。
  • 如果你使用自己的 ID,尝试使用一种 Lucene 友好的 ID。 包括零填充序列 ID、UUID-1 和纳秒;这些 ID 都是有一致的顺序的模式, 可以很好的压缩。 相反的,像 UUID-4 这样的 ID,本质上是随机的,压缩比很低,会拖慢 Lucene。