What to Check Before Tuning a Qdrant Collection
TL;DR · AI 摘要
调整Qdrant集合前需检查检索流程、指标配置及数据完整性,以确保优化效果。
核心要点
- 检查向量索引和payload索引完整性是调整前的必要步骤
- 构建带标签的查询集并选择匹配业务指标是评估优化效果的基础
- 混合搜索需优先测试sparse prefetch和fusion效果
结构提纲
按章节快速跳转。
- §引言
调整集合前需明确优化目标并验证基础配置正确性
说明dense-only和hybrid搜索的处理流程及对应配置项
建立常见问题与对应验证步骤的对应关系表
提供评估指标选择和结果分析的标准化流程
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 调整Qdrant集合前的检查
- 检索流程验证
- dense-only配置检查
- hybrid搜索验证
- 症状诊断
- 结果缺失诊断
- 重复结果处理
- 测量方法论
- 指标选择
- 基准测试
金句 / Highlights
值得收藏与分享的关键句。
未正确配置向量索引会导致检索结果失效,必须优先验证索引完整性
混合搜索优化需同时测试sparse prefetch和fusion效果,单独测试任一环节可能误导优化方向
当集合超出内存容量时,应优先测试内存布局优化而非直接增加检索阶段
调整 Qdrant 集合前需要检查的内容
返回搜索质量
Dylan Couzon
·
2026 年 8 月 20 日
在更改任何设置之前,请先明确“更优检索”对您的工作负载意味着什么。排名第一的文档正确、重排序器有更多候选、更低延迟、更小内存占用,这些目标需要不同的设置,因此请先确定您的核心目标。如果您的标注查询无法检测到您追求的改进,就无法判断更改是否真的带来了帮助。
部分设置用于验证正确性,而非性能调优。如果向量未被索引、稀疏向量缺少 IDF 修正因子、或 BM25 平均长度错误,结果将无效。在此之后进行的任何基准测试或对比都将反映一个存在缺陷的配置。本文将指导您如何检查每个设置,并说明正确状态应是什么样子。
您正在调整的检索流程
每个查询首先获取候选文档,然后进行排序。在纯密集检索中,一次向量搜索同时完成这两个步骤。混合检索则先通过稀疏预取获取精确术语,再通过融合算法合并密集和稀疏候选列表。如果存在重排序器,它将对顶级候选进行二次评分。
混合检索流程及其各阶段的专属设置。纯密集检索仅使用密集预取路径,因此此处唯一相关设置是 limit 和 hnsw_ef。
如果您运行纯密集检索但结果中缺少精确关键词,应首先测试混合检索。混合检索调优涉及请求形状、第二次预取的成本,以及如何验证融合效果是否优于单独预取。
调整前请检查:
- 确认向量已正确索引,且所有用于过滤的字段都有有效负载索引。集合详情和有效负载索引部分说明了需要检查的内容。
- 构建标注查询集,并选择与产品体验匹配的评估指标。标注查询将真实用户查询与应返回的文档进行配对。检索相关性评估部分详细说明了设置方法。
症状指引您找到问题起点
从故障模式而非配置参考入手。下表将每个症状映射到首个有用检查项及对应文章。
您观察到的现象 | 首要检查项 | 接下来阅读 ---|---|--- 无法区分信号与噪声 | 构建标注查询,选择指标并计算置信区间 | 本文 相关文档未出现 | 测量候选深度是否限制召回率 | 候选深度:多少检索量足够? 关键词、标识符、SKU 或错误码不匹配 | 添加稀疏预取并单独对比每个预取效果 | 如何在 Qdrant 调优混合检索 相关文档存在但顺序错误 | 调整融合参数。如果候选列表需要额外排序阶段,请测试重排序器 | 何时需要重排序器? 结果出现近似重复 | 测试最大边际相关性。如果某文档片段占满页面,请使用分组功能 | 搜索未达到 p95 目标 候选深度成本测量 | 在添加新检索阶段前测量候选深度成本 | 集合超出 RAM 容量时的处理
如何解读这些指标
操作流程保持一致:选择与产品体验匹配的指标,基于标注查询对比不同设置,并在新查询上验证最优方案。
Qdrant 的 API 和算法机制在所有集合中保持一致。参数扫描的结果取决于嵌入模型、数据集、查询组合、过滤器、索引状态、分片布局和部署方式。使用每个结果来选择适合自己集合的测试方案,然后仅保留标签支持的设置。
静默设置可能影响质量
在调整其他参数之前,请检查运行的阶段。每个先决条件对于特定集合都有正确的状态,且可能在没有错误提示的情况下失败。在进行基准测试或比较设置之前,请先修复这些问题,否则你测量的是配置错误而非权衡取舍。
密集搜索与索引
通过调用 GET /collections/{collection_name} 并比较 indexed_vectors_count 与 points_count 来查看向量是否已正确索引。在纯密集型集合中,索引完成后这两个数值应完全一致。在混合集合中(每个点包含一个密集向量和一个稀疏向量),indexed_vectors_count 应为 points_count 的两倍,因为 Qdrant 会单独统计每个向量。
如果索引数量较低,可能仍在进行索引、已停止,或某些分片的大小低于默认的 10,000 KB 索引阈值。请参阅索引优化器文档。Qdrant 仅在分片达到索引阈值后才会构建 HNSW 图。在此之前,它会直接搜索分片而不使用 HNSW,因此修改 hnsw_ef 参数不会产生任何效果。
full_scan_threshold 密集向量和稀疏向量在不同单位下有独立的阈值,因此在两者之间复制的值可能与预期相差甚远。密集向量的阈值以千字节为单位计算分片中的向量数量,默认值为 10,000。当分片中的向量数量少于该阈值,或过滤器匹配的点数少于该阈值时,搜索会转为精确扫描而非图搜索。
稀疏向量的阈值以向量数量为单位计算,默认值为 5,000,且仅在存在过滤器时生效。
稀疏检索
这些设置适用于集合包含数千个文档或数十亿个文档的情况。
Modifier.IDF 请将此修饰符用于来自 BM25 或 miniCOIL 的稀疏向量。这两种方法都会将逆文档频率(IDF)计算交给 Qdrant,Qdrant 会针对每个查询词在每个分片中计算 IDF 并按此加权词项。SPLADE 已经包含语料级词项加权,因此应用此修饰符会导致稀有性计算重复。
BM25 avg_len 将 avg_len 设置为 BM25 对字段进行词干提取并移除停用词后,该字段中词项的平均数量。BM25 使用此值来调整文档长度。不要根据原始词频估算该值。在本测试的五个数据集中,词干提取后的数量比原始词频低 15% 到 43%。正确的取值范围为 35.3 到 151.4,而默认值为 256。请使用与集合相同的词干提取器和停用词设置进行测量。
混合搜索
在分片集合中,融合位置的选择至关重要。当请求从单向量检索切换到融合时,score_threshold 在任何规模下都存在风险。
融合位置 在查询根节点,融合在每个分片返回候选结果后执行一次。在预取(prefetch)内部,融合在每个分片上执行。每个分片仅融合自己的候选结果,外部查询会根据这些分片本地的融合得分进行排序。结果会随着分片数量和点分布方式而变化,且没有错误提示会告知你发生了这种情况。当外部阶段重新评分其输出时,嵌套融合是刻意为之。在单分片集合中,两种位置的设置会产生相同的排序结果。
score_threshold 仅在您拥有该阶段结果的已测量最低接受分数时使用 score_threshold。从仅密集搜索复制的阈值在根级 RRF 或 DBSF 查询中是不安全的。Qdrant 将其与融合后的分数进行比较,而非密集或稀疏分数。这可能导致静默截断结果列表或返回无结果。请在标注查询上验证该阈值,或保持其未设置。
过滤搜索
对所有过滤字段建立索引。跳过一个字段的成本会随着集合规模和查询并发量增加而增长。
负载索引 一个健康的集合应为每个过滤字段建立负载索引。在数据摄入前创建这些索引。若后续添加新字段,Qdrant 不会自动添加感知过滤的 HNSW 边缘。您必须重建 HNSW 索引。Qdrant Cloud 的严格模式会拒绝对未建立索引字段进行过滤的查询。即使有正确索引,严格过滤仍可能降低召回率。ACORN 修复了这一问题,而 ACORN 测量了这一影响在一百万点上的表现。
按成本顺序进行更改
从无需重建集合或添加检索阶段的更改开始。只有在低成本选项无法解决症状时,才升级到更高成本层级。
层级 | 更改内容 | 适用范围 | 成本 --- | --- | --- | --- 无新检索工作 | 融合方法,RRF | k、权重 | 混合搜索 重新排序已检索列表,无需重建或额外检索阶段 | | | 扩展检索 | hnsw_ef | 密集搜索 | 增加搜索广度和查询时间 预取 | limit | 任何包含下游阶段的流水线 | 获取更多候选结果,增加查询时间 full_scan_threshold | 密集搜索(尤其是过滤搜索) | 对更大候选池使用精确扫描,可能增加查询时间 |
新阶段 | 稀疏预取 | 仅密集搜索 | 第二个索引、每个点的第二个向量、单分片上 0.6 到 1.5 毫秒的查询时间 重排序器 | 任何流水线 | 每个候选结果一次模型调用 | 重建 | 嵌入模型、m | 每个集合 | 重新索引集合。更改嵌入模型意味着为每个点生成新向量 量化 | 受内存限制的集合 | 重新索引,加上每个向量的压缩副本。此时排名质量取决于重新评分 |
仅在该更改解决了可测量约束时才考虑模型级重建,因为新嵌入模型意味着要重新嵌入每个点。如何选择嵌入模型涵盖了这一决策。当内存是限制因素时,Matryoshka 模型的 mrl 参数会缩短向量本身,这与通过量化压缩向量是不同的权衡。
在调优前选择指标
在比较设置前选择指标,因为指标决定了胜者。在我们的测试中,nDCG@10、MRR@10 和 Recall@100 各自指定了不同的最佳设置,且 Recall@100 在五个数据集中有四个与 nDCG@10 不一致。
nDCG@k 奖励位于顶部的相关结果,当标签分级时给予额外加分,并将每个查询归一化到完美排序。当多个结果间的排名顺序重要时使用它。
MRR@k 是第一个相关结果排名倒数的平均值。它询问的是您多快能找到一个好结果。当查询有一个正确答案时使用它。
Recall@k 表示所有相关文档中进入前 k 名的比例。当需要评估一个为其他系统提供输入的初始阶段时使用该指标。每个查询的 Recall@k 会受到相关文档数量的限制:包含 359 个相关文档的查询,其 Recall@100 最高只能达到 0.28,因为前 100 名最多只能容纳 100 个文档。但整体查询的平均值可能更高,因为相关文档数量较少的查询不受这个限制。在我们的测试中,某个数据集每个查询平均有 358.9 个相关文档,其最佳 Recall@100 达到了 0.3877。在确定 k 值前,请先统计每个查询的相关文档数量。
确保标签能检测到性能提升
检索相关性评估涉及构建标注数据集。数据集的规模决定了你是否能观察到任何检索优化效果。
当标注数据集足够大时,它才能区分你关心的改进与正常的查询间差异。数据集规模本身无法挽救一个不具代表性的集合。请从产品实际处理的查询混合集中抽取样本,包括重要的查询类型和过滤条件,并亲自抽查部分标签的准确性。
以下每个评估项都为每个查询和每个对比设置生成一个分数。请使用你的服务已发送给 Qdrant 的请求。无论你的管道执行的是纯密集检索、混合融合还是重排序器,评分机制都保持一致。
评分从指标本身开始。dcg 通过随着排名增长的折扣系数对分级相关性进行求和。ndcg_at_k 会对实际返回结果执行该求和,然后除以标签允许的最佳排序下的相同求和值。
import
math
def
dcg
(
gains
):
"""Relevance summed with a discount that grows with rank."""
return
sum
(
gain
/
math
.
log2
(
rank
+
2
)
for
rank
,
gain
in
enumerate
(
gains
))
def
ndcg_at_k
(
doc_ids
,
relevance
,
k
=
10
):
"""One query's ranking against the best ranking its labels allow."""
returned
=
[
relevance
.
get
(
doc_id
,
0
)
for
doc_id
in
doc_ids
[:
k
]]
ideal
=
sorted
(
relevance
.
values
(),
reverse
=
True
)[:
k
]
return
dcg
(
returned
)
/
dcg
(
ideal
)
if
any
(
ideal
)
else
0.0随后将标注查询同时运行在两种设置下。你编写搜索逻辑时,应将一种设置应用于服务已发送的请求,并按服务器返回的排序结果返回得分。请在请求中添加 with_payload=["doc_id"] 参数,确保每个返回结果都携带标签使用的 ID,或者如果点 ID 已经是文档 ID,则直接读取 point.id。score 函数会将每个列表转换为一个数值,每个查询的两个得分差值即为该查询的性能提升。
# 按照你现有标签所使用的文档 ID 来确定相关性。
qrels
=
{
"q1"
:
{
"doc-41"
:
1
,
"doc-77"
:
2
}}
# 你的标注查询。每个值是搜索发送给 Qdrant 的内容:文本或向量。
queries
=
{
"q1"
:
[
...
]}
# 正在测试的唯一参数,以你搜索应用的任何形式呈现。
current_setting
=
{
"hnsw_ef"
:
64
}
candidate_setting
=
{
"hnsw_ef"
:
256
}
def
search
(
query_id
,
query
,
setting
):
"""你编写这部分内容:你自己的 Qdrant 请求,应用设置参数。
按服务器排序后的结果返回点,每个点携带 doc_id。
"""
raise
NotImplementedError
def
score
(
queries
,
qrels
,
search
,
setting
):
"""每个查询一个 nDCG@10,对应一个设置。"""
return
{
query_id
:
ndcg_at_k
(
[
point
.
payload
[
"doc_id"
]
for
point
in
search
(
query_id
,
query
,
setting
)],
qrels
.
get
(
query_id
,
{}),
)
for
query_id
,
query
in
queries
.
items
()
}
candidate
=
score
(
queries
,
qrels
,
search
,
candidate_setting
)
current
=
score
(
queries
,
qrels
,
search
,
current_setting
)
per_query_gain
=
[
candidate
[
q
]
-
current
[
q
]
for
q
in
sorted
(
queries
)]两次调用必须仅在单一设置上存在差异。过滤器、查询结构和候选限制必须保持完全一致。对于 MRR@10 和 Recall@100,pytrec_eval 会基于相同的 qrels 计算两者。
通过有放回地重采样每个查询的增益,估算如果抽到不同查询集合,平均增益会有多大变化。得到的 95% 置信区间展示了这种抽样变化的可能范围。如果区间包含零,说明你的标注无法证明质量提升。
import
numpy
as
np
def
interval
(
per_query_gain
,
resamples
=
1000
,
seed
=
42
):
"""两个设置之间每查询增益均值的 95% 置信区间。"""
gains
=
np
.
asarray
(
per_query_gain
,
dtype
=
float
)
rng
=
np
.
random
.
default_rng
(
seed
)
draws
=
rng
.
integers
(
0
,
len
(
gains
),
size
=
(
resamples
,
len
(
gains
)))
return
np
.
percentile
(
gains
[
draws
]
.
mean
(
axis
=
1
),
[
2.5
,
97.5
])你评估的标注查询越多,测量的增益就越精确。在我们的数据集中,95% 置信区间通常在 nDCG@10 增益两侧延伸的范围如下:
标注查询数量
增益两侧的置信区间范围
25
0.047
50
0.035
100
0.025
200
0.018
300
0.015
你所需的标注数量主要取决于效应大小和查询间差异,而非单纯取决于数据集规模。
在我们的测试中,融合设置使 nDCG@10 提升了 0.012 到 0.038,这些增益来自于对已有集合进行调优,而非重建检索流程。
对于较大的增益,50 个标注查询就足够:0.038 的增益在 93% 的抽样中置信区间不包含零,而小于 0.02 的增益在 7% 到 38% 的抽样中达到该标准。在获得足够标注前,应将小幅增益视为未解决的问题。
在新查询上验证胜出设置
在相同查询上选择和评估的设置,在新查询上的表现通常会显得不如实际效果。将标注查询分为两半:在一半数据上选择胜出设置,然后在另一半数据上测量其增益。我们对每个数据集重复了 200 次这种分割。
胜出设置通常具有迁移能力。在新数据上重新对所有 30 个设置进行排序时,我们的选择通常排在前四,且在 0% 到 6% 的分割中落后于默认设置。增益确实会缩小:它保留了 67% 到 95% 的选择时报告的增益,因此应以新查询的数值为准。
如果你要比较单独重建的索引,应在将微小的nDCG@10差异视为调优收益之前,先检查两次构建之间的前10名一致性。在我们干净的重建测试中,查询采样对nDCG@10的影响程度超过了图结构变化的影响。
## 从单一变更开始
记录当前相关性指标和代表性查询集的p95延迟。从症状表中选择一个低成本变更,在新查询上验证该变更,只有在收益持续存在时才保留该变更。一旦建立好基准线,《候选深度:多少检索量足够?》将展示如何测试检索深度是否是限制因素。