Milvus向量数据库

26 年 8 月 4 日 星期二
4761 字
24 分钟

为什么需要向量数据库?

传统数据库很擅长回答“价格小于100元”“用户名等于Han”“订单创建于最近7天”这类条件明确的问题,但它很难直接回答:

  • 哪些文章和“怎样让大模型拥有长期记忆”意思相近?
  • 哪张图片和当前上传的商品图最相似?
  • 哪段客服记录最可能解决用户正在描述的故障?
  • 在几百万个知识片段中,哪些最适合交给大模型回答当前问题?

这些问题没有稳定的关键词或等值条件。常见做法是先用Embedding模型把文本、图片、音频等非结构化内容转换成向量,再寻找与查询向量最接近的数据。

Milvus就是为大规模向量存储、管理和相似度检索设计的开源数据库。 它负责保存向量及其关联数据、构建近邻索引、执行过滤和检索;它本身通常不负责理解原文,也不等同于Embedding模型。

Milvus向量数据库:在语义空间中寻找最接近查询的内容
查询向量进入由多个语义簇组成的向量空间,找到最接近的一组数据

一句话理解整条链路:

text
非结构化内容 → Embedding模型 → 向量 → Milvus存储与索引
用户问题     → 同一Embedding模型 → 查询向量 → Milvus返回近邻

向量检索到底在比较什么?

假设一个Embedding模型把两句话转换成了768维浮点数。人很难直接理解这768个数字分别表示什么,但模型会尽量让语义接近的内容在向量空间中靠得更近。

例如:

text
"接口一直没有响应"  ─┐
                  ├─ 在向量空间中距离较近
"请求发生超时"     ─┘

“今天晚饭吃什么”    ─── 距离较远

向量数据库接收到查询向量后,会按照指定的距离度量寻找Top K个近邻。常见度量方式如下:

度量方式直觉如何判断更相似常见场景
COSINE比较两个向量的方向值越大越相似文本语义检索最常见
IP计算向量内积值越大越相似模型明确使用内积训练时
L2计算欧氏距离值越小越相似图像、几何特征等场景

余弦相似度的公式是:

cosine(A,B)=ABAB\operatorname{cosine}(A,B)=\frac{A\cdot B}{\lVert A\rVert\lVert B\rVert}

如果向量已经做过L2归一化,Cosine和Inner Product往往会得到相同的排序。但工程上不要凭感觉选择:应该遵循Embedding模型的说明,并保证建索引和查询使用相同的度量方式。

还有一个容易踩坑的细节:Milvus返回结果中的字段通常叫distance,但“越大越好”还是“越小越好”取决于度量类型,不能看到distance就一律按升序理解。

一次完整的写入与检索

Milvus只保存向量远远不够。真正可用的知识库还要保存原文、来源、租户、权限、时间等信息,否则即使找到了一个向量,也不知道它代表什么,更无法把原文交给大模型。

Milvus向量数据写入与检索流程
文档经过清洗、切分和Embedding后写入Milvus,查询经过同一模型转换后执行过滤与近邻检索

写入链路通常包括:

  1. 读取PDF、网页、Markdown或业务数据;
  2. 清洗内容并切分成大小合适的Chunk;
  3. 使用Embedding模型生成向量;
  4. 把主键、向量、原文和Metadata一起写入Milvus;
  5. 为向量字段和常用过滤字段建立索引。

查询链路则是:

  1. 用同一个Embedding模型转换用户问题;
  2. 根据租户、权限、时间、类型等条件缩小候选范围;
  3. 执行近似最近邻检索(ANN),召回Top K;
  4. 可选地使用Reranker重排;
  5. 把少量高质量原文交给LLM生成答案。

这里的“同一个模型”不只是模型名称相同,还包括模型版本、输出维度、文本前缀、池化方式和归一化规则一致。任何一个环节变化,都可能让新查询和旧数据不再处于同一个向量空间。

Milvus中的核心概念

Milvus的概念和关系型数据库有一些相似之处,但不能完全一一对应。

Milvus概念可以近似理解为说明
Database数据库用于组织和隔离多个Collection
Collection一组具有相同Schema的数据
Entity一条完整记录,例如一个文档Chunk
Field可以是主键、标量、JSON或向量字段
Schema表结构定义字段类型、向量维度、主键等约束
Partition分区在Collection内部按业务边界组织数据
Index索引为向量近邻搜索或标量过滤加速
Segment内部数据单元Milvus内部组织、密封和加载数据的单位

一个用于RAG的Collection可以设计成:

字段类型示意用途
chunk_idINT64VARCHAR每个Chunk的唯一主键
document_idVARCHAR关联原始文档,便于整篇删除和更新
textVARCHAR召回后交给LLM的原文
dense_vectorFLOAT_VECTOR语义检索向量
tenant_idVARCHAR多租户隔离与过滤
sourceVARCHAR文件名、URL或业务来源
updated_atINT64增量同步与时间过滤
metadataJSON不固定的扩展属性

Collection不是“一个文档一个表”

通常应该让同一业务、同一向量模型和同一Schema的数据共享Collection,而不是每上传一个文件就创建一个Collection。文档之间的归属关系用document_id等标量字段表达。

Collection过多会提高元数据、索引和运维复杂度。只有在数据确实需要独立Schema、资源或生命周期时,才值得拆分。

Partition也不是越多越好

Partition适合表达少量、稳定并且经常参与整体过滤的边界。高基数字段,例如“每个用户一个分区”或“每天一个永久分区”,容易制造大量分区。

多租户场景应该先根据租户数量、数据规模、安全要求和查询模式做设计,不能仅凭一个字段就决定使用Partition、独立Collection还是独立Database。

向量索引:用一点召回率换取大量速度

如果只有几千条数据,逐条计算距离也能完成检索,这就是精确搜索。数据增长到百万、千万甚至更大规模后,每次扫描全部向量的成本很高,于是需要ANN索引尽快找到“足够接近”的候选。

它的本质是一组权衡:

text
查询延迟 ↔ 吞吐量 ↔ 召回率 ↔ 内存 ↔ 索引构建时间

Milvus支持多种索引,常见选择可以这样理解:

索引工作方式优点代价与适用场景
FLAT暴力比较全部向量精确、无需训练索引数据较小、过滤后候选极少或需要基准真值
HNSW在多层近邻图中导航低延迟、高召回占用内存较多,建图也有成本
IVF_FLAT先聚类,再扫描最相关的若干桶容易理解,吞吐表现好需要调节nlistnprobe
IVF_SQ8 / IVF_PQIVF加向量量化压缩明显降低内存压缩会损失精度,需要评估召回率
DISKANN利用SSD保存大规模图索引数据超出内存时仍可检索更依赖磁盘IOPS和硬件特征
AUTOINDEX由服务选择和管理索引策略上手简单仍然需要用真实数据验证效果

HNSW常见参数包括:

  • M:每个节点保留的邻居数量,增大后通常提高召回,也增加内存和构建成本;
  • efConstruction:建索引时搜索候选的范围,越大通常建得更慢但图质量更好;
  • ef:查询时搜索候选的范围,越大通常召回更高、延迟也更高。

IVF系列常见参数包括:

  • nlist:建索引时聚类中心的数量;
  • nprobe:查询时探测多少个聚类,越大越接近全量搜索,也越慢。

网上的“最佳参数”只能作为起点。最终应该用自己的数据分布、真实查询和目标硬件压测,至少观察:

  • Recall@K:真实近邻中有多少被召回;
  • P50、P95和P99延迟;
  • 峰值QPS与并发下的稳定性;
  • 常驻内存、磁盘和网络占用;
  • 索引构建与数据加载时间。

Milvus为什么能扩展到分布式?

学习阶段使用Milvus Lite时几乎感受不到复杂架构,但Milvus Distributed的核心是计算与存储解耦,并把访问、调度、写入、查询和持久化拆成不同职责。

Milvus Distributed四层架构
访问层、协调层、工作节点和存储层可以分别扩缩容

这张图是便于理解的简化版本:

  • 访问层由无状态Proxy组成,提供统一入口,完成认证、路由和结果聚合;
  • Coordinator维护集群拓扑并调度各类任务,可以把它看作控制平面的“大脑”;
  • Streaming Node负责实时写入和流式处理;
  • Query Node加载数据并执行查询;
  • Data Node承担批处理、索引和持久化相关工作;
  • 存储层分别保存元数据、对象数据/索引文件和WAL。

职责拆开后,读请求变多时可以重点扩Query Node,写入压力升高时可以扩展对应的写入与数据处理能力,而不必把整套系统按同一个比例放大。

三种部署形态怎么选?

Milvus官方提供三种主要部署形态,客户端API尽量保持一致,因此可以从本地原型逐步迁移到生产集群。

部署方式运行形态适合场景不适合场景
Milvus Lite嵌入Python进程,数据保存到本地文件Notebook、学习、原型、边缘设备大规模生产和高可用要求
Milvus Standalone单机服务,通常使用Docker部署中小规模生产、没有Kubernetes的团队需要跨节点水平扩展的负载
Milvus DistributedKubernetes分布式集群大规模、高可用、读写负载独立扩缩容小型项目或不具备集群运维能力的团队

我的选择原则是:

  1. 先用Lite验证Chunk、Embedding和检索效果;
  2. 需要独立服务或多人共享时迁移到Standalone;
  3. 只有容量、可用性或吞吐明确要求横向扩展时,才引入Distributed。

不要为了“以后可能有十亿向量”而在第一天就维护一套Kubernetes集群。向量检索项目最早遇到的瓶颈,往往是数据质量、切分策略和评测集,而不是数据库节点数量。

用Milvus Lite跑通最小示例

下面的例子不下载Embedding模型,而是使用手工构造的4维向量,目的是只用几十行代码看清Milvus的建库、写入、过滤和检索API。

1. 安装PyMilvus

shell
python -m pip install -U pymilvus

pymilvus既包含Python客户端,也包含Milvus Lite。传入本地文件路径即可创建一个嵌入式数据库:

python
from pymilvus import MilvusClient

client = MilvusClient("./milvus_demo.db")

2. 创建Collection并写入数据

python
from pymilvus import MilvusClient

client = MilvusClient("./milvus_demo.db")
collection_name = "knowledge_chunks"

# 为了让示例可以重复运行,这里会删除同名Collection。
# 生产代码不要无条件执行drop_collection。
if client.has_collection(collection_name=collection_name):
    client.drop_collection(collection_name=collection_name)

# 快捷建表会使用默认主键字段id和向量字段vector。
client.create_collection(
    collection_name=collection_name,
    dimension=4,
    metric_type="COSINE",
)

rows = [
    {
        "id": 1,
        "vector": [0.95, 0.05, 0.02, 0.01],
        "text": "Milvus是面向相似度检索的向量数据库。",
        "category": "AI",
    },
    {
        "id": 2,
        "vector": [0.86, 0.12, 0.05, 0.02],
        "text": "RAG会先检索知识,再让大模型生成答案。",
        "category": "AI",
    },
    {
        "id": 3,
        "vector": [0.04, 0.96, 0.03, 0.01],
        "text": "关系型数据库擅长事务、约束和结构化查询。",
        "category": "Database",
    },
]

result = client.insert(collection_name=collection_name, data=rows)
print(result)

正式项目建议定义固定Schema,明确主键、字符串长度、向量字段和是否允许动态字段;上面的快捷方式更适合入门演示。

3. 带Metadata过滤的向量搜索

假设查询“哪个数据库适合语义搜索”经过同一个Embedding模型后得到下面的向量:

python
query_vector = [0.92, 0.08, 0.03, 0.01]

results = client.search(
    collection_name=collection_name,
    data=[query_vector],
    filter="category == 'AI'",
    limit=2,
    output_fields=["text", "category"],
)

for hits in results:
    for hit in hits:
        print(
            f"id={hit['id']}, "
            f"score={hit['distance']:.4f}, "
            f"text={hit['entity']['text']}"
        )

data是二维数组,因为一次请求可以同时搜索多个查询向量;返回结果也相应分成多组。limit=2表示每个查询最多返回两个Entity。

这个示例中的向量是人为设计的,只能用来理解API。接入真实Embedding模型时,需要把:

python
query_vector = [0.92, 0.08, 0.03, 0.01]

替换成类似下面的逻辑:

python
query_vector = embedding_model.encode("哪个数据库适合语义搜索").tolist()

同时把Collection的dimension改成模型真实输出维度,并用同一模型重新生成所有文档向量。

4. 查询和删除

向量搜索用于“找相似内容”,普通Query用于“按条件取数据”,两者不要混淆:

python
# 按标量条件查询
rows = client.query(
    collection_name=collection_name,
    filter="category == 'AI'",
    output_fields=["text", "category"],
)

# 按主键删除
client.delete(collection_name=collection_name, ids=[3])

在RAG项目中删除原始文档时,应该通过document_id找到并删除它的全部Chunk,而不是只删当前看到的一条搜索结果。

Milvus在RAG中的正确位置

Milvus是RAG检索层的重要组成部分,但不是完整的RAG系统。

text
数据源
  ↓ 解析、清洗、切分
Embedding模型

Milvus(向量 + 原文 + Metadata)
  ↓ 语义召回 / 关键词召回 / 过滤
Reranker

Prompt组装

LLM生成答案并引用来源

如果最终回答不准确,问题可能出在任意一层:

  • PDF解析丢失了表格或标题;
  • Chunk太大导致主题混杂,太小又失去上下文;
  • Embedding模型不适合中文或专业领域;
  • topK太小导致漏召回,太大又引入噪声;
  • Metadata过滤错了,把正确文档排除在外;
  • Reranker或Prompt没有正确使用召回内容;
  • 知识库里本来就没有答案。

所以“换一个向量数据库”不会自动解决所有RAG效果问题。数据库擅长把候选快速找出来,候选是否有用仍然取决于数据、模型和检索策略。

Dense、Sparse与混合检索

稠密向量擅长理解改写和语义,但对错误码、产品型号、人名和精确术语有时不够稳定;BM25等稀疏检索擅长关键词精确命中,却可能错过同义表达。

Milvus既能执行稠密向量搜索,也支持稀疏向量与基于BM25 Function的全文检索,还可以把多路结果进行混合搜索和融合排序。

text
用户问题
  ├─ Dense Embedding → 语义召回 ─┐
  └─ BM25 / Sparse → 关键词召回 ─┤

                          RRF / 加权融合

                            Reranker

这通常比单一路检索更稳健。关于BM25的公式、词频饱和和RRF,可以继续阅读《BM25算法》

生产环境最容易踩的坑

1. 升级Embedding模型却不重建向量

不同模型生成的是不同向量空间。即使维度刚好相同,新模型生成的查询向量也不能直接检索旧模型生成的文档向量。

建议为每条数据记录embedding_modelembedding_version,升级时创建新字段或新Collection做双写、回填和灰度切换。

2. 把相似度分数当成正确率

相似度高只表示在当前模型的向量空间中更接近,不表示文档一定能回答问题,更不表示内容一定真实。阈值也不能从另一个模型或数据集直接照搬。

3. 只压测延迟,不评估召回

把HNSW的ef或IVF的nprobe调得很小,P99可能很好看,但真正相关的内容也可能被漏掉。性能优化必须和Recall@K一起观察。

4. 忘记给高频过滤字段建标量索引

向量索引只加速向量近邻搜索。租户、权限、状态和时间范围等过滤字段在数据量大时也需要合适的标量索引。

5. 用Partition代替权限控制

Partition是数据组织和检索优化手段,不是完整的鉴权系统。请求进入Milvus前仍然要完成身份验证,检索时必须强制带上正确的数据范围过滤,不能让大模型自行决定是否添加租户条件。

6. 只存Chunk,不保存可追溯来源

至少要能从一个Chunk追溯到原始文档、标题、URL、页码或段落位置。否则无法在答案中展示引用,也难以排查错误数据来自哪里。

上线前检查清单

  • 文档与查询使用完全一致的Embedding模型和预处理规则;
  • Collection维度与Embedding输出维度一致;
  • Metric符合模型说明,建索引与查询配置一致;
  • 主键稳定,原始文档可以批量更新和删除全部Chunk;
  • 保存原文、来源、租户、权限、时间和模型版本;
  • 高频Metadata过滤字段已经建立合适索引;
  • 有一套来自真实问题的离线评测集,而不是只看几个Demo;
  • 同时测量Recall@K、P95/P99、QPS和资源占用;
  • 设计了Embedding升级、数据回填和灰度切换方案;
  • 有备份、恢复、监控、容量预警和访问控制方案。

什么时候不必使用Milvus?

Milvus很强,但不是所有项目都需要独立的向量数据库:

  • 只有几百条数据,加载到内存后直接计算已经足够;
  • 需求只是精确关键词查询,倒排索引可能更合适;
  • 系统核心是复杂事务、外键和多表Join,应继续以关系型数据库作为事实源;
  • 团队还没有验证Embedding和Chunk策略,先用Milvus Lite完成实验更划算。

常见架构不是“用Milvus替换MySQL”,而是让两者各司其职:MySQL保存强事务业务数据,Milvus保存面向相似度检索的向量副本和必要Metadata,通过稳定主键关联。

总结

理解Milvus,可以抓住五句话:

  1. Embedding模型负责把内容变成向量,Milvus负责存储、索引和搜索向量;
  2. 向量必须和原文、主键、来源、权限等Metadata一起保存;
  3. ANN索引不是免费的加速器,它在延迟、召回率、内存和构建成本之间做权衡;
  4. Lite、Standalone和Distributed对应从本地实验到分布式生产的不同阶段;
  5. RAG效果取决于整条数据与检索链路,不能只看向量数据库本身。

如果只是想快速体验,Milvus Lite几行代码就能启动;如果要进入生产,则应该把主要精力放在数据建模、真实评测、容量规划和可运维性上。

参考资料

文章标题:Milvus向量数据库

文章作者:梦幻の风

文章链接:https://www.hstudent.xyz/posts/ai/milvus%E5%90%91%E9%87%8F%E6%95%B0%E6%8D%AE%E5%BA%93[复制]

最后修改时间:


梦幻の风 梦幻の风

商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。