深入理解 Qdrant 向量数据库:从 Filterable HNSW 到混合搜索,一篇讲透

读完这篇,你将彻底搞懂:Qdrant 凭什么用 Rust 单枪匹马扛住亿级向量搜索?“Filterable HNSW” 到底和普通 HNSW 差在哪?混合搜索里的 RRF 融合又是怎么一回事?


一、开场:先讲一个"电梯演讲"

想象一下这样的场景:

你的 RAG 应用上线了,老板很满意。直到某天,数据量从 10 万涨到 1000 万,噩梦开始了——

  • 用 FAISS:只有索引没有存储,没有元数据过滤,写"带条件的搜索"得自己造轮子,轮子还漏油;
  • 用 Elasticsearch:向量搜索比传统数据库慢 10-50 倍,QPS 上不去,老板看着监控曲线直摇头;
  • 用 Pinecone:性能确实好,但月费蹭蹭涨,而且数据进了"黑盒",想迁移都搬不走(供应商锁定)。

传统的向量数据库,似乎总要在速度、功能、可靠性里三选二——要么快但不持久,要么功能全但慢,要么啥都好但贵且锁。

直到你遇到了 Qdrant:

from qdrant_client import QdrantClient, models

client = QdrantClient("localhost", port=6333)   # 装个 Docker 就能跑,Rust 单二进制
client.create_collection(
    collection_name="demo",
    vectors_config=models.VectorParams(size=384, distance=models.Distance.COSINE),
)

# 向量 + 元数据(Payload)一起存,一个数据库搞定
client.upsert(collection_name="demo",
              points=[models.PointStruct(id=1, vector=[0.1, -0.3, 0.5, ...],
                                         payload={"category": "编程", "level": "入门"})])

# 带过滤条件的向量搜索:一次图遍历,边走边筛(这就是 Filterable HNSW)
results = client.search(
    collection_name="demo",
    query_vector=[0.1, -0.2, 0.4, ...],
    query_filter=models.Filter(must=[models.FieldCondition(
        key="category", match=models.MatchValue(value="编程"))]),
    limit=10,
)

几行代码,语义搜索 + 元数据过滤 + 持久化,全齐了。

一句话定位:Qdrant(发音 “quadrant”,意为"象限")是一个纯 Rust 编写的高性能开源向量搜索引擎。 它不是"给现有数据库加个向量功能",而是 2021 年从零开始为向量搜索量身定制的数据库——把过滤、持久化、分布式、量化全做进了引擎里(Apache 2.0 协议,免费商用)。


二、先看懂大框架:一座四层大楼

庖丁解牛的故事大家都听过——庖丁为什么游刃有余?因为他看透了牛的结构。理解 Qdrant 也一样,先看它的"骨架":

┌──────────────────────────────────────────────────────────┐
│                    客户端层 (Client Layer)                  │
│    Python · JS/TS · Rust · Go · .NET · Java · HTTP/gRPC   │
├──────────────────────────────────────────────────────────┤
│                      API 层 (API Layer)                    │
│                 REST API (HTTP) + gRPC API                 │
├──────────────────────────────────────────────────────────┤
│               集合管理层 (Collection Manager)               │
│       数据路由 · 分片管理 · 复制控制 · 查询分发               │
├───────────────┬──────────────────────────────────────────┤
│  ┌────────────┴─────────────┐                            │
│  │     Segment 引擎          │                            │
│  │  · Segment 管理器         │                            │
│  │  · WAL(写前日志)         │                            │
│  │  · 存储管理               │                            │
│  ├──────────────────────────┤                            │
│  │    索引层 (Index Layer)    │                            │
│  │  · Filterable HNSW(稠密) │                            │
│  │  · 倒排索引(稀疏向量)      │                            │
│  │  · Payload Index(元数据)  │                            │
│  │  · 量化器 (SQ/PQ/Binary)  │                            │
│  └──────────────────────────┘                            │
├──────────────────────────────────────────────────────────┤
│                     存储层 (Storage Layer)                 │
│       内存(向量热存)· 磁盘(MMAP)· RocksDB(元数据)       │
└──────────────────────────────────────────────────────────┘

记住这张图,整篇文章就是给这四层"讲故事":

  1. 客户端层:你用哪种语言、哪个协议访问它;
  2. API 层:REST + gRPC 双协议,把请求接进来;
  3. 集合管理层:你的请求该路由到哪个 Shard、哪个副本;
  4. Segment 引擎 + 索引层 + 存储层:数据到底怎么存、怎么索引、怎么搜——这是本文的主角。

2.1 数据组织层级:Collection → Shard → Segment

Qdrant 的数据组织,很像"国家 → 省 → 市 → 人"的行政区划:

Collection (集合)   ← 逻辑上的"一张表",配置维度、距离度量、索引参数
  │
  ├── Shard 1 ──── Replica 1 (Leader)     ← 水平分片,可分布在不同节点
  │     ├── Segment 1   [Points 1..100K]  ← 物理数据片段,独立索引
  │     ├── Segment 2   [Points 100K..200K]
  │     └── WAL         [未归档操作]
  │
  ├── Shard 1 ──── Replica 2 (Follower)   ← 副本,用 Raft 保持一致性
  │     └── Segment ...(同一数据副本)
  │
  └── Shard 2 ──── Replica 1
        └── ...
层级是什么类比
Collection向量和 Payload 的逻辑存储单元,配置维度、距离、索引参数传统数据库的"表"
Shard集合的水平分区,分布在不同节点上(可配 shard_number分库分表
Replica每个 Shard 的副本,Raft 保证一致性(可配 replication_factor数据备份
SegmentShard 内的物理数据片段,独立索引和 WAL一张表底下的数据文件
Point单个向量 + 其 Payload表中的一行

一句话人话:Collection 是逻辑上的"一张表",Shard 是"分库",Segment 是"分库里的数据文件",Point 是"一行"。

2.2 分布式一致性:Raft 共识

多副本数据不一致怎么办?Qdrant 用的是分布式系统界的"老熟人"——Raft 共识协议

Shard 0:
  ┌──────────────────────┐
  │ Replica 0 (Leader)   │ ← 所有写入都经过它,它说了算
  ├──────────────────────┤
  │ Replica 1 (Follower) │ ← 从 Leader 同步日志
  ├──────────────────────┤
  │ Replica 2 (Follower) │ ← 从 Leader 同步日志
  └──────────────────────┘
        Raft Log → 保证所有副本数据一致

Raft 干三件事:Leader 选举(Leader 挂了自动换人)、日志复制(写操作同步到所有副本)、安全保证(已确认的写入不会丢)。翻译成人话:“写数据先开会表决,多数派点头才算数。”


三、核心原理一:存储设计——向量和元数据怎么"同住一屋"

很多向量数据库是"向量归向量存,元数据归另一个库管"。Qdrant 的做法是:向量和 Payload(JSON 元数据)原生共存,一个数据库全搞定,不用额外接 MySQL/PostgreSQL。

3.1 Segment:物理存储的最小单元

每个 Segment 是一个独立的小世界:

Segment = 向量数据 + HNSW 索引 + Payload 存储 + WAL

这带来一个经典的权衡

策略优点缺点
少量大 Segment查询吞吐高(更大的 HNSW 图 → 更少跨段遍历)构建慢、写入慢
多量小 Segment索引快、写入快查询要扫描更多 Segment

类比:大 Segment 像"大卖场"——东西全,逛一次就买齐,但进货慢;小 Segment 像"便利店"——随时上新货,但买齐东西得跑好几家店。

3.2 WAL:先记账,再干活

写入流程中最关键的一步:

Client → upsert(points)
  │
  ▼
API Layer(验证请求)
  │
  ▼
Collection Manager(路由到目标 Shard)
  │
  ▼
Segment Engine
  │ 1. WAL(写前日志): 先把操作写进日志 → 持久化保证
  │ 2. 内存中更新 Segment
  │ 3. HNSW 图更新(异步增量)
  │ 4. Payload 索引更新
  │
  ▼
写入确认 → Client

这和 Redis 的 AOF 一个套路:先记账(写 WAL),再干活(改索引),确认持久化后才告诉客户端"成功"。如果进程崩溃,重启时从 WAL 重放没干完的活。写入是内存操作(快),WAL 是磁盘操作(持久)——快和稳,两头都占

3.3 存储模式:内存 vs 磁盘的灵活切换

Qdrant 用 MMAP(内存映射)+ 缓存 实现灵活的内存/磁盘平衡,元数据放 RocksDB:

配置说明适用
全内存向量和 HNSW 图都在 RAM最佳性能、低延迟场景
向量磁盘 + 图内存向量存磁盘,HNSW 图在内存内存受限
全磁盘向量和 HNSW 图都在 SSD(需 NVMe)极致内存节省

磁盘模式下,频繁访问的向量自动缓存在内存,冷数据按需从磁盘读取——就像你家冰箱:常喝的牛奶放冷藏室(内存缓存),囤的年货放地下室(磁盘),要用再去拿。

金句:“内存不够,磁盘来凑;热数据留内存,冷数据住磁盘。” 这也是 Qdrant 敢在单机上扛 1 亿+ 向量的底气之一。

另外,官方还提供快照(Snapshot)备份能力用于容灾与迁移,配合多副本策略,生产环境的可靠性就稳了。


四、核心原理二:Filterable HNSW——全场 MVP

这是整篇文章的重头戏,也是 Qdrant 最具标志性的创新,面试官最爱问。

4.1 先复习 HNSW:来自"跳表"的灵感

100 万条 768 维向量暴力搜索,一次要算 7.68 亿次浮点运算,延迟线性飙升。更致命的是维度灾难:高维空间里,B-Tree、KD-Tree 这些传统索引全部失效。

所以业界转向 ANN(近似最近邻)牺牲一点精度,换 10 倍甚至 100 倍的快。Qdrant 用的是 HNSW(Hierarchical Navigable Small World,分层可导航小世界),复杂度 O(log N)。

HNSW 的思路和跳表(Skip List)如出一辙——把一维的跳表推广到高维空间,变成"多层图":

Layer 2 (最稀疏):  ● ─────────────────────── ●       ← "高速公路",大跨步导航
                                ↓
Layer 1:            ● ───── ● ───── ● ───── ●       ← 中等跨度
                                ↓
Layer 0 (最密集):    ●──●──●──●──●──●──●──●──●──●     ← 精确搜索层,包含所有节点

关键规则:

  1. 第 0 层包含所有节点,连接局部最近的邻居;
  2. 上层是下层的随机子集,节点数指数递减;
  3. 上层节点连接更远的邻居,形成"高速公路";
  4. 同一节点在不同层之间垂直连接,方便"坐电梯下楼"。

搜索就是贪心 + 逐层下钻:从顶层随机入口开始 → 不断移动到离 query 最近的邻居 → 本地找不到更近的就降层 → 直到第 0 层 → 返回距离最小的 K 个邻居。

类比:像你在上海找一家藏在弄堂里的小吃店——先坐地铁大站快车到市中心(顶层,一步千里),再换乘公交到街区(中层),最后步行挨家挨户看门牌(第 0 层,精确到户)。“地铁—公交—步行”,就是 HNSW 的三层导航。

4.2 标准 HNSW 的致命伤:后置过滤会"漏人"

传统 HNSW 处理带条件的搜索,一般是先搜索、后过滤

标准 HNSW 查询流程:
  1. HNSW 图搜索 → 获得 topK 候选
  2. 后置过滤 → 只保留符合条件的
  问题: 符合条件的候选可能不足 K 个!

举个例子:你想搜"1 万篇文档里,最近邻的 10 篇 Python 教程"。HNSW 先给你 topK=10 个最近邻,结果这 10 篇全是 Java——过滤后 0 篇符合条件。要凑够 10 篇?那你得先搜 topK=1000,再做后置过滤,白白浪费 100 倍算力。

这就是"过滤漏斗效应":条件越苛刻,后置过滤越容易漏,召回率崩得越厉害。

4.3 Filterable HNSW:把过滤做进图遍历里

Qdrant 的解法很聪明——把过滤条件从"搜完之后"挪到"搜索过程中"

Filterable HNSW 查询流程:
  1. 在 HNSW 图遍历中实时检查过滤条件
  2. 跳过不符合条件的节点
  3. 继续搜索,直到找到 K 个符合条件的最近邻居
  优势: 保证返回 K 个结果,同时保持 HNSW 的效率

图遍历细节:
  当前节点 → 检查 Payload 过滤条件
    ├─ 通过 → 加入候选列表
    └─ 不通过 → 跳过,但继续沿图遍历

  不会因为一个节点不满足条件而终止搜索!

这就是 Qdrant 官网说的 “同程过滤”(Filtering at the same time)

Payload Index = 扩展了 HNSW 图结构的辅助索引

工作原理:
  · 在 HNSW 图构建时,将 Payload 过滤条件编码到图结构中
  · 搜索时:一次图遍历即可同时完成向量搜索和过滤
  · 不是前置过滤,也不是后置过滤 → 是"同程过滤"

类比:传统做法像"先面完所有候选人,再筛 985 学历"——费劲还可能一个都不符合;Filterable HNSW 像面试官一边看简历一边筛,不合条件的简历直接跳过,面试流程永不中断,直到招满为止。

一句话人话:别人家是"先搜再筛",筛完可能凑不齐人;Qdrant 是"边走边筛",保证一次遍历就把 K 个符合条件的邻居全找齐。

与普通 HNSW 实现的区别(面试金句):

维度普通 HNSW(如 FAISS/部分实现)Filterable HNSW(Qdrant)
过滤时机搜索后置过滤,可能不足 K 个图遍历中同程过滤,保证 K 个
过滤方式依赖外部工具(如 SQL 子查询)Payload Index 编码进图结构
复杂过滤多条件需多次 IO / 精度下降一次遍历内完成,可启用 ACORN

4.4 三大必懂参数(调优的灵魂)

参数默认值管什么调大调小
m16图连通性(每节点最大邻居数)图更密、精度高、内存大涨省内存、精度降
ef_construct索引构建时的探索范围建索引慢、图质量高建得快、精度降
ef自动查询时的探索范围搜索慢、召回率高搜索快、可能漏真邻居

三个参数的生命周期完全不同:

  • mef_construct创建 Collection 时设置,之后不可更改——它们是"盖楼图纸";
  • ef查询时动态设置,可随请求调整——它是"找房耐心"。
# 查询时动态指定 ef(高精度场景调大)
results = client.search(
    collection_name="docs",
    query_vector=vector,
    search_params=models.SearchParams(ef=200),
    limit=10,
)

一句话记忆:ef_construct 管"盖楼质量",ef 管"找房耐心",m 管"楼里邻居密度"。 精度和速度,永远是鱼和熊掌。

4.5 Payload Index 与 ACORN

Payload Index 是把过滤做进 HNSW 的关键,也是踩坑高发区:

  • 最佳实践:在建索引之前创建 Payload Index(效率最高);
  • 如果已有大量数据后再创建:Payload Index 不会立即影响 HNSW 图,需要触发 Segment 重建(比如临时设置 m=0 再恢复)才会生效——就像装修完才想起加电梯,得先把楼拆了重盖。
# 创建 Payload Index(在建索引前!)
client.create_payload_index(
    collection_name="docs",
    field_name="category",
    field_schema="keyword",  # 或 "integer" / "float" / "text" 等
)

另外,对于多个高基数字段过滤的场景,Qdrant 还实现了 ACORN 算法来进一步改善搜索精度——多个复杂过滤条件叠加时,同程过滤的图结构可能会失真,ACORN 就是专门来"纠偏"的。

金句:“Filterable HNSW 是 Qdrant 的灵魂:别人把过滤当附加功能,它把过滤做进了索引的 DNA。”


五、核心原理三:向量量化——内存不够,精度来凑

向量数据库最大的成本往往是内存。10M 条 768 维的 float32 向量,裸存要 ~30 GB。Qdrant 提供三种量化策略,在内存和精度之间做交易:

量化类型压缩比精度损失内存占用(10M × 768 维)
无量化1x0%~30 GB
Scalar Quantization (SQ)4x< 1%~8 GB
Product Quantization (PQ)8-32x3-5%~2-4 GB
Binary Quantization (BQ)32x5-10%~1 GB

5.1 Scalar Quantization(标量量化):把浮点"四舍五入"成整数

SQ 的思路极其朴素:每个 float32 占 4 字节,干脆缩成 1 字节的 int8:

Float32: [0.023456, -0.145678, ...]   每个值 4 bytes
  ↓ 标量量化
Int8:    [23, -146, ...]              每个值 1 byte

4 倍压缩,精度损失却 < 1%——性价比之王,官方建议"内存不足时优先启用 SQ"。

5.2 Product Quantization(乘积量化):向量切成"盲盒段",每段查码本

PQ 更激进:把高维向量切成 N 段,每段用"码本"编码成一个小数字:

向量 [384维]
  ↓ 切成 N 段
  [48维] [48维] ... [48维]
    ↓      ↓           ↓
  Code 3 Code 7 ... Code 12    ← 每个子段从码本里挑最接近的码字

就像把一道菜切成几份分别"快递",每份只留一个标签,收货时按标签组合。8-32 倍压缩,代价是 3-5% 精度损失,适合内存极其紧张的场景。

一句话人话:SQ 是把每个数"四舍五入"(4 倍省内存),PQ 是把向量"切片打码"(8-32 倍省内存),精度换内存,各取所需。

5.3 量化与存储组合拳

量化(压缩单条向量)+ 磁盘模式(卸载冷数据)可以叠加使用——内存不够时先上 SQ(4x 压缩、<1% 损失),还不够再让向量住磁盘,层层递进,像"先节流再开源"。


六、核心原理四:混合搜索——稠密与稀疏的"左右互搏"

6.1 为什么需要混合搜索

稠密向量(语义)和稀疏向量(关键词)各有所长,也各有盲区:

场景稠密向量稀疏向量
“climate change” 找 “global warming”✓ 语义匹配✗ 词不匹配
找包含 “API_KEY_XYZ123” 的文档✗ 可能遗漏✓ 精确匹配
找技术术语 “Rust cargo build”✓ 理解编程话题✓ 精确术语匹配

类比:稠密向量像"阅读理解高手",懂引申义但记不住生僻词;稀疏向量像"字典扫描机",字字精确但对"气候变化"和"全球变暖"这种同义词一概不知。合体才能打。

6.2 稀疏向量:倒排索引

Qdrant 用倒排索引存储稀疏向量——只存非零维度,一个字一个词条:

稀疏向量: {word_id_123: 0.8, word_id_456: 0.5, word_id_789: 1.2, ...}

倒排索引:
  word_id_123 → [Point(id=1, weight=0.8), Point(id=3, weight=0.6), ...]
  word_id_456 → [Point(id=1, weight=0.5), ...]
  word_id_789 → [Point(id=1, weight=1.2), Point(id=5, weight=0.9), ...]

查"内存泄漏",直接查词条 内存泄漏 → 拿到所有包含它的 Point,精确匹配,一个不漏

6.3 Universal Query API:一套 API 三种玩法

Qdrant 的 Query API 统一了稠密、稀疏、混合三种搜索:

from qdrant_client import QdrantClient, models

client = QdrantClient("localhost", port=6333)

# 玩法一:纯稠密向量搜索(语义)
results = client.query(
    collection_name="docs",
    query=models.NearestQuery(nearest=query_vector),
    limit=10,
)

# 玩法二:纯稀疏向量搜索(关键词)
results = client.query(
    collection_name="docs",
    query=models.NearestQuery(nearest=sparse_vector),
    limit=10,
)

# 玩法三:混合搜索(RRF 融合)
results = client.query(
    collection_name="docs",
    prefetch=[
        models.Prefetch(query=models.NearestQuery(nearest=query_vector), limit=20),
        models.Prefetch(query=models.NearestQuery(nearest=sparse_vector), limit=20),
    ],
    query=models.FusionQuery(fusion=models.Fusion.RRF),
    limit=10,
)

注意写法:prefetch 两个"预检索",再 query 一个"融合器"——每个检索通道各取前 20,最后融合取 top 10。这就是"各显神通,再论功行赏"。

6.4 RRF(Reciprocal Rank Fusion)融合算法

融合的关键问题:两个通道的分数量纲完全不同(余弦距离 vs 关键词权重),没法直接相加。RRF 的聪明之处在于——不比分数,比排名

RRF Score = Σ ( 1 / (k + rank_i) )

其中:
  rank_i = 结果在第 i 个搜索结果列表中的排名
  k      = 平滑常数(默认 60)

具体演算:

结果 A: 稠密搜索排名 #1, 稀疏搜索排名 #3
  RRF = 1/(60+1) + 1/(60+3) = 0.0164 + 0.0159 = 0.0323

结果 B: 稠密搜索排名 #2, 稀疏搜索排名 #1
  RRF = 1/(60+2) + 1/(60+1) = 0.0161 + 0.0164 = 0.0325  → 排名更高

结果 C: 稠密搜索排名 #5, 稀疏搜索排名 #5
  RRF = 1/(60+5) + 1/(60+5) = 0.0308  → 排名最低

为什么用排名不用分数? 因为两个通道的分数量纲完全不同(余弦距离 vs 关键词权重),没法直接相加;而排名是"序数",天然可比。就像乒乓球比赛:A 队冠军 + 季军,和 B 队亚军 + 亚军,比的是"名次之和"而不是"得分之和"——因为两个赛场的裁判打分标准不一样。

一句话人话:RRF 就是"多个评委排座次,名次越靠前权重越高,求和定总榜"——绕开了不同打分体系的量纲问题。


七、一次查询的五段旅程(查询执行流程)

client.query() 时,Qdrant 内部走一条清晰的流水线:

Client → search / query
  │
  ▼
API Layer(验证请求、鉴权)
  │
  ▼
Collection Manager(确定目标 Shard(s),查询分发)
  │
  ▼
Segment Engine(每个 Segment 并行执行)
  │ 1. 如果是过滤查询:
  │    → Filterable HNSW: 遍历图时实时检查过滤条件(同程过滤)
  │ 2. 如果是混合查询:
  │    → 并行执行稠密 + 稀疏搜索
  │    → RRF 融合结果
  │ 3. 如果是纯向量搜索:
  │    → HNSW 图贪心搜索,逐层下钻
  │
  ▼
结果合并(每个 Shard → 全局)
  │ 去重、排序、取 topK
  ▼
最终 topK → Client

三个关键设计:

  1. 过滤查询走同程过滤:Filterable HNSW 在遍历时实时检查条件,跳过不满足的节点,保证凑够 K 个;
  2. 混合查询并行 + 融合:稠密、稀疏两条通道独立并行搜索,再用 RRF 融合——互不干扰,最后"论功行赏";
  3. 分段合并:每个 Segment 先算局部 topK,再到 Shard/全局合并去重——典型的"分而治之"。

优化建议速查表(生产必看)

方面建议理由
Payload Index在大量写入之前创建事后创建要触发 Segment 重建
ef 参数按请求动态调整高精度场景调大,低延迟场景调小
量化内存不足时启用 SQ4x 压缩,<1% 精度损失
Segment查询密集型用大 Segment,写入密集型用小 Segment大段查询快、小段写入快
Shard 数量初始 = 节点数便于后续水平扩展
Replication生产环境 ≥ 2保证高可用
磁盘仅用 NVMe SSD全磁盘模式需要快速存储

八、技术栈与选型建议

8.1 完整技术栈一览

层面技术作用
核心语言Rust零成本抽象、无 GC、内存安全
API 层REST API (HTTP) + gRPC双协议支持
SDKPython, JS/TS, Rust, Go, .NET, Java多语言客户端
稠密向量索引Filterable HNSWO(log N) 搜索 + 同程过滤
稀疏向量索引倒排索引精确关键词匹配
混合搜索RRF (Reciprocal Rank Fusion)稠密 + 稀疏融合
量化SQ / PQ / Binary Quantization4-32x 内存压缩
Payload 存储原生 JSON 存储向量 + 属性共存
持久化WAL + RocksDB写入持久化 + 元数据存储
内存管理MMAP + 缓存灵活的内存/磁盘平衡
分布式Raft 共识 + 分片 + 复制集群一致性
SIMDAVX2 / AVX-512距离计算 4-8x 加速

为什么选 Rust? 四个字:可预测。没有 GC,就没有"垃圾回收引发的延迟尖峰";零成本抽象 + SIMD(AVX2/AVX-512),让距离计算快 4-8 倍;没有 JIT 抖动,p99 延迟稳如老狗。对实时检索来说,"偶尔慢一次"比"每次都慢一点"更致命。

8.2 性能基准参考(官方单节点,8 CPU / 64GB RAM)

数据集维度QPSp50 延迟p99 延迟
10M76810,000-15,0002-5ms5-10ms
10M15365,000-8,0005-10ms10-20ms
100M+768< 10ms

8.3 和其他向量数据库怎么选

特性QdrantChromaMilvusPineconeWeaviate
语言RustPython + Rust (v1.0+)Go 等专有(闭源)Go
开源✓ Apache 2.0✓ Apache 2.0✗ SaaS
定位高性能生产级嵌入式开发级十亿级大规模免运维托管模块化/图式
稠密索引Filterable HNSWHNSW / SPANN多种(HNSW/IVF 等)专有HNSW
稀疏向量✓ 原生 + 倒排索引有限支持有限/逐步支持有限
混合搜索✓ RRF 融合有限 (where_document)
Payload✓ 原生 JSONmetadata(16KB 限制)
量化✓ SQ/PQ/BQ有限
分布式✓ Raft + 分片有限 (Client-Server)✓ 原生分布式✓ 完全托管企业版
运维复杂度极低极低(贵)

一句话选型

  • 个人项目 / RAG 原型 → Chroma(pip install 即用,零门槛)
  • 百万~亿级、生产级搜索/推荐系统 → Qdrant(Rust 性能 + 过滤 + 混合搜索全都要)
  • 十亿级、超大集群 → Milvus(原生分布式,规模天花板最高)
  • 不想运维、预算充足、怕麻烦 → Pinecone(全托管,贵且锁定)
  • 想要图数据库能力 / 模块化插件生态 → Weaviate

Qdrant 的最优场景:生产级搜索/推荐系统——既要过滤又要语义、既要混合检索又要量化省内存,还要分布式高可用,Qdrant 是"六边形战士"。


九、金句总结(看完只需要记住这几句)

  1. Qdrant = Rust 的性能 + Filterable HNSW 的同程过滤 + 原生 JSON Payload + 混合搜索,从零开始为向量搜索而生。
  2. "先搜再筛"会漏人,"边走边筛"才稳:Filterable HNSW 把过滤做进图遍历,一次遍历凑够 K 个,这是它区别于普通 HNSW 的灵魂。
  3. HNSW = 跳表的高维版:上层"高速公路"大跨步,第 0 层精确找,复杂度 O(log N)。
  4. ef_construct 管盖楼质量,ef 管找房耐心,m 管邻居密度——精度和速度是鱼和熊掌。
  5. 量化是内存和精度的交易:SQ 4 倍压缩 <1% 损失,PQ 8-32 倍压缩 3-5% 损失,BQ 32 倍压缩 5-10% 损失。
  6. 混合搜索不是加法,是"论功行赏":RRF 不比分数比排名,绕开不同通道的量纲问题。
  7. 写入先记账(WAL)再干活:确认持久化才返回成功,崩溃也不丢数据。

课后思考题(检验你是否真懂了):

  • 为什么 Filterable HNSW 是"同程过滤"而不是"前置过滤"?前置过滤先把候选集筛出来再搜索,和同程过滤比,各自有什么代价?
  • 如果过滤条件极苛刻(比如 1 万篇里只有 3 篇符合),Filterable HNSW 还能保证返回 K=10 个结果吗?它的图遍历会怎么表现?
  • RRF 里 k=60 这个常数的作用是什么?如果 k 调到 0,排名第 1 的结果会怎样?k 调得非常大呢?
  • 为什么 ef_construct 创建后不可改,而 ef 可以随请求动态调整?这背后的设计考量是什么?
  • 量化压缩的是"向量本身",那 HNSW 图的结构会被压缩吗?压缩之后搜索路径还是原来的吗?

参考资料


如果这篇文章对你有帮助,欢迎点赞、收藏、关注。下一篇可以聊聊:Milvus / Weaviate / Pinecone 的索引原理与 Qdrant 的深入对比,咱们下期见。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐