RAG 性能总卡在检索?先搞懂 Milvus 向量数据库再说

RAG 性能总卡在检索?先搞懂 Milvus 向量数据库再说

在构建 Agent 记忆模块时,我完整走了一遍 Milvus 的接入流程。本文以 AI 日记项目为例,梳理其中的关键技术点。


一、向量数据库解决什么问题

传统数据库靠关键词精确匹配,搜"户外活动"找不到"爬山""公园散步"这类语义相关的内容。向量数据库的做法是将文本通过 Embedding 模型转为高维向量,以向量距离衡量语义相似度,从而实现"以义搜义"。

在 RAG 架构中,向量数据库承担知识检索环节,是 AI Agent 实现长期记忆的基础。

核心概念映射

概念 类比 说明
Collection Table 数据表
Entity Row 一条记录
Field Column 字段,标量或向量类型
Partition 分区表 逻辑隔离,用于多租户/按时间归档
Index Index 加速 ANN(近似最近邻)搜索

二、环境准备

bash 复制代码
pnpm add @zilliz/milvus2-sdk-node @langchain/openai dotenv
js 复制代码
import { MilvusClient } from '@zilliz/milvus2-sdk-node';
import { OpenAIEmbeddings } from '@langchain/openai';

// 以阿里通义 text-embedding-v2 为例,维度 1536
const embeddings = new OpenAIEmbeddings({
  apiKey: process.env.OPENAI_API_KEY,
  model: 'text-embedding-v2',
  configuration: { baseURL: process.env.OPENAI_BASE_URL },
  dimensions: 1536,
});

const client = new MilvusClient({
  address: process.env.MILVUS_ADDRESS,
  token: process.env.MILVUS_TOKEN,
  maxRetries: 3,
  timeout: 30000,
});

async function getEmbeddings(text) {
  return await embeddings.embedQuery(text);
}

Zilliz Cloud 免运维开箱即用;自托管则通过 Docker Compose 一键启动,默认地址 localhost:19530,无需 token。


三、Schema 设计与 Collection 管理

3.1 字段类型

类型 使用场景
VarChar id, content, date, mood
Int64 / Float / Bool 数值及布尔标量
JSON 灵活的元数据
Array 标签 tags
FloatVector content 的 Embedding
BinaryVector / SparseFloatVector 图像指纹 / 关键词稀疏向量

3.2 创建 AI 日记 Collection

js 复制代码
await client.createCollection({
  collection_name: 'ai_diary',
  fields: [
    { name: 'id', data_type: DataType.VarChar, max_length: 50, is_primary_key: true },
    { name: 'vector', data_type: DataType.FloatVector, dim: 1536 },
    { name: 'content', data_type: DataType.VarChar, max_length: 5000 },
    { name: 'date', data_type: DataType.VarChar, max_length: 50 },
    { name: 'mood', data_type: DataType.VarChar, max_length: 50 },
    { name: 'tags', data_type: DataType.Array, element_type: DataType.VarChar, max_capacity: 10, max_length: 50 },
  ],
});

Milvus 也支持动态 SchemaenableDynamicField: true),未声明字段自动归入 JSON 列,适合原型快速迭代,生产建议切回强 Schema。

3.3 生命周期操作

js 复制代码
await client.hasCollection({ collection_name: 'ai_diary' });
await client.describeCollection({ collection_name: 'ai_diary' });
await client.showCollections();
await client.alterCollection({ collection_name: 'ai_diary', properties: { 'collection.ttl.seconds': '604800' } });
await client.dropCollection({ collection_name: 'ai_diary' });

3.4 Partition 分区

js 复制代码
await client.createPartition({ collection_name: 'ai_diary', partition_name: '2026_01' });
await client.insert({ collection_name: 'ai_diary', partition_name: '2026_01', data: [...] });
await client.search({ collection_name: 'ai_diary', partition_names: ['2026_01'], vector, limit: 10 });

限定分区搜索可大幅缩小候选集,显著提升 QPS。


四、向量索引------性能的核心

4.1 索引选型对比

索引 原理 精度 内存 适用规模
FLAT 暴力搜索 100% <10 万
IVF_FLAT K-Means + 候选集暴搜 >95% 百万级
IVF_SQ8 IVF + 8-bit 量化 >92% 低(4×压缩) 百万级,内存受限
IVF_PQ IVF + 乘积量化 >85% 极低(16×) 千万级
HNSW 分层图搜索 >98% 百万级,高 QPS
DISKANN 基于 SSD >95% 磁盘 亿级

4.2 索引创建

js 复制代码
// 默认选择:IVF_FLAT + COSINE
await client.createIndex({
  collection_name: 'ai_diary',
  field_name: 'vector',
  index_type: 'IVF_FLAT',
  metric_type: 'COSINE',
  params: { nlist: 1536 },
});

// HNSW------高召回高QPS,内存换性能
await client.createIndex({
  collection_name: 'ai_diary', field_name: 'vector',
  index_type: 'HNSW', metric_type: 'COSINE',
  params: { M: 16, efConstruction: 200 },
});

// IVF_SQ8------向量压缩到 1/4
await client.createIndex({
  collection_name: 'ai_diary', field_name: 'vector',
  index_type: 'IVF_SQ8', metric_type: 'COSINE',
  params: { nlist: 1024 },
});

决策逻辑:<10 万用 FLAT;百万级默认 IVF_FLAT,内存受限用 IVF_SQ8 或 IVF_PQ,高 QPS 用 HNSW;亿级走 DISKANN。

4.3 相似度度量

方式 公式 适用
COSINE 余弦相似度 文本语义(最常用)
L2 欧几里得距离 未归一化向量
IP 内积 归一化向量(计算最快)
HAMMING / JACCARD 汉明/杰卡德 BinaryVector

注意:如果向量已 L2 归一化,IP 与 COSINE 数学等价,IP 计算开销更小。本项目的 text-embedding-v2 未做归一化,故用 COSINE。


五、数据操作

插入

js 复制代码
const diaryData = [
  { id: 'diary_001', content: '今天天气很好,去公园散步了...', date: '2026-01-10', mood: 'happy', tags: ['生活', '散步'] },
  { id: 'diary_002', content: '今天工作很忙,完成了一个重要的项目里程碑...', date: '2026-01-11', mood: 'excited', tags: ['工作', '成就'] },
  { id: 'diary_003', content: '周末和朋友去爬山,天气很好...', date: '2026-01-12', mood: 'relaxed', tags: ['户外', '朋友'] },
  { id: 'diary_004', content: '今天学习了Milvus向量数据库...', date: '2026-01-12', mood: 'curious', tags: ['学习', '技术'] },
  { id: 'diary_005', content: '晚上做了一顿丰盛的晚餐...', date: '2026-01-13', mood: 'proud', tags: ['美食', '家庭'] },
];

const data = await Promise.all(diaryData.map(async d => ({ ...d, vector: await getEmbeddings(d.content) })));
const { insert_cnt } = await client.insert({ collection_name: 'ai_diary', data });

Upsert / Query / Delete

js 复制代码
// Upsert:存在即更新,不存在即插入
await client.upsert({ collection_name: 'ai_diary', data: [{ id: 'diary_001', vector: newVec, content: '...' }] });

// Query:标量条件精确匹配,不走向量
await client.query({ collection_name: 'ai_diary', expr: 'mood == "happy"', output_fields: ['id', 'content'], limit: 100 });

// Delete
await client.delete({ collection_name: 'ai_diary', expr: 'id in ["diary_003"]' });
await client.delete({ collection_name: 'ai_diary', expr: 'date < "2026-01-01"' });

Query vs Search:Query 对标量做精确过滤,速度快;Search 做向量 ANN 搜索,能匹配语义相近的结果。


六、向量搜索

基础语义搜索

js 复制代码
await client.loadCollection({ collection_name: 'ai_diary' });

const query = '我想看看关于户外活动的日记';
const queryVector = await getEmbeddings(query);

const { results } = await client.search({
  collection_name: 'ai_diary',
  vector: queryVector,
  limit: 3,
  metric_type: 'COSINE',
  output_fields: ['id', 'content', 'date', 'mood', 'tags'],
});

搜索"户外活动"会优先返回 diary_003(爬山)和 diary_001(公园散步),尽管原文中并不包含"户外活动"这个词。

标量过滤搜索

js 复制代码
// 向量 + 条件组合,生产中的标准用法
await client.search({
  collection_name: 'ai_diary',
  vector: queryVector,
  limit: 10,
  filter: 'array_contains(tags, "户外") and date >= "2026-01-10"',
  output_fields: ['id', 'content', 'tags'],
});

支持的表达式:==, !=, >, >=, <, <= / and, or, not / in [...] / like "prefix%" / array_contains() / metadata["key"]

范围搜索与分页

js 复制代码
// 按相似度阈值搜索
await client.search({ ..., params: { radius: 0.8, range_filter: 0.5 } });

// 分页
await client.search({ ..., limit: 10, offset: 0 });
await client.search({ ..., limit: 10, offset: 10 });

多向量混合搜索(2.4+)

同时用稠密向量(语义)和稀疏向量(关键词)做多路召回,RRF 重排序:

js 复制代码
await client.hybridSearch({
  collection_name: 'ai_diary',
  searches: [
    { vector: denseVec, anns_field: 'dense_vector', metric_type: 'COSINE', limit: 100 },
    { vector: sparseVec, anns_field: 'sparse_vector', metric_type: 'IP', limit: 100 },
  ],
  rerank: { strategy: 'rrf', params: { k: 60 } },
  limit: 10,
  output_fields: ['id', 'content'],
});

七、内存管理与一致性

Collection 必须 load 进 QueryNode 内存才能被检索,用毕 release 释放:

js 复制代码
await client.loadCollection({ collection_name: 'ai_diary', replica_number: 2 });
await client.releaseCollection({ collection_name: 'ai_diary' });

四种一致性级别:

级别 语义 场景
Strong 线性一致,保证读到最新 金融、库存
Session 同客户端 read-your-writes 写后即查
Bounded 容忍指定窗口内的延迟 一般业务
Eventually 最终一致,可能陈旧 推荐、分析
js 复制代码
await client.insert({ ..., consistency_level: 'Session' });

八、生产要点

错误重试 :SDK 内置 maxRetries,业务层建议再兜一层 rate_limit 的退避重试。

批量写入 :每批 1000 条分片提交,最后显式 flush 确保落盘。

模型选型

模型 维度 特点
text-embedding-3-small (OpenAI) 1536 通用均衡
text-embedding-v2 (阿里) 1536 中文语义强
BGE-M3 (BAAI) 1024 开源,稠密+稀疏
M3E-large 1024 开源中文轻量

选定后不要轻易更换------换模型意味着所有向量重新入库。

成本控制:维度选 1536 即可;非核心场景用 IVF_SQ8 压缩 75% 内存;按时间分区 + TTL 自动清理;冷数据 release、热数据常驻。


九、总结

本文以 AI 日记项目为线索,覆盖了 Milvus 从接入到生产的核心链路:

  1. Schema:字段类型、强/动态 Schema、Partition 分区
  2. 索引:FLAT → IVF_FLAT → HNSW → IVF_SQ8 → IVFPQ → DISKANN 的递进路径
  3. 操作:Insert / Upsert / Query / Delete
  4. 搜索:语义搜索、标量过滤、范围搜索、分页、多向量混合+RFF
  5. 可靠性:一致性级别与内存管理
  6. 工程:重试、批量写入、模型选型、成本优化

向量数据库是 RAG 的记忆基础,搞懂 Milvus 就搞懂了 AI 检索链路的底层逻辑。

相关推荐
山间小僧13 小时前
「AI学习笔记」Agent Memory(二)跨会话记忆:从聊天记录到可演化的长期记忆
aigc·agent·vibecoding
dong_junshuai19 小时前
每天一个开源项目#99 OpenResearch:2.2K星的本地研究Agent工作台
开源·github·agent
花椒技术20 小时前
把 AI 视频接进直播:提前生成、按次生成、持续生成怎么选?
agent·音视频开发
用户80825986668720 小时前
用 LangGraph 重写自己手写的 Agent:框架替你解决了什么,什么它不管
agent
能不能静下心来看20 小时前
从 RAG 到 Agent:跑通两个 Demo 后,我终于分清了这两个词
agent
ZGi.ai20 小时前
知识库权限变了,怎样避免答错?
agent·权限管理·知识库·工作流·zgi·客服运营
大模型真好玩20 小时前
DeepSeek Harness 入门很简单(四)——DeepSeek Harness接入插件
人工智能·agent·deepseek
掰头战士20 小时前
MCP、Skill、Plugin,都是给agent拓展能力,到底有何区别?
typescript·llm·agent
嘟嘟嘟95271 天前
AI Agent 的边缘困境
人工智能·架构·agent
HYDtomako1 天前
Agent checkpoint设计
ai·agent·checkpoint·claude code