如何优化100s的Elasticsearch 查询到1s以内

在SQL的世界里, 查询优化是相当成熟并且是可以理解的, 另外一方面, 分布式数据库系统是新出现的, 并且不太成熟. 理解查询是如何工作的将是一件非常重要的事情.

Elstaticsearch的查询有时候将超过100s, 从而引发了很多timeout(超时), GC(垃圾回收) ,cache(缓存)更新等问题, 这里我们列出了在分析过程中发现的几个有意思的问题, 我们是如何优化到1s以内的, 下面也会给出答案.

在最慢的时候, 我们的请求量大约有150,000每秒 输入图片说明

大量的请求并不是问题, 如下图片显示了filter cache的大小变化过程, 当前我们环境中有11个Elasticsearch的结点, 每个结点实例有30G内存, 总计整个集群有330G的内存, 40%的内存贡献给了 filter cache, 当我们执行查询的时候, 查询结果就缓存在这里, 查询结果的重复使用极大地提升了查询性能. 输入图片说明

内存空间是被所有消费者共享使用的, 并且受最近最少执行计划影响, 当我们增加一块filter cache内存空间时, 最近最少执行的查询所对应的filter cache就会被踢出去. 输入图片说明

从上面的图可以看出, 一次次内存空间的周期波动,导致了一些超长GC的执行.

输入图片说明

任何内存的踢出导致了无数周期性老年代垃圾回收. 老年代GC导致了执行过程的暂停, 这意味着Elasticsearch 每个结点在GC的过程中, 对于集群中的其它结点, 状态是死亡的, 不会接受任何请求, 即使是集群的的请求. 理想情况下老年代GC的应该是少见和短时间的. 这里我们看到的许多结点的GC是频繁的和长时间的.

我们的CPU使用率或disk i/o 是没有压力的, 主要是内存的约束. 集群结点有64G的内存, 30G分配给了elasticsearch JVM, 剩下的分配给了file cache, 由于file cache使用了SSD, 我们发现4s不到的时间, SSD写入了132G的数据, 这导致了Out of memory 异常, 并且该结点的异常导致了集群的崩溃.

  1. 升级内存

升级硬件并不总是一个正确的方案, 但是从我们的案例来看, 主要受到内存的限制, 我们可以在结点上增加一倍的内存. 但是我们不建议在JVM heap上分配的内存大于32G, 因此我决定在每个结点增加一倍内存的基础上运行2个Elasticsearch实例,感谢Elasticsearch已经意识到主,备分区(shard)不能同时运行是同一个盒子(box), 时间将验证内存升级后的运行情况.

  1. 控制什么数据被缓存

我们的第一直觉就是检查什么数据被缓存了, 当检查我们的查询语句才发现, 好像所有的东西都被缓存了, 缓存的数据太多了, 如下语句是我们查询语句中的一个: 输入图片说明

只需要缓存查询语句中的过滤条件, 其它不用管, 这和我们期望的差不多.

  1. 改变查询语句

查询慢的时候, 我们集群中的文档数量大约有64,000,000. 请求需要做Map-Reduce的工作, 请求从负载均衡的客户端向分布式集群中的所有结点请求数据, 结点收到请求后分发请求到所有分区(shard), 每个分区(shard)缓存过滤条件到集合中. 输入图片说明

从第一次请求的过滤条件被缓存之后, 后面所有的查询都从缓存的内存的读取数据, 但是这里有个问题, 由于数据太多的原因, 请求有可能被路由到主结点或者备份结点, 过滤器缓存被不断的刷新并重建, 为了减少请求查询数量, 我们来看看聚合查询是如何工作的.

  1. 聚合查询

输入图片说明

Elasticsearch 支持很多聚合查询, 例如上图中的短语(terms)聚合查询, 和SQL中的group by很像, 但是Elasticsearch 的数据分布在多个分区(shard), 当一个聚合查询执行时, 所有分区(shard)都会收到请求并返回数据视图, 结点收到返回的数据, 完成聚合加工除理后将数据返回给客户端, 由于分布式数据的原因, 计算结果并不是绝对精确, Elasticsearch 是如何工作的官方文档如下: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html

Elasticsearch支持分桶后的嵌套分桶. 输入图片说明

上面的查询, 首先文档通过 gender 分组并完成了avg_height的计算, 这个查询在跨集群中的所有结点机器并行计算, 由于查询下发到了每个结点, 减少了内存使用的压力.

但是因为需要做聚合计算, 所有的字段数据必须加载到内存中, Elasticsearch在field cache中缓存字段数据, 默认情况下JVM heap中的10%分配给field cache, 随着聚合的不断使用, 不得不缓存几乎所有的字段到内存中, 由于我们不能预测需要多大的内存, 这很容易导致内存溢出(Out of memeory)的异常, 这也直接导致了内存上的压力, 引发了更多的老年代垃圾回收, 查询也会变慢, 甚至发生集群崩溃的危险.

为了避免这类事情的发生, Elasticsearch 对field cache所占用的总内存有回收中断的保护措施, 这可以在请求层面设置, 当请求消耗的内存达到设置值时, 请求会被中止. 默认情况下, field cache使用了延迟加载, 除此之外, Elastsicsearch的处理方式还有很多, 也有通过文件系统缓存field data的, 文件系统缓存被操作系统管理, 由于没有垃圾回收机制更加高效, 分布式系统正在慢慢地远离JVM内存模型, Apache Kafka已经完全依赖文件系统缓存.

http://www.elasticsearch.org/guide/en/elasticsearch/guide/current/preload-fielddata.html

有时候聚合查询可能会比一般的查询慢10倍, 例如当一个小于25个字符的字段做为terms聚合列查询时, 普通查询将比聚合查询快很多, 极限时, JVM内存使用上也难以接受, Elasticsearch如此之快, 是因为使用了内存, 同样如果不关心内存的使用, 性能从根本上也会受影响. 没有必要的情况下, 不要使用内存, 降低JVM内存的使用压力, 使用文件系统缓存或者类似的方式.

猜你喜欢

转载自my.oschina.net/u/2392330/blog/1558733