这是《LLM 应用开发 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 向量库负责存储向量、元数据和索引,并支持相似度查询、过滤、更新和分布式扩展。选型时要同时看召回、延迟、规模、事务、运维和生态。
10.1 核心能力
| 能力 | 说明 |
|---|---|
| 向量索引 | ANN 近似最近邻 |
| 标量过滤 | 权限、租户、时间、类型 |
| 全文检索 | BM25 或稀疏向量 |
| 混合查询 | 向量 + 关键词 + 过滤 |
| upsert | 按 ID 更新 |
| 删除 | 物理或逻辑删除 |
| 持久化 | 快照、日志、备份 |
| 监控 | QPS、延迟、召回率 |
10.2 常见方案
| 方案 | 特点 |
|---|---|
| pgvector | PostgreSQL 扩展,事务和运维友好 |
| Elasticsearch | 搜索生态强,支持混合检索 |
| Milvus | 面向大规模向量,组件较多 |
| Qdrant | 向量优先,过滤能力强 |
| Weaviate | 内置模块和混合检索 |
| Chroma | 轻量,适合原型和中小场景 |
| OpenSearch | 生态接近 Elasticsearch |
| FAISS | 库而非数据库,需要自建服务 |
没有绝对最优。小规模知识库可从 pgvector 开始;强搜索需求可看 Elasticsearch/OpenSearch;大规模高并发向量场景再评估专用库。
10.3 索引类型
| 类型 | 特点 |
|---|---|
| Flat | 精确检索,小数据集适用 |
| IVF | 聚类分桶,需训练 |
| HNSW | 图索引,召回和延迟平衡好 |
| DiskANN | 面向磁盘大规模数据 |
| 量化 | 压缩内存,可能损失精度 |
HNSW 示例参数:
| 参数 | 影响 |
|---|---|
M |
图连接数,越大召回越高、内存越多 |
ef_construction |
建索引搜索宽度,影响构建时间 |
ef_search |
查询搜索宽度,影响召回和延迟 |
ANN 是近似检索,需要用业务查询集测召回。
10.4 表设计
pgvector 示例:
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE doc_chunk (
chunk_id BIGINT PRIMARY KEY,
doc_id TEXT NOT NULL,
tenant_id TEXT NOT NULL,
acl TEXT[] NOT NULL DEFAULT '{}',
title_path TEXT NOT NULL DEFAULT '',
content TEXT NOT NULL,
embedding vector(1536) NOT NULL,
effective_time TIMESTAMPTZ,
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX doc_chunk_embedding_idx
ON doc_chunk
USING hnsw (embedding vector_cosine_ops);
过滤索引:
CREATE INDEX doc_chunk_tenant_idx
ON doc_chunk(tenant_id, active);
10.5 查询示例
SELECT chunk_id, doc_id, title_path, content,
1 - (embedding <=> $1::vector) AS score
FROM doc_chunk
WHERE tenant_id = $2
AND active = TRUE
AND $3 = ANY(acl)
AND effective_time <= NOW()
ORDER BY embedding <=> $1::vector
LIMIT 30;
查询要点:
- 先做权限和租户过滤;
- 限制返回字段;
- 设置 top K;
- 设置分数阈值需谨慎;
- 保存 chunk_id 供引用;
- 超时后可降级关键词检索。
10.6 标量过滤
常见过滤:
tenant_id = current_tenant
acl @> current_roles
doc_type IN (policy, faq, manual)
effective_time <= now()
expires_time > now()
language = zh-CN
active = true
注意:
- 权限过滤必须在数据库或检索引擎内完成;
- 不能先取 top K 再由应用补权限;
- 高选择性过滤可能影响 ANN 性能;
- 大租户可分索引或分区;
- 过滤字段变更要有同步机制。
10.7 写入与更新
上传文档
-> 解析
-> 切块
-> embedding
-> 批量 upsert
-> 状态置为 indexed
一致性建议:
- 使用稳定 chunk ID;
- 文档级别版本号;
- 先写新版本,再原子切换;
- 删除文档时标记 inactive 并异步清理;
- 定期对账源存储和向量库;
- 记录失败队列并重试。
10.8 混合检索
Query
|-- dense vector search
|-- sparse / BM25 search
|-- metadata filter
-> candidate union
-> fuse
-> rerank
融合示例:
def rrf_fusion(result_lists, k=60, top_n=20):
scores = {}
for results in result_lists:
for rank, item in enumerate(results, start=1):
scores[item] = scores.get(item, 0) + 1 / (k + rank)
return sorted(scores, key=scores.get, reverse=True)[:top_n]
混合检索适合错误码、产品型号、专有名词、中英混合和长尾术语。
10.9 容量与性能
| 指标 | 说明 |
|---|---|
| vectors | 向量总量 |
| dimensions | 维度 |
| QPS | 查询吞吐 |
| p50 / p95 / p99 latency | 延迟 |
| recall@K | 召回质量 |
| filter ratio | 过滤后比例 |
| write throughput | 写入吞吐 |
| memory usage | 内存占用 |
压测要使用真实查询分布和真实过滤条件,不能用随机向量代表业务负载。
10.10 运维
必备工作:
- 备份向量数据或可重建管道;
- 监控磁盘、内存和查询延迟;
- 记录索引版本和模型版本;
- 支持重建索引;
- 设置慢查询告警;
- 控制批量写入速率;
- 灰度切换索引;
- 定期做召回回归。
推荐保留原始文档、解析结果和 chunk,向量丢失时可以重建。
本章小结
向量库是 RAG 的检索基础设施。设计时要先明确规模、过滤条件、混合检索和一致性要求,再选择方案。权限过滤必须发生在检索阶段,索引和模型版本要可追踪,原始数据要可重建。
思考题
- 为什么不能先向量检索 top K 再做权限过滤?
- HNSW 的
ef_search如何影响召回和延迟? - 混合检索解决什么问题?
- 文档更新时如何设计 chunk ID 和版本切换?
- 如何为向量库设计容量评估?