Weaviate Blog

Query Profiling: See Where a Slow Query Spends Its Time

8.5内容质量
Query Profiling: See Where a Slow Query Spends Its Time

TL;DR · AI 摘要

Weaviate 推出查询分析功能,可实时定位慢查询耗时环节,解决传统慢查询日志的四大痛点。

核心要点

  • 查询分析功能无需重启节点即可实时分析慢查询
  • 新方法解决了现有日志需重启、临时设置、延迟记录和节点独立日志四大问题
  • 查询分析提供完整的性能数据拆分,精准定位过滤/向量搜索/磁盘读取等耗时环节

结构提纲

按章节快速跳转。

  1. 生产环境查询变慢时,需要精准定位耗时环节才能有效优化。

  2. 通过环境变量启用慢查询日志,但需要重启节点且存在诸多限制。

  3. 包括需重启节点、临时设置、延迟记录和节点日志分散等问题。

  4. ·查询分析功能定义

    查询分析是可选标志功能,可实时获取每个查询的完整性能数据。

  5. 无需重启、持久化配置、实时分析和集群级日志聚合是核心改进点。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 查询分析
    • 现有方案缺陷
      • 需重启节点
      • 临时设置
      • 延迟记录
      • 节点日志分散
    • 新方案优势
      • 实时分析
      • 持久化配置
      • 集群级聚合

金句 / Highlights

值得收藏与分享的关键句。

#数据库优化#查询分析#Weaviate#性能调优
打开原文

查询性能分析:查看慢查询的时间消耗 | Weaviate

查询性能分析:查看慢查询的时间消耗

2026年7月21日

·

10分钟阅读

Byron Voorbach

领域首席技术官

生产环境中的查询速度很慢。可能它一直都有点慢,直到某天有人注意到才突破了临界点;也可能是在数据加载后性能下降了。不管怎样,你现在只有一个问题,而且每次遇到这种情况都是同样的问题:时间都去哪儿了?是过滤条件耗时?是向量搜索?是从磁盘读取对象?还是关键词评分?在将这个数字拆解成各个组成部分之前,所有修复方案都只是猜测。

Weaviate 一直都能回答这个问题。困难在于如何获取答案。

我们过去如何发现慢查询

现有工具是慢查询日志。你只需通过两个环境变量开启它并重启节点:

code
QUERY_SLOW_LOG_ENABLED=true
QUERY_SLOW_LOG_THRESHOLD=2s

任何运行时间超过阈值的查询都会以 WARN 级别记录,并附带完整的耗时分析:包括类和分片信息、过滤条件、限制条件以及引擎在处理过程中记录的每个内部计时器。如果你已经配置了运行时覆盖设置,可以直接通过覆盖文件实时切换这两个参数,无需重启,因为服务器会在加载间隔重新读取这些设置。

对于长期监控整个集群的性能回归,这种方法效果不错。它就像一张被动的网,只有在被触发时才会显现。

旧方法的局限性

慢查询日志的设计目标与当前特定查询变慢时的需求存在差异,这导致了四个关键问题。

首先,启用该功能需要重启节点。为了调试延迟问题而重启节点会改变你正在测量的对象。新进程启动时操作系统页面缓存是冷的,因此重启后的首次查询会从磁盘读取,而正常运行的节点会命中内存。你最终分析的是重启过程本身,而非查询。

其次,运行时调整的设置是临时的。如果你通过运行时覆盖提高阈值或切换日志以捕获特定查询,这些更改只保存在覆盖文件中,而不是集群实际部署的配置文件中。你必须记住要撤销这些更改,而这些更改很容易与部署定义脱节。

第三,它总是滞后于问题发生。日志只在查询已经超出阈值后才会记录。你无法直接指向当前查询并获取其数据,只能等待该查询或类似查询再次变慢。更糟糕的是,日志会随机采样约1%的查询(无论快慢),因此快速查询可能与真正慢的查询混在一起,你需要手动过滤掉噪声。

第四,它是按节点记录的。每个节点只记录在其上执行的分片搜索,没有任何机制能将这些记录串联起来。对于跨集群广播的单个查询,你关心的耗时信息会分散在多个节点日志中,重新拼凑出完整分析需要手动操作。

什么是查询性能分析?

查询性能分析是针对上述所有问题的直接解决方案。它是一个按查询启用的标志。你只需在某个搜索请求上开启它,服务器就会收集该搜索的耗时分析,并在响应中直接返回这些数据。无需环境变量、无需阈值、无需重启,事后也不需要任何重置操作。

在底层,它使用与慢查询日志相同的监控机制,因此数字是相同的,只是呈现方式不同。它直接解决了每个节点之间的差距:协调节点会从参与查询的每个节点上的每个分片收集分析信息,并将它们一并返回。一次请求即可获取整个集群对该查询的完整视图。

你可以在每个查询中启用此功能。在 Python 中,将其添加到返回的元数据中:

code
from weaviate.classes.query import MetadataQuery
response = collection.query.near_vector(
    near_vector=[0.1, 0.2, 0.3],
    limit=10,
    return_metadata=MetadataQuery(query_profile=True)
)
for shard in response.query_profile.shards:
    print(shard.name, shard.node, shard.searches)

查询分析功能在官方 Python、JavaScript/TypeScript、Java (v6) 和 C# 客户端中可用,也可以通过在请求元数据中设置 QueryProfile 直接通过 gRPC 使用。分析结果位于响应对象的 response.query_profile 属性中,而不是任何单个结果中。分析涵盖向量搜索、关键词评分和过滤器评估,但不测量生成模块、重排序器或其他后处理操作。

查询分析功能在 v1.36.9 版本中首次引入,作为 v1.37 版本的预览功能发布,并在 v1.38 版本中正式推出。当该功能关闭时,成本仅是一个布尔检查。当该功能开启时,增加的成本是微秒级的计时器读取。该功能旨在用于调试和优化,而不是用于生产环境的热点路径。

解读数据

分析结果按分片组织。每个分片条目会标明运行该分片的分片名称和节点,并包含一个详细指标映射表,显示每种搜索类型执行的指标名称和值。纯向量查询会显示向量计时器,BM25 查询会显示关键词计时器,过滤查询会增加过滤计时器,混合查询则同时包含向量和关键词部分。只会显示实际执行的阶段,因此具体字段的集合取决于查询内容。

total_took 表示整个分片搜索的墙钟时间。其他所有内容都解释了这段时间的去向。

在向量路径中,objects_took 表示对象填充时间:从磁盘对象存储中读取最终对象。此处的高值意味着磁盘限制的填充,通常指向页面缓存未命中、较大的 limit 值或较大的对象。这是分析中最清晰的磁盘信号。

filters_build_allow_list_took 表示过滤器解析时间:通过倒排索引将 where 子句转换为匹配文档 ID 的集合。它由过滤器基数以及这些索引段是在内存中还是在磁盘上决定。filters_ids_matched 计数紧邻其旁,告诉你当前处于哪种状态。较大的计数意味着过滤器匹配了大量文档;较小的计数配合较高的时间则更可能指向磁盘读取。

vector_search_took 表示向量索引正在进行搜索,其主要子计时器是 knn_search_layer_N_took,每个 HNSW 层对应一个条目,通常第 0 层的耗时占比最高。这是图遍历过程:探索候选对象并计算距离。此处需要特别注意精确性,因为很容易误判高向量耗时是由于节点资源不足。实际上通常不是这种情况。HNSW 图存储在内存中,因此该计时器衡量的是计算耗时,而非磁盘等待耗时。高值意味着遍历本身成本较高,影响遍历成本的关键因素包括 ef 值、向量维度以及过滤搜索中的过滤策略。在考虑增加硬件之前,应优先调整这些参数。

唯一可能真正访问磁盘的向量阶段是 knn_search_rescore_took。该计时器仅在启用压缩时出现,此时需要将完整精度的向量读回内存,对压缩候选对象进行重新评分。此处的高值可能意味着重新评分时的磁盘延迟,这属于与遍历速度慢不同的问题类型。

hnsw_flat_search 是布尔值而非计时器。当在过滤条件下该值为 true 时,表示过滤器的选择性足够高,直接暴力扫描匹配项比遍历图更高效,引擎会主动选择这种路径。

在关键词路径中,kwd_* 系列指标以相同方式分解 BM25。其中两个指标需要特别关注:kwd_3_term_time 表示从倒排段中读取每个术语的倒排列表的耗时,因此对查询术语数量和磁盘读取敏感;kwd_4_bmw_time 表示 BlockMax WAND 遍历过程对倒排块进行评分的耗时,该值会随着查询选择性降低和倒排列表变长而增加。缓慢的关键词查询通常属于其中一种情况,指标拆分可以帮助判断具体原因。

实例解析

以下数据仅供参考,但展示了输出格式和解读方式。

以对象读取耗时占主导的向量搜索为例:

code
{
"shards"
:
[
{
"name"
:
"1a2b3c4dshard"
,
"node"
:
"weaviate-0"
,
"searches"
:
{
"vector"
:
{
"details"
:
{
"total_took"
:
"48.2ms"
,
"filters_build_allow_list_took"
:
"2.1ms"
,
"filters_ids_matched"
:
"512"
,
"vector_search_took"
:
"8.4ms"
,
"knn_search_layer_0_took"
:
"7.9ms"
,
"knn_search_rescore_took"
:
"0.3ms"
,
"hnsw_flat_search"
:
"false"
,
"objects_took"
:
"36.8ms"
}
}
}
}
]
}

过滤和向量搜索耗时较低。objects_took 占据 total_took 的大部分,说明时间消耗在从磁盘读取对象上。需要检查该节点的页面缓存和存储情况,考虑减小结果限制或优化返回数据负载。

匹配结果过多的过滤器示例:

code
{
"shards"
:
[
{
"name"
:
"9f8e7d6cshard"
,
"node"
:
"weaviate-1"
,
"searches"
:
{
"vector"
:
{
"details"
:
{
"total_took"
:
"61.5ms"
,
"filters_build_allow_list_took"
:
"41.2ms"
,
"filters_ids_matched"
:
"12840000"
,
"vector_search_took"
:
"15.6ms"
,
"knn_search_layer_0_took"
:
"14.8ms"
,
"hnsw_flat_search"
:
"false"
,
"objects_took"
:
"3.9ms"
}
}
}
}
]
}

此处 filters_build_allow_list_took 是耗时最长的部分,且 filters_ids_matched 超过一千万。由于过滤条件过于宽泛,构建允许列表成为主要成本。这是基数问题而非磁盘问题:需要重新设计过滤条件以减少匹配数量,或采用其他方式处理宽泛条件而非作为全局预过滤器。

压缩索引中重新评分导致磁盘访问的示例:

code
{
"shards"
:
[
{
"name"
:
"5c4b3a2fshard"
,
"node"
:
"weaviate-0"
,
"searches"
:
{
"vector"
:
{
"details"
:
{
"total_took"
:
"39.7ms"
,
"vector_search_took"
:
"35.1ms"
,
"knn_search_layer_0_took"
:
"9.2ms"
,
"knn_search_rescore_took"
:
"25.4ms"
,
"hnsw_flat_search"
:
"false"
,
"objects_took"
:
"3.8ms"
}
}
}
}
]
}

第 0 层的图遍历表现正常。向量搜索耗时中几乎全部时间都消耗在 knn_search_rescore 阶段,该阶段需要从磁盘读取全精度向量以对压缩候选结果进行重新评分。这表明延迟发生在重新评分阶段的磁盘访问而非图遍历过程,因此需要进一步调查存储配置和压缩参数。

总结

当客户报告查询性能问题时,查询分析已成为我们解决方案工程团队的首选排查手段。无需启用日志、等待查询变慢、再拼接节点日志,您只需设置一个标志位、执行一次查询,即可获得整个集群的性能分解数据。时间消耗的猜测被彻底消除,剩下的就是明确的性能瓶颈阶段需要修复。

如需帮助分析性能报告或制定优化方案,请随时联系我们。我们的解决方案团队会定期与客户分析此类数据,因此如果您是客户,通过常规的 Weaviate 联系渠道或支持通道联系我们是最高效的方式。如果您使用的是开源项目,请在社区论坛提问,我们将协助您完成分析。

深入阅读:

准备开始构建?

查看[快速入门教程](#),或注册免费的 Weaviate Cloud 账号。

[GitHub](#) [论坛](#) [X (Twitter)](#)

不想错过下一篇博客?

订阅我们的双周通讯以获取最新动态!

通过提交本表,我同意接受

[服务条款](#)

[隐私政策](#)