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

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

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


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

想象一下这样的场景:

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

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

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

直到你遇到了 Qdrant:

python 复制代码
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 数据备份
Segment Shard 内的物理数据片段,独立索引和 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 三大必懂参数(调优的灵魂)

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

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

  • mef_construct创建 Collection 时设置,之后不可更改------它们是"盖楼图纸";
  • ef查询时动态设置,可随请求调整------它是"找房耐心"。
python 复制代码
# 查询时动态指定 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 再恢复)才会生效------就像装修完才想起加电梯,得先把楼拆了重盖。
python 复制代码
# 创建 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 维)
无量化 1x 0% ~30 GB
Scalar Quantization (SQ) 4x < 1% ~8 GB
Product Quantization (PQ) 8-32x 3-5% ~2-4 GB
Binary Quantization (BQ) 32x 5-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 统一了稠密、稀疏、混合三种搜索:

python 复制代码
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 参数 按请求动态调整 高精度场景调大,低延迟场景调小
量化 内存不足时启用 SQ 4x 压缩,<1% 精度损失
Segment 查询密集型用大 Segment,写入密集型用小 Segment 大段查询快、小段写入快
Shard 数量 初始 = 节点数 便于后续水平扩展
Replication 生产环境 ≥ 2 保证高可用
磁盘 仅用 NVMe SSD 全磁盘模式需要快速存储

八、技术栈与选型建议

8.1 完整技术栈一览

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

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

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

数据集 维度 QPS p50 延迟 p99 延迟
10M 768 10,000-15,000 2-5ms 5-10ms
10M 1536 5,000-8,000 5-10ms 10-20ms
100M+ 768 --- --- < 10ms

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

特性 Qdrant Chroma Milvus Pinecone Weaviate
语言 Rust Python + Rust (v1.0+) Go 等 专有(闭源) Go
开源 ✓ Apache 2.0 ✓ Apache 2.0 ✗ SaaS
定位 高性能生产级 嵌入式开发级 十亿级大规模 免运维托管 模块化/图式
稠密索引 Filterable HNSW HNSW / SPANN 多种(HNSW/IVF 等) 专有 HNSW
稀疏向量 ✓ 原生 + 倒排索引 有限支持 有限/逐步支持 有限
混合搜索 ✓ RRF 融合 有限 (where_document)
Payload ✓ 原生 JSON metadata(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 的深入对比,咱们下期见。

相关推荐
marvelyu2 小时前
每天10分钟学会OceanBase系列(Day 20):跨机房容灾实战——构建多数据中心高可用架构
java·大数据·数据库
ZCBUS实时计算3 小时前
金融证券实时数仓建设实践:轻量化实时计算平台落地,实现交易数据端到端秒级处理
大数据·数据库·数据仓库·金融·flink·dba·etl
zd2005723 小时前
海洋微生物数据库
数据库·宏基因组
海上小飞龙3 小时前
Redis 分布式锁原理:从 SET NX EX 到 Redisson 看门狗
数据库·redis·分布式
xiaoye-duck4 小时前
MySQL 表约束全解:从基础约束到外键关联规则
数据库·mysql
Hammer_Hans4 小时前
DFT笔记98
java·开发语言·数据库
迪康Defender5 小时前
从静态存储到动态流转:终端透明加密两种模式实战解析
运维·服务器·网络·数据库·其他
2601_965798475 小时前
Is Piroll WordPress Theme Worth It for Freelancers? Full Review
数据库·php
冷凝娇5 小时前
【MySQL】2026总结(二)
数据库·mysql
aaa小葵6 小时前
LangGraph
数据库·人工智能