前几篇把 RAG 的各个环节拆开讲了:Loader、切分、Embedding。现在到了存储环节:向量数据库。
demo-23 用的是 MemoryVectorStore,适合理解原理,但进程一退出数据就没了,也撑不起真实业务。这一篇讲清向量数据库是什么、为什么需要、常见产品有哪些,并用 Milvus 跑一遍完整的写入、建索引、检索和过滤流程。
代码基于 JavaScript 版
langchainv1,向量库使用 Milvus,Embedding 复用 demo-23 的 Qwen 文本向量模型。
一、什么是向量数据库
向量数据库(Vector Database)是专门用来存储和检索向量的数据库。
它不只是「把数组存起来」,还需要提供:
- 向量存储:保存高维向量、原文和 metadata;
- 相似度索引:用 HNSW、IVF、DiskANN 等索引加速最近邻搜索;
- 相似度检索:Top-K 查询,支持 COSINE / IP / L2 等度量;
- 标量过滤:按
category、tenant_id、时间等字段先过滤再检索; - CRUD:插入、更新、删除、按条件删除;
- 持久化与运维:落盘、备份、扩容、监控、多租户。
它和传统数据库的区别:
| 关系型数据库 | 向量数据库 | |
|---|---|---|
| 主要查询 | 精确匹配、范围查询、事务 | 按语义相似度排序 |
| 核心索引 | B+Tree、Hash | HNSW、IVF、DiskANN |
| 典型问题 | 「id 等于 1 的记录」 | 「和这句话最像的 5 条」 |
| 结果 | 精确结果集 | Top-K 近似结果 |
它和向量索引库(如 FAISS)的区别:
- FAISS 更像一个索引库,擅长向量计算;
- 向量数据库在索引之外,还负责存储、过滤、并发、持久化和集群。
二、为什么需要向量数据库
demo-23 的 MemoryVectorStore 适合开发和演示。一旦进入真实业务,会很快遇到它的边界:
| 需求 | MemoryVectorStore | 向量数据库(如 Milvus) |
|---|---|---|
| 数据持久化 | 进程退出即丢失 | 落盘、备份、恢复 |
| 数据规模 | 受内存限制 | 支持千万 / 亿级向量 |
| 检索性能 | 线性扫描 | ANN 索引,毫秒级 |
| 标量过滤 | 手写函数 | 表达式过滤 + 标量索引 |
| 并发与多实例 | 单进程 | 多客户端、集群 |
| 多租户 | 自己隔离 | collection / partition |
| 运维 | 无 | 监控、扩缩容、权限 |
什么时候该上向量数据库:
- 文档量持续增长,内存放不下;
- 查询延迟有要求,线性扫描太慢;
- 需要按用户、租户、类型过滤;
- 需要持久化、备份和多实例共享;
- 需要把向量检索接入正式服务。
三、常见向量数据库
| 产品 | 类型 | 特点 |
|---|---|---|
| Milvus | 开源 / 云服务 | 分布式、亿级规模、索引类型丰富 |
| Zilliz Cloud | 托管 Milvus | 免运维,兼容 Milvus API |
| Qdrant | 开源 / 云服务 | Rust 实现,过滤能力强,部署轻量 |
| Weaviate | 开源 / 云服务 | 模块化,支持混合检索 |
| Chroma | 开源 | 轻量,适合本地开发和原型 |
| pgvector | PostgreSQL 扩展 | 已有 Postgres 时最省事,支持 SQL 过滤 |
| Pinecone | 全托管 | 免运维,适合快速上线 |
| Redis | 内存数据库扩展 | 低延迟,适合缓存与实时场景 |
| FAISS | 向量检索库 | 不是数据库,适合离线实验和自建服务 |
怎么选:
- 已经在用 Postgres,数据量中小:先看 pgvector;
- 想快速做原型:Chroma、
MemoryVectorStore; - 需要大规模、分布式、丰富索引:Milvus;
- 想要托管、不想运维:Zilliz Cloud、Pinecone;
- 需要强过滤和轻量部署:Qdrant。
四、Milvus 核心概念与架构
Milvus 是开源的分布式向量数据库,核心概念如下:
| 概念 | 说明 |
|---|---|
| Collection | 类似关系库里的「表」,一组同构向量和字段 |
| Partition | Collection 内的逻辑分区,常按租户 / 时间划分 |
| Schema | 字段定义:主键、向量字段、标量字段 |
| Primary Key | 每行的唯一 ID,可以 autoId 或手动指定 |
| Vector Field | 向量字段,必须指定维度和数据类型 |
| Scalar Field | 普通字段,用于过滤、展示和排序 |
| Index | 向量索引,如 HNSW、IVF_FLAT、IVF_SQ8、DiskANN |
| Metric | 相似度度量:COSINE、IP、L2 |
| Search Params | 检索参数,如 HNSW 的 ef |
| Load / Release | 检索前需要把 collection 加载到内存 |
4.1 两种部署形态
Standalone
单机进程,依赖 etcd 和 MinIO,适合开发、测试、中小规模。
Milvus Standalone
├── etcd 元数据
└── MinIO 对象存储
Distributed
Proxy、Coordinator、Query Node、Data Node、Index Node 等组件,适合大规模、高并发、生产环境。
Proxy -> Coordinator -> Query / Data / Index Nodes
├── etcd
└── 对象存储
本 demo 用 Standalone,通过 Docker Compose 启动。
4.2 一次检索发生了什么
查询文本
↓ Embedding
查询向量
↓
Milvus 在 Collection 的向量索引中做 ANN 搜索
↓
可选:标量过滤(category == "milvus")
↓
返回 Top-K 向量 + 原文 + metadata
五、环境准备
5.1 启动 Milvus
demo-24 提供了一份 docker-compose.yml,包含 etcd、MinIO 和 Milvus Standalone:
cd demo-24
docker compose up -d
docker compose ps
端口说明:
| 端口 | 用途 |
|---|---|
19530 | Milvus gRPC |
9091 | Milvus 健康检查 |
9000 | MinIO |
9001 | MinIO 控制台 |
检查是否启动:
nc -z localhost 19530
看到 succeeded 或端口可连接后,再运行 demo。
如果使用 Zilliz Cloud,把
MILVUS_ADDRESS设为云端地址(通常带:443),并配置MILVUS_TOKEN。云端 collection 和索引的创建方式一致。
5.2 安装依赖
npm install @langchain/community @zilliz/milvus2-sdk-node
@langchain/community/vectorstores/milvus:LangChain 的 Milvus 适配;@zilliz/milvus2-sdk-node:Milvus 官方 Node.js SDK。
5.3 环境变量
MILVUS_ADDRESS=localhost:19530
MILVUS_TOKEN= # 本地 Milvus 留空
MILVUS_COLLECTION=demo24_documents
Embedding 配置复用 demo-23:
API_KEY;EMBEDDING_BASE_URL;EMBEDDING_MODEL=qwen3.7-text-embedding。
demo 会优先读取 demo-24/.env,没有则读取 demo-23/.env。
六、用 LangChain 连接 Milvus
6.1 先做健康检查
Milvus 类内部会自己创建客户端。为了尽早发现连接问题,demo 先用官方 SDK 探活:
import { MilvusClient } from "@zilliz/milvus2-sdk-node";
const clientConfig = {
address: "localhost:19530",
timeout: 5000,
maxRetries: 1,
retryDelay: 500,
};
const probe = new MilvusClient(clientConfig);
await probe.connectPromise;
const health = await probe.checkHealth();
if (!health.isHealthy) {
throw new Error("Milvus 健康检查未通过");
}
const exists = await probe.hasCollection({
collection_name: "demo24_documents",
});
if (exists.value) {
await probe.dropCollection({ collection_name: "demo24_documents" });
}
await probe.closeConnection();
删除旧 collection 是为了让 demo 可重复运行,避免 schema 或数据残留导致冲突。
6.2 创建 VectorStore
import { Milvus } from "@langchain/community/vectorstores/milvus";
const vectorStore = await Milvus.fromDocuments(documents, embeddings, {
collectionName: "demo24_documents",
clientConfig,
autoId: true,
indexCreateOptions: {
index_type: "HNSW",
metric_type: "COSINE",
params: {
M: 16,
efConstruction: 200,
},
search_params: {
ef: 64,
},
},
});
fromDocuments 会自动完成:
- 创建 collection;
- 根据 metadata 创建标量字段;
- 创建向量字段;
- 创建 HNSW 索引;
- 写入文档和向量。
注意配置项名字是 indexCreateOptions,不是 indexConfig。
6.3 文档和 metadata
import { Document } from "@langchain/core/documents";
const documents = [
new Document({
pageContent: "Milvus 是一个开源的分布式向量数据库……",
metadata: {
source: "vector-db.md",
category: "milvus",
lang: "zh",
},
}),
// ...
];
Milvus 适配器会把 metadata 的每个 key 变成标量字段,所以:
- 所有文档的 metadata key 和类型必须一致;
- 过滤字段要有明确的类型;
- 不要把大对象塞进 metadata;
- 向量字段由适配器管理,不需要手动写进 metadata。
七、写入、建索引与检索
7.1 写入结果
demo 写入 6 条文档,每条 1024 维向量。写入后可以查看 collection 的字段和统计:
const description = await vectorStore.client.describeCollection({
collection_name: "demo24_documents",
});
console.log(description.schema.fields.map((field) => field.name));
const stats = await vectorStore.client.getCollectionStatistics({
collection_name: "demo24_documents",
});
console.log(stats.data);
预期字段包括:
pk, text, vector, source, category, lang
pk:主键,autoId;text:原文;vector:1024 维向量;source、category、lang:标量字段。
7.2 相似度检索
const query = "Milvus 支持哪些索引类型?";
const results = await vectorStore.similaritySearchWithScore(query, 3);
for (const [doc, score] of results) {
console.log(score, doc.metadata.category, doc.pageContent);
}
结果按相似度排序,score 来自 Milvus。使用 COSINE 时,分数越高越相似。
7.3 带标量过滤的检索
const results = await vectorStore.similaritySearchWithScore(
"向量数据库有哪些索引?",
5,
'category == "milvus"'
);
过滤表达式使用 Milvus 的标量过滤语法:
category == "milvus";lang == "zh";rating >= 4;category in ["milvus", "qdrant"];- 组合:
category == "milvus" && lang == "zh"。
先过滤再检索,可以显著减少候选集,也能满足多租户隔离。
7.4 按条件删除
await vectorStore.delete({
filter: 'category == "qdrant"',
});
删除后再查一次,qdrant 分类的文档就不会出现在结果里。
7.5 Collection 管理
// 统计
await vectorStore.client.getCollectionStatistics({
collection_name: "demo24_documents",
});
// 删除整个 collection
await vectorStore.client.dropCollection({
collection_name: "demo24_documents",
});
// 关闭连接
await vectorStore.client.closeConnection();
八、Milvus 索引与参数怎么选
8.1 常见索引
| 索引 | 特点 | 适用场景 |
|---|---|---|
FLAT | 暴力扫描,100% 召回 | 小数据、评估基线 |
IVF_FLAT | 倒排聚类,速度与精度平衡 | 中大规摸 |
IVF_SQ8 | 量化压缩,省内存 | 内存受限 |
HNSW | 图索引,查询快、召回好 | 在线检索默认选择 |
DiskANN | 基于磁盘,超大规摸 | 内存放不下时 |
本 demo 使用 HNSW,参数:
{
M: 16, // 每个节点的邻居数
efConstruction: 200 // 建索引时的候选队列大小
}
检索参数:
{ ef: 64 } // 查询时的候选队列大小,越大越准但越慢
8.2 Metric 怎么选
| Metric | 含义 | 适用场景 |
|---|---|---|
COSINE | 余弦相似度 | 文本 embedding,最常用 |
IP | 内积 | 向量已归一化时与 COSINE 等价 |
L2 | 欧氏距离 | 图像、未归一化向量 |
文本向量一般用 COSINE。如果 embedding 模型输出已经归一化,IP 和 COSINE 的排序结果基本一致。
8.3 参数调优顺序
- 先用
HNSW + COSINE做 baseline; - 用真实问题集测 Recall@K 和延迟;
- 再调整
M、efConstruction、ef; - 数据量继续增长时,再考虑 IVF、DiskANN 或分区。
不要一开始就追求复杂索引,baseline 往往已经够用。
九、生产实践
9.1 Schema 设计
- 主键、向量维度、标量字段要提前规划;
- 过滤字段建标量索引;
- 多租户可以用 partition 或
tenant_id字段隔离; - 文本字段长度要留足,但不要盲目设成超大值。
9.2 写入与更新
- 批量写入,控制单批大小;
- 更新通常用 upsert 或先删后插;
- 删除要同步清理原始文档和缓存;
- 记录写入失败和重试,避免数据不一致。
9.3 运维
- 监控 QPS、延迟、内存、磁盘和索引构建时间;
- 定期备份 etcd、MinIO 和 collection;
- 生产环境用分布式部署或 Zilliz Cloud;
- 升级 Milvus 前先在测试环境验证兼容性。
9.4 和 RAG 的配合
- Embedding 模型和向量维度必须与索引一致;
- 换模型后要重建索引;
- 检索结果还要经过 rerank、上下文压缩和提示词约束;
- 过滤条件要和权限系统保持一致。
十、Python 对应写法
Python 侧可以使用 langchain-milvus:
pip install langchain-milvus pymilvus
from langchain_milvus import Milvus
vector_store = Milvus.from_documents(
documents,
embedding=embedding,
collection_name="demo24_documents",
connection_args={"uri": "http://localhost:19530"},
index_params={
"index_type": "HNSW",
"metric_type": "COSINE",
"params": {"M": 16, "efConstruction": 200},
},
search_params={"ef": 64},
drop_old=True,
)
results = vector_store.similarity_search_with_score(
"Milvus 支持哪些索引类型?",
k=3,
expr='category == "milvus"',
)
for doc, score in results:
print(score, doc.metadata["category"], doc.page_content)
也可以直接用 pymilvus:
from pymilvus import MilvusClient
client = MilvusClient(uri="http://localhost:19530")
print(client.list_collections())
print(client.get_collection_stats("demo24_documents"))
十一、小结
- 向量数据库:存储向量,并提供 ANN 索引、相似度检索、标量过滤和 CRUD。
- 为什么需要:持久化、规模、性能、过滤、并发、多租户和运维。
- 常见选择:Milvus、Qdrant、Weaviate、Chroma、pgvector、Pinecone、FAISS。
- Milvus 核心:Collection、Partition、Schema、Vector Field、Index、Metric。
- 使用流程:连接 → 创建 collection / 索引 → 写入向量 → 相似度检索 → 过滤 / 删除。
- LangChain 接入:
Milvus.fromDocuments(documents, embeddings, config);similaritySearchWithScore(query, k, filter)。
- 生产注意:索引选型、metric、schema、批量写入、监控、备份和模型版本。
一句话:MemoryVectorStore 适合理解原理,Milvus 负责把向量检索做成可持久化、可扩展的服务。