原文链接
https://www.elastic.co/cn/blog/advanced-tuning-finding-and-fixing-slow-elasticsearch-queries
Elasticsearch 是一个非常灵活且功能丰富的应用程序,它提供了许多不同的数据查询方法。但是,您是否遇到过查询速度低于预期的情况?对于像 Elasticsearch 这样的分布式系统,可能会有各种影响查询性能的因素,包括负载平衡器设置、网络延迟(带宽、网卡/驱动程序)等在内的各种外部因素。
在本篇博文中,我将讨论导致慢查询的原因,以及如何在 Elasticsearch 的上下文中识别它们。本文主要源于一些常见的排查方法展开论述,因此,可能需要您对 ElasticSearch 的工作原理非常熟悉。
在我们研究一些更棘手的情况之前,让我们先从一些导致慢查询的最常见原因及其解决方案入手。
每个分片都消耗资源(CPU/内存)。即使没有索引/搜索请求,分片的存在也会产生集群开销。
集群中的分片太多,以至于任何查询的执行速度看起来都很慢。一个好的经验法则是:确保对于每个节点上已配置的每个 GB 堆,将非冻结的分片数量保持在 20 以下。
减少分片计数,实施冻结索引和/或添加附加节点来实现负载平衡。考虑结合使用 Elasticsearch 中的热/温架构(非常适合基于时间的索引)以及滚动/收缩功能,以高效管理分片计数。要想顺利完成部署,最好先执行适当的容量计划,以帮助确定适合每个搜索用例的最佳分片数。
根据上次集群重新启动的累积值看,搜索线程池显示“rejected”计数在持续增加。
GET /_cat/thread_pool/search?v&h=node_name,name,active,rejected,completed
响应类似如下:
node_name name active rejected completed instance-0000000001 search 0 10 0 instance-0000000002 search 0 20 0 instance-0000000003 search 0 30 0
查询面向的分片太多,超过了集群中的核心数。这会在搜索线程池中造成排队任务,从而导致搜索拒绝。另一个常见原因是磁盘 I/O 速度慢,导致搜索排队或在某些情况下 CPU 完全饱和。
创建索引时采用 1 个主分片:1 个副本分片 (1P:1R) 模型。使用索引模板是一个在创建索引时部署此设置的好方法。(Elasticsearch 7.0 或更高版本将默认 1P:1R)。Elasticsearch 5.1 或更高版本支持搜索任务取消,这对于取消任务管理 API 中出现的慢查询任务非常有用。若要改进磁盘 I/O,请查看存储建议,并确保使用推荐的硬件以获得最佳性能。
指标相关性表明,当集群不堪重负时,CPU 使用率和索引延迟都会很高。
集群索引量大会影响搜索性能。
将 index.refresh_ interval 的值(从文档被索引到其变为可见的时间间隔)增加到 30 秒,通常有助于提高索引性能。实际业务情境中可能会有所不同,因此测试是关键。这可以确保分片不必因为每 1 秒默认创建一个新分段而造成工作负载增大。
对于索引量大的用例,请查看索引调优建议,以优化索引和搜索性能。
在副本分片计数增加(例如,从 1 增加到 2)后,可以观察到查询延迟。如果存在较多的数据,那么缓存的数据将很快被逐出,从而导致操作系统页面错误增加。
文件系统缓存没有足够的内存来缓存索引中经常查询的部分。Elasticsearch 的 查询缓存实现了 LRU 逐出策略:当缓存变满时,将逐出最近使用最少的数据,以便为新数据让路。
为文件系统缓存留出至少 50% 的物理 RAM。内存越多,缓存的空间就越大,尤其是当集群遇到 I/O 问题时。 假设堆大小已正确配置,任何剩余的可用于文件系统缓存的物理 RAM 都会大大提高搜索性能。
例如,128GB 的 RAM 服务器留出 30GB 的内存用于堆,剩余内存用于文件系统缓存(有时称为 OS 缓存),假设操作系统缓存了最近访问的 4KB 数据块,这样,如果您一次又一次地读取相同的文件,那么您不必花很长时间到磁盘上读,而是会从内存中直接读取。
除了文件系统缓存,Elasticsearch 还使用查询缓存和请求缓存来提高搜索速度。 所有这些缓存都可以使用搜索请求首选项进行优化,以便每次都将某些搜索请求路由到同一组分片,而不是在不同的可用副本之间进行交替。这将更好地利用请求缓存、节点查询缓存和文件系统缓存。
操作系统显示出持续的高 CPU/磁盘 I/O 利用率。停止第三方应用程序后,可以看到性能会提高。
其他进程(例如 Logstash)和 Elasticsearch 本身之间存在资源(CPU 和/或磁盘 I/O)争用。
避免在共享硬件上与其他资源密集型应用程序一起运行 ElasticSearch。
当查询包含高度唯一值(如 ID、用户名、电子邮件地址等)的聚合字段时,性能不佳。在堆转储分析期间发现:使用“search”、“buckets”、“aggregation”等术语的 Java 对象消耗了大量的堆空间。
聚合在高基数字段上运行,需要大量资源来提取许多存储桶。 此外,还可能存在涉及嵌套字段和/或联接字段的嵌套聚合。
若要改进高基数术语聚合的性能,请阅读我的咨询团队同事的这篇博文:Improving the performance of high-cardinality terms aggregations in Elasticsearch | Elastic Blog
有关进一步的调整,请查看我们关于嵌套字段和联接字段的建议,以更好地提高聚合性能。
一般来说,可以采用一些索引调优/搜索调优建议来解决偶尔或间歇出现的慢查询问题。偶发的慢查询应该与这些监测指标中的一个或多个密切相关:
Elasticsearch 还有另一个有用的功能,称为自适应副本选择 (ARS),它允许协调节点了解数据节点上的负载,并允许它选择最佳分片副本来执行搜索,从而提高搜索吞吐量并降低延迟。通过在查询期间更均匀地分配负载,ARS 对于偶发的减速有很大帮助。在 Elasticsearch 7.0 及更高版本中,默认情况下将启用 ARS。
对于持续性的慢查询,我们可以尝试逐个删除查询中的功能,并检查查询是否仍然慢。查找最简单查询以再现性能问题,有助于隔离和识别问题:
大小
设为 0,它是否仍然慢?(当大小
设为 0 时,Elasticsearch 会缓存搜索请求的结果,以便更快地进行搜索)一些“搜索调优”的建议是否有用?
在故障排除期间,执行以下操作通常很有用:
如果查询来自 Kibana 可视化,则使用可视化侦查面板(Kibana 版本 6.3 及更高版本)或仪表板检查面板(Kibana 版本 6.4 及更高版本)来查看并导出实际的查询请求,并将其导入到配置文件 API 中以做进一步分析。
有时,在像 Elasticsearch 这样的分布式应用程序中,当同时处理不同的请求/线程时,很难捕获慢查询或耗费资源的查询。如果对运行耗费资源查询的用户不加以控制,情况就会变得愈加复杂,这些查询会降低集群性能(例如,较长的垃圾收集 (GC) 周期),甚至更糟糕地会出现内存不足 (OOM) 的情况。
在 Elasticsearch version 7.0 中,我们引入了一种新的内容熔断策略,用于在保留内存时测量实际堆内存的使用情况。这个新策略可提高节点对耗费资源的查询导致集群过载的弹性支持,并且在默认情况下处于启用状态,并可使用新的集群设置 indices.breaker.total.use_real_memory 对其进行控制。
但是,我们应该注意,这些都是尽力而为;对于以上内容未涉及的情况,最好在 OOM 崩溃后收集堆转储,或从运行的 JVM 中收集堆转储,以更好地了解根本原因。
Elasticsearch 还有另一个保护设置(最大存储桶软限制),用于防止集群出现 OOM。当超过存储桶数量(在 7.0 版中默认为 10,000)时(例如,当运行多层聚合时),此最大存储桶聚合设置将停止执行并使搜索请求失败。
为了进一步识别潜在的耗费资源查询,我们可以设置断路器设置 (indices.breaker.request.limit),从一个低阈值开始隔离查询,并逐渐向上移动阈值,以将范围缩小到特定的查询。
此外,还可以通过在 Elasticsearch 中启用 slowlogs 来识别运行缓慢的查询。slowlogs 专门用于分片级别,这意味着仅适用于数据节点。仅协调/客户端节点不具备慢日志分析功能,因为它们不保存数据(索引/分片)。
slowlogs 有助于回答如下问题:
slowlog 输出示例:
[2019-02-11T16:47:39,882][TRACE][index.search.slowlog.query] [2g1yKIZ] [logstash-20190211][4] took[10.4s], took_millis[10459], total_hits[16160], types[], stats[], search_type[QUERY_THEN_FETCH], total_shards[10], source[{"size":0,"query":{"bool":{"must":[{"range":{"timestamp":{"from":1549266459837,"to":1549871259837,"include_lower":true, "include_upper":true,"format":"epoch_millis","boost":1.0}}}],"adjust_pure_negative":true,"boost":1.0}},"_source":{"includes":[],"excludes":[]},"stored_fields":"*","docvalue_fields": [{"field":"timestamp","format":"date_time"},{"field":"utc_time","format":"date_time"}],"script_fields":{"hour_of_day":{"script":{"source":"doc['timestamp'].value.getHourOfDay()", "lang":"painless"},"ignore_failure":false}},"aggregations":{"maxAgg":{"max":{"field":"bytes"}},"minAgg":{"min":{"field":"bytes"}}}}], id[]],
slowlog 消息拆解:
拆分项 | 说明 |
[2019-02-11T16:47:39,882] |
查询日期 |
[TRACE] |
日志级别 |
[index.search.slowlog.query] |
搜索 slowlog 的查询阶段 |
[2g1yKIZ] |
节点名称 |
[logstash-20190211] |
索引名称 |
[4] |
查询执行的分片序号 |
took[10.4s] |
在分片 [4] 上花费的处理时间。注意:在查看 slowlog 时,我们需要避免将来自不同分片的所有时间相加,因为每个分片可能并行执行。 |
took_millis[10459] |
花费的时间(毫秒) |
total_hits[16160] |
总命中数 |
search_type[QUERY_THEN_FETCH] |
搜索类型 |
total_shards[10] |
索引的总分片数 |
source[] |
执行的查询正文 |
黄金级或白金级订阅的客户(包括 Elastic 安全功能),可以启用审计日志来捕获有关查询的更多详细信息。(用户可以开始为期 30 天的试用来测试 Elastic 安全功能。) 审计日志有助于回答以下问题:
由于默认的审计设置相当繁琐,我们需要调整设置:
xpack.security.audit.enabled: true
。xpack.security.audit.outputs:[logfile, index]
。xpack.security.audit.logfile.events.include: authentication_success
。借助这些设置,您就可以像下面这样监测用户查询。
"request.body":
开始:{"@timestamp":"2019-05-01T19:26:53,554", "node.id":"Z1z_64sIRcy4dW2eqyqzMw", "event.type":"rest", "event.action":"authentication_success", "user.name":"louisong", "origin.type":"rest", "origin.address":"127.0.0.1:51426", "realm":"default_native", "url.path":"/_msearch", "url.query":"rest_total_hits_as_int=true&ignore_throttled=true", "request.method":"POST", "request.body":"{\"index\":\"*\",\"ignore_unavailable\":true,\"preference\":1556709820160}\n{\"aggs\":{\"2\":{\"terms\":{\"field\":\"actions\",\"size\":5,\"order\":{\"_count\":\"desc\"},\"missing\":\"__missing__\"}}},\"size\":0,\"_source\":{\"excludes\":[]},\"stored_fields\":[\"*\"],\"script_fields\":{},\"docvalue_fields\":[{\"field\":\"access_token.user_token.expiration_time\",\"format\":\"date_time\"},{\"field\":\"canvas-workpad.@created\",\"format\":\"date_time\"},{\"field\":\"canvas-workpad.@timestamp\",\"format\":\"date_time\"},{\"field\":\"creation_time\",\"format\":\"date_time\"},{\"field\":\"expiration_time\",\"format\":\"date_time\"},{\"field\":\"maps-telemetry.timeCaptured\",\"format\":\"date_time\"},{\"field\":\"task.runAt\",\"format\":\"date_time\"},{\"field\":\"task.scheduledAt\",\"format\":\"date_time\"},{\"field\":\"updated_at\",\"format\":\"date_time\"},{\"field\":\"url.accessDate\",\"format\":\"date_time\"},{\"field\":\"url.createDate\",\"format\":\"date_time\"}],\"query\":{\"bool\":{\"must\":[{\"range\":{\"canvas-workpad.@timestamp\":{\"format\":\"strict_date_optional_time\",\"gte\":\"2019-05-01T11:11:53.498Z\",\"lte\":\"2019-05-01T11:26:53.498Z\"}}}],\"filter\":[{\"match_all\":{}},{\"match_all\":{}}],\"should\":[],\"must_not\":[]}},\"timeout\":\"30000ms\"}\n", "request.id":"qrktsPxyST2nVh29GG7tog"}
在本文中,我们讨论了慢查询的常见原因以及相应的解决方案。我们还讨论了识别持续性慢查询和偶发慢查询的不同方法。通常会将慢查询视为需要解决的更广泛集群性能问题的典型症状。
在 Elasticsearch,我们一直在努力改进查询时间,致力于在未来版本中提高查询性能。希望本文对处理慢查询的问题有所帮助。请在 Elasticsearch 讨论论坛上自由提问,参与进一步的讨论。祝您搜索愉快!