深入研究Elasticsearch聚合的性能 - Raoul Meyer


Elasticsearch在提高性能方面做了很多工作,我们没有注意到,我们还能做些什么来进一步改进它?这是我在研究我们正在使用的一些重型聚合的性能时问自己的问题。在这篇文章中,我给出了Elasticsearch中缓存的基本解释,然后是两个验证缓存和查询如何交互的实验。

Elasticsearch如何缓存?
Elasticsearch具有不同级别的缓存,所有缓存都协同工作以确保它尽可能快地响应。所有缓存级别都具有相同的承诺:接近实时响应。这意味着您获得的响应既快又与数据匹配(或几乎匹配),因为它当前存在于索引中。

请求缓存
Elasticsearch有自己的智能请求缓存。它将根据对底层索引的更新来更新此缓存,从而确保缓存始终准确。当然有一些问题,例如:

即使在索引设置中启用了请求缓存,也不会缓存大小大于0的请求。

请求缓存可能不起作用的其他原因是您的响应包含每个请求都更改的值。例如,如果您的响应包含当前日期或某个随机生成的数字,则会使响应无法缓存。

查询缓存
在更深层次上,过滤器类型查询的结果可以缓存到称为bitset的二进制表示。就像请求缓存一样,只要索引中的相关内容得到更新,就会自动更新此缓存。Elasticsearch仅缓存适用于大量文档的查询:

只有包含超过10,000个文档的段(或总文档的3%,以较大者为准)才会缓存该位集。

它这样做,因为对于较小的段,评估查询可能更快。有了Elasticsearch已经优化了多少没有缓存的性能,通过添加缓存可以很容易地减慢速度。您可以在此处找到有关查询缓存的更多信息。

字段数据缓存​​​​​​​
字段数据缓存与聚合非常相关。正如文档所说:

它将所有字段值加载到内存中,以便提供对这些值的快速文档访问。

没有为字段数据缓存保留足够的内存将使您的聚合变慢。您可以监控现场数据缓存的使用情况,并根据您的需要进行调整。去这里更多地了解它。

提取常见查询元素有帮助吗?
查询缓存似乎对很多真实世界的聚合非常有益。在索引的过滤子集上执行某些聚合是很常见的。在这种情况下,Elasticsearch可以重用过滤吗?或者我们可以帮助它吗?

让我们比较以下查询的性能。第一个查询具有为两个聚合分别指定的相同过滤器:

{
  "size": 0,
 
"aggregations": {
   
"1": {
     
"filter": {
       
"match": {
         
"search_field": "text"
        }
      },
     
"aggregations": {
       
"items": {
         
"top_hits": {
           
"size": 100,
           
"_source": {
             
"includes": "field1"
            }
          }
        }
      }
    },
   
"2": {
     
"filter": {
       
"match": {
         
"search_field": "text"
        }
      },
     
"aggregations": {
       
"items": {
         
"top_hits": {
           
"size": 100,
           
"_source": {
             
"includes": "field2"
            }
          }
        }
      }
    }
  }
}

第二个查询将此过滤器提取到更高级别,这应该使聚合共享结果。我们需要将过滤器包装在bool.filter中以确保评分相同:

{
  "query": {
   
"bool": {
     
"filter": [
        {
         
"match": {
           
"search_field": "text"
          }
        }
      ]
    }
  },
 
"size": 0,
 
"aggregations": {
   
"1": {
     
"top_hits": {
       
"size": 100,
       
"_source": {
         
"includes": "field1"
        }
      }
    },
   
"2": {
     
"top_hits": {
       
"size": 100,
       
"_source": {
         
"includes": "field2"
        }
      }
    }
  }
}

我们禁用了此测试的请求缓存,但查询缓存和字段数据缓存仍可以执行其工作。我们已经确定过滤查询的段实际上大于10,000个文档。这意味着查询缓存应该为此启动,并且这两个查询之间的查询时间应该没有区别。

这两种解决方案之间没有性能差异。看起来查询缓存正在很好地完成其工作。请记住查询缓存的要求,您可能仍然更喜欢第二种变体。在这两种情况下,现场数据缓存也同样有效。

聚合是否并行运行?
在日常工作中,我看到很多情况下我们在一个查询中放置了大量聚合。这让我想知道,聚合实际上并行运行吗?或者我们可以通过例如对每个聚合进行查询来进行msearch来改善响应时间吗?
在此测试中,我们运行与以前相同的查询。我们在一个查询中使用1,2,5和10个聚合进行测试。我们将它与分割聚合的时间进行比较,因此每个聚合都有自己的查询。
当我们为每个聚合提供自己的查询并进行msearch时,我们会看到显着的性能提升。在10次聚合时,加速比接近2倍。在此测试中,Elasticsearch实例在具有2个可用CPU的docker容器内运行,因此这种加速大约是您期望获得的最佳速度。
很明显,聚合不仅仅是默认并行运行。因此,如果您希望缩短响应时间,将聚合拆分为多个查询可能会有意义。这仅适用于CPU不是瓶颈的情况,因为通过拆分查询,您将总共使用更多的CPU时间。

结论​​​​​​​
那么,需要提取常见的查询元素吗?一般来说,不需要,因为Elasticsearch可以针对这些情况进行优化。如果您的过滤器不适合查询缓存,则在聚合中移动更高级别的常见查询元素可能仍会稍微提高性能。
聚合是否并行运行?它们不是默认的。只要你还没有CPU限制,使用msearch拆分它们可能很聪明。在尚未充分利用的群集上,这可以显着缩短响应时间。​​​​​​​