Qdrant 数据结构、索引与过滤机制详解
Qdrant 是一个面向向量检索的数据库,常用于语义搜索、RAG、推荐系统和知识库等场景。相比传统关系型数据库,Qdrant 最核心的能力是对高维向量进行高效的相似度检索。要理解 Qdrant 的使用方式,首先需要掌握 Collection、Point、Vector、Payload、索引和过滤这几个核心概念。
Collection 和 Point
Qdrant 中最基本的数据组织单位是 Collection 和 Point。可以把 Collection 类比成关系型数据库中的 Table,用来存储同一类数据;Point 则是 Collection 中的一条数据记录。
例如可以创建一个名为 defectdojo_knowledge 的 Collection,用于保存知识库中的文本和对应向量。一个 Collection 可以包含大量 Point,而每个 Point 都有自己的唯一 ID、向量以及 Payload。
需要注意的是,这只是帮助理解的类比。Collection 并不是传统数据库表的简单复制,它在创建时还需要定义向量相关配置,例如向量维度、距离计算方式以及是否使用稀疏向量等。
一个 Point 的完整结构
Qdrant 中一个 Point 通常包含三个核心部分:
Point(
id=uuid5(NAMESPACE_URL, "audit:{workflow_id}:0"),
vector=[dense embedding…],
payload={
"source_type": "audit",
"source_id": workflow_id,
"text": "...",
"content_hash": "sha256(...)",
"embedding_model": "text-embedding-...",
"source": f"workflow:{workflow_id}",
"workflow_id": workflow_id,
"intent": "triage",
"intents": ["import_scan", "triage"],
"confidence": 0.92,
"outcome": "completed",
"verification_status": "observed",
}
)
其中,id 用于唯一标识 Point,可以使用整数或 UUID。vector 是文本经过 Embedding 模型转换后的向量,用于后续的向量相似度搜索。payload 则用于保存与这条向量相关的业务信息和元数据。
因此,可以简单理解为:
Point
├── ID
├── Vector
└── Payload
Vector 和 Payload 虽然都属于 Point,但用途完全不同。Vector 主要用于“计算相似度”,Payload 主要用于“保存和描述数据”。
Vector:Qdrant 进行语义检索的基础
向量是 Qdrant 最核心的数据类型之一。在进行语义搜索之前,需要先使用 Embedding 模型将文本转换为固定维度的浮点数组。
例如:
SQL injection vulnerability
经过 Embedding 模型之后,可能变成:
[0.128, -0.372, 0.614, 0.051, ...]
这个数组就是向量。
当用户输入新的查询语句时,系统同样会将查询转换成向量,然后计算查询向量与数据库中已有向量之间的相似度,最后返回最相近的数据。
常见的向量距离或相似度方式包括:
Cosine
Dot Product
Euclidean
不同距离指标适用于不同的 Embedding 模型和业务场景,因此在创建 Collection 时通常需要提前确定。
Payload:保存业务数据和元数据
如果说 Vector 负责“找到相关数据”,那么 Payload 负责“告诉我们找到的数据是什么”。
Payload 使用 JSON 风格的数据结构,可以保存字符串、数字、布尔值、数组以及对象等信息。例如:
{
"source_type": "audit",
"workflow_id": 1024,
"intent": "triage",
"confidence": 0.92,
"outcome": "completed"
}
这里的 source_type、workflow_id、intent 等字段都属于 Payload。
实际使用中,Payload 通常会保存三类信息:第一类是原始文本,例如用于生成 Embedding 的 text;第二类是数据来源,例如文档 ID、业务 ID、数据类型;第三类是检索过程中需要使用的结构化属性,例如分类、标签、状态和时间等。
因此,在 RAG 系统中经常会出现这样的关系:
文本
↓
Embedding
↓
Vector ───────→ 用于相似度搜索
原始文本 + Metadata
↓
Payload ──────→ 用于返回结果和条件过滤
Content Hash 和 Embedding Model
在实际项目中,Payload 往往还会保存一些用于数据同步和数据管理的字段。
例如:
{
"text": "...",
"content_hash": "9f86d081884c...",
"embedding_model": "text-embedding-3-small"
}
content_hash 可以用于判断文本内容是否发生变化。当数据重新同步时,如果新旧文本对应的 Hash 相同,就可以跳过重新 Embedding,从而减少计算成本。
而 embedding_model 用于记录当前向量是由哪个 Embedding 模型生成的。当系统更换 Embedding 模型后,可以通过这个字段识别不同模型产生的向量,避免由于向量空间不同而造成数据混用。
什么是 Payload Index
当 Collection 中只有少量 Point 时,即使没有索引,也可以进行简单过滤。但随着数据量增长,如果每次查询都需要检查大量 Point 的 Payload,效率会受到影响。
因此,Qdrant 提供了 Payload Index。
例如有下面这样的数据:
{
"source_type": "audit",
"severity": "high",
"cwe_id": "CWE-89"
}
如果经常需要执行:
source_type = "audit"
或者:
cwe_id = "CWE-89"
就可以针对这些字段建立 Payload Index。
例如:
from qdrant_client.models import PayloadSchemaType
client.create_payload_index(
collection_name="defectdojo_knowledge",
field_name="source_type",
field_schema=PayloadSchemaType.KEYWORD,
)
建立索引后,Qdrant 可以更高效地处理对应字段的条件查询。
如何选择 Payload Index 类型
Qdrant 支持针对不同数据类型建立相应的 Payload Index。例如字符串类别通常使用 KEYWORD,数值字段可以使用数值类型索引,时间字段也可以使用相应的范围查询能力。
例如:
source_type → KEYWORD
intent → KEYWORD
severity → KEYWORD
cwe_id → KEYWORD
score → 数值
created_at → 时间
并不是所有字段都需要建立索引。通常应该根据实际查询条件决定哪些字段建立索引。一个简单的原则是:
经常出现在过滤条件中的字段值得考虑建立索引,不参与查询的字段通常没有必要专门建立索引。
什么是过滤
Payload Index 解决的是“如何更高效地查找”,而 Filter 解决的是“查询哪些数据”。
例如用户希望查询:
source_type = audit
可以构造这样的过滤条件:
from qdrant_client.models import Filter, FieldCondition, MatchValue
query_filter = Filter(
must=[
FieldCondition(
key="source_type",
match=MatchValue(value="audit")
)
]
)
这表示只允许 source_type 为 audit 的 Point 参与此次检索。
如果需要多个条件,还可以组合:
query_filter = Filter(
must=[
FieldCondition(
key="source_type",
match=MatchValue(value="audit")
),
FieldCondition(
key="severity",
match=MatchValue(value="high")
)
]
)
这相当于:
source_type = "audit"
AND
severity = "high"
过滤和向量检索可以同时存在。Qdrant 先根据条件限制候选数据范围,再进行向量相似度检索。
Filter 和向量检索的区别
这是使用 Qdrant 时一个非常容易混淆的地方。
Filter 解决的是结构化条件筛选,例如:
cwe_id = CWE-89
severity = high
source_type = audit
向量检索解决的是语义相似性判断。
例如:
查询:
数据库查询缺少参数化导致安全问题
即使某篇文档没有完全出现相同的句子,只要语义比较接近,它仍然有可能通过向量检索被召回。
所以:
Filter
→ 筛选哪些数据可以参与检索
Vector Search
→ 判断这些数据与查询有多相似
二者是互补关系,而不是相互替代。
Dense Vector 和 Sparse Vector
除了 Dense Vector 之外,Qdrant 还支持 Sparse Vector。
Dense Vector 通常适合进行语义检索。例如:
“SQL injection”
和:
“数据库查询参数缺少安全处理”
虽然字面表达不同,但语义可能非常接近,因此 Dense Vector 能够较好地进行召回。
Sparse Vector 则更适合关键词和专业术语,例如:
CWE-89
CVE-2025-1234
SQL Injection
SSRF
在知识库、搜索系统和技术文档场景中,Dense 与 Sparse 结合可以同时兼顾语义匹配和关键词匹配。
因此一个 Collection 可以设计成:
Collection
├── Dense Vector
├── Sparse Vector
└── Payload
最终执行 Hybrid Search。
Qdrant 中的完整检索流程
结合前面的几个概念,一个典型的 Qdrant 检索过程如下:
用户查询
↓
Embedding
↓
Query Vector
↓
┌──────────────────────────┐
│ Qdrant │
│ │
│ Payload Filter │
│ ↓ │
│ Dense / Sparse Search │
│ ↓ │
│ Similarity Ranking │
└──────────────────────────┘
↓
Top-K Point
↓
读取 Payload
↓
返回给应用程序 / LLM
例如查询一个知识库时,可以先使用 source_type、category 等 Payload 字段缩小搜索范围,然后使用 Dense Vector 判断语义相关性,再结合 Sparse Vector 处理精确关键词,最终返回 Top-K 结果。
总结
Qdrant 可以从几个核心概念来理解:
Collection
↓
数据集合
Point
↓
一条完整数据
Vector
↓
用于相似度检索
Payload
↓
保存文本和业务 Metadata
Payload Index
↓
加速结构化条件查询
Filter
↓
限制检索范围
Dense / Sparse Search
↓
进行语义或关键词相关性检索
理解这些概念之后,Qdrant 的整体工作方式就比较清晰了:Payload 负责描述数据,Payload Index 负责提高结构化查询效率,Filter 负责缩小候选范围,而 Vector Search 则负责从候选数据中找到与查询最相关的内容。

272

被折叠的 条评论
为什么被折叠?



