
简介这是一份关于向量数据库 Milvus 的技术分享PDF源自Zilliz技术合伙人栾小凡的公开演讲适合正在选型向量检索方案的后端工程师、算法工程师与数据平台开发者。内容从“80%以上数据是非结构化数据”这一背景切入讲清Embedding与向量检索的基本原理再重点拆解Milvus 2.0的云原生架构、微服务化、流批一体设计以及FAISS/HNSW/DISKANN等索引算法和向量搜索、范围搜索、标量过滤等能力。同时结合以图搜图、问答机器人等落地案例给出典型使用流程并介绍了10分钟导入亿级数据、支持删除更新、RBAC、多资源组隔离等企业级特性以及相较Elastic Search超过10倍性能提升的主要来源。资源为单份PDF文件大小16.8MB便于直接阅读或打印已有211人学习下载。对想理解Milvus性能优势、索引原理和云原生实践的读者这份讲稿能帮助快速建立整体认知并为后续上手或架构选型提供参考。1. 万物皆向量化Milvus 在解决什么问题凭什么值得关注做某电商图片搜索Demo时数据量从十万涨到千万我意识到靠标签和人工规则已经撑不住了。后来把图片交给开源Embedding模型转成向量问题就变成了这些几百上千维的数字该放哪、怎么按相似度快速找回来。于是走向了向量数据库Milvus是绕不开的那一个。这篇笔记想讲清楚三件事为什么向量化能成为非结构化数据的通用语言Milvus现状到底走到哪一步了以及你要是真上手会踩到哪些坑。适合推荐系统、RAG、图搜、音视频检索的工程师也适合正在做技术选型的架构师。2. 为什么是向量化向量数据库的选型逻辑与 Milvus 的定位2.1 向量的本质万物如何变成一串数字向量化不是一个新概念NLP、CV、推荐里早就用了。关键思路是用模型将原始输入映射到一个 d 维特征空间让语义相近的对象在空间里距离接近。一只猫的图片和一只狗的图片向量距离会小于它和一辆汽车的向量距离一句“今天天气不错”和“今日晴”语义距离也远小于它和“我要买手机”的距离。模型输出的东西就是一堆浮点数比如[0.12, -0.87, 0.33, ...]这就是 embedding也就是“向量化”的结果。具体到落地链路常见做法是原始数据 → 预处理 → 模型推理 → 归一化 → 写出到存储。很多团队忽略归一化这一步后面用余弦距离和用内积结果就会不一致这是血泪经验。维度通常从 256 到 2048 不等不是越大越好。图像纹理细节要求高的任务才需要更高维度纯文本分类 384 维往往就够用盲目拉到 1536 维只会让存储和计算成本翻倍。向量化之后的相似检索本质就是最近邻搜索给定一个 query 向量在底库中找到 TopK 个距离最近的向量。衡量距离有欧氏距离、内积、余弦距离三种Milvus 里对应metric_type选项。选哪个不只看数学定义还得看你用的 Embedding 模型训练时用的什么损失函数这个后面会专门说。2.2 精确 KNN 为什么会在百万级数据量上翻车最直观的做法是逐条算距离然后排序一亿条向量算一遍大概要几十秒到分钟级。即使上 GPU成本和功耗也完全不可接受。所以工程上普遍转向近似最近邻 ANN也就是用“差不多准但快得多”的搜索替代“绝对准但太慢”的搜索。ANN 的核心思想是通过空间划分、图索引、量化和倒排来缩小搜索范围。最常见的是 IVF 这类聚类思路把底库向量预先分成 nlist 个桶查询时只搜索距离最近的 nprobe 个桶而不是全库。另一种是 HNSW用多层图做导航从粗到细逐层逼近最近邻实际效果在大多数数据集上精度和速度平衡得非常好。这两类算法 Milvus 都内置了还给包了一层分布式层所以你不用自己实现。传统关系库在这里很尴尬SQL 表达不了“最相近”即使强行把向量塞进 BLOB 字段也没有高效的近似索引、分段合并和并行搜片能力只能全表扫。Milvus 把分片、索引、标量过滤、动态扩缩容打包成服务这是它区别于“数据库加插件”方案的根本点。2.3 为什么选型会落到 Milvus 而不是自己拼装我见过三种常见替代方案。一是自己写 Python 暴力扫描只适合几千到几万规模离线分析可以线上服务想都不要想。二是在某个关系库里加向量插件单机小库能用但数据量一上来、写入并发一高插件方案很快就卡在查询规划和索引维护上。三是直接用商业向量服务省运维但成本不透明数据出口也是个问题。Milvus 作为开源项目聚合了向量索引库和分布式框架的能力。单机可以跑数据量和并发上来之后查询节点、索引节点、数据节点可以分开扩展。缺点也真实组件比单机数据库多理解门槛高版本之间接口有变化文档有些地方写得不够清楚。我的判断标准比较朴素如果你已经有一批现成的 Embedding 向量未来预期数据量是千万级以上且有持续实时写入那 Milvus 是比自研和通用库更省心的选择。方案适合规模水平扩展向量检索能力维护成本自研暴力扫描万级以下无TopK 精确但慢高关系库向量插件百万级内受限依赖插件实现中Milvus千万到百亿级完整ANN 索引 标量过滤中高但值得3. 当下的 Milvus 到底长什么样架构、部署与生态现状3.1 架构演进从单机单体到存算分离Milvus 1.x 时代更像一个单机工具数据索引和查询都在一个进程里元数据靠外部 KV 组件保存。当时遇到瓶颈只能加内存、加 CPU很难水平扩展。如果你只在网上看过 1.x 的教程会以为 Milvus 就是个跑在单机上的 ANN 库其实那已经是过去式了。2.x 重构为存算分离底层用对象存储和日志存储配合计算层拆成代理层、协调层、数据节点、索引节点、查询节点。数据以 segment 为单位追加写后台做 compaction 合并索引查询节点不持有数据副本索引坏了可以重建。这套设计带来的直接好处是查询负载高了可以单独扩 QueryNode写入量大可以扩 DataNode索引重建也不影响在线查询。组件职责Proxy接收客户端请求路由、鉴权、限流Coord协调写入、查询、索引任务的调度DataNode接收写入数据落盘到对象存储IndexNode异步构建向量和标量索引QueryNode加载索引执行查询和检索这套架构下的现状是Milvus 已经具备了一个生产级分布式数据库该有的骨架不再是一个“能跑 demo 的玩具”。3.2 部署方式与资源预算从最小可行到上生产实际部署我见过三种走法。单机 Docker Compose 适合测试环境一个命令把依赖全拉起来压测到百万级向量没问题K8s 部署适合生产用官方 Operator 管理节点扩缩容比较顺手云托管版适合不想自己运维的团队但要接受厂商绑定。不要一上来就上 K8s先用单机跑通业务再考虑扩展这是投入产出比最高的路径。单机部署的最小 compose 文件大概是这样的services: etcd: image: quay.io/coreos/etcd:v3.5.5 minio: image: minio/minio:RELEASE.2023-03-20T20-16-18Z milvus: image: milvusdb/milvus:v2.4.1 command: [milvus, run, standalone] environment: ETCD_ENDPOINTS: etcd:2379 MINIO_ADDRESS: minio:9000 ports: - 19530:19530 - 9091:9091 volumes: - ${DOCKER_VOLUME_DIRECTORY:-/var/lib/milvus}:/var/lib/milvus depends_on: - etcd - minio这里的关键是command: [milvus, run, standalone]它把各个内部组件放到一个进程里跑适合开发调试。生产环境不要这么搞各组件的日志和故障域会互相干扰。资源预算我一般按经验估算亿级 768 维向量用 HNSW 索引常驻内存大概需要 64C256G 起步如果预算有限可以换成 DiskANN 索引内存占用降到原来的五分之一但查询延迟会涨。3.3 生态现状SDK、导入工具与大模型应用衔接现状一个很明显的信号是主流语言都有官方客户端Python 生态尤其完整和数据处理框架衔接方便。数据导入也已经有批量工具支持从对象存储、文件系统读取向量数据不用自己写并发导入脚本。大模型应用爆发之后Milvus 和 Embedding 模型、RAG 框架之间的集成路径基本被趟平了社区里大量教程在讲“用开源 Embedding 模型把文档切块转向量再灌进 Milvus 做知识库问答”。还有一个值得注意的现状集合、分区、分片这套数据模型已经稳定索引类型从 FLAT 到 IVF 到 HNSW 到 DiskANN 都有完整实现。这说明它已经过了“只能演示”的阶段正在往“好用”的方向走。但“好用”的另一面是——版本升级频繁2.3 到 2.4 之间接口就有些变化老项目的代码换个版本可能要小改。选型时要把这个维护成本算进去。4. 把 Milvus 用起来Schema 设计、索引参数与三段式落地4.1 Schema 设计先定维度再定字段主键别拍脑袋集合Collection可以理解成一张表先想清楚字段类型再动手。我以一个商品图片搜索场景为例主键用业务商品 ID向量字段存图片 Embedding再加一个商品类目标签和一个上架时间戳方便后续做过滤。这个设计兼顾了检索纯度和业务筛选是最常见的模板之一。字段名类型说明idINT64主键用业务商品 ID不自增vecFLOAT_VECTOR图片向量dim768tagVARCHAR商品类目用于过滤tsINT64上架时间戳用于过滤维度必须和 Embedding 模型的输出维度一致不要自己截断否则模型训练的语义空间被破坏召回率会明显下降。主键要用稳定的业务唯一键不要用自增 ID否则数据源重复推送时会产生大量主键冲突upsert 的坑会非常难排查。4.2 索引类型与参数初始值一张表看懂怎么起步索引是向量数据库的灵魂。Milvus 里内置了多种索引选错索引类型和参数性能和效果差距可以超过一个数量级。我直接给出一张选型对照表这也是我最常给团队讲的一张表索引原理一句话内存占用适用场景初始参数FLAT暴力全扫描极高百万级以下、需要绝对精确无IVF_FLAT聚类分桶桶内精确搜索中百万到千万级nlist4096, nprobe16HNSW多层图导航高千万到亿级、高召回要求M16, efConstruction200, efSearch64DISKANN磁盘索引内存缓存低亿级以上、成本敏感基本默认HNSW 的M是每个节点的最大连接数越大图越密、召回越高但内存越大通常 16 到 32 就够efConstruction是建索引时的搜索宽度影响索引质量建议 100 到 200efSearch是查询时的搜索宽度影响召回和延迟起步设 64 足够不要一下拉到 512延迟会翻好几倍。IVF_FLAT 的nlist是聚类桶数按数据量的平方根量级估1000 万条向量设 4096 左右是合理起点nprobe是查询时搜多少个桶起点设 16然后根据召回率动态调。4.3 用 Python 客户端把集合建起来最小可用代码我用官方 Python 客户端走一遍建立集合和索引的流程。这里用的是新版轻量客户端连接方式和老版connections.connect不同注意区分。from pymilvus import MilvusClient client MilvusClient(urihttp://localhost:19530) schema client.create_schema(auto_idFalse) schema.add_field(id, int64, is_primaryTrue) schema.add_field(vec, float_vector, dim768) schema.add_field(tag, varchar, max_length64) schema.add_field(ts, int64) index_params client.prepare_index_params() index_params.add_index( field_namevec, index_typeHNSW, metric_typeCOSINE, params{M: 16, efConstruction: 200} ) client.create_collection( collection_nameproduct_img, schemaschema, index_paramsindex_params ) client.load_collection(collection_nameproduct_img)代码逻辑不复杂但有三个点容易踩坑。第一dim768必须和 Embedding 模型的输出维度严格对齐建完集合后维度改不了只能删了重建。第二metric_type选COSINE还是IP要看 Embedding 模型当时的训练方式拿不准就选COSINE它对向量模长不敏感业界兼容性最好。第三load_collection这行不能省索引建好不等于能查必须把集合加载进内存这一步不做会直接报“collection not loaded”。4.4 导入数据与调检索参数从能查到够快集合建好后导入和检索是连着来的。我先给出批量导入和一次检索的完整代码然后讲参数怎么调。import time batch [ {id: i, vec: embedding_list[i], tag: tag_list[i], ts: ts_list[i]} for i in range(len(embedding_list)) ] client.insert(collection_nameproduct_img, databatch, timeout300) query_vec embedding_model.encode(query_text) res client.search( collection_nameproduct_img, data[query_vec], limit10, search_params{metric_type: COSINE, params: {ef: 64}}, filterts 1700000000 and tag in [shoes, bag], output_fields[id, tag] )批量导入时一次性把所有数据塞进去很容易超时。常见做法是每条请求 500 到 1000 条向量或者按文件大小 200MB 一批这样既能跑满带宽又不会让服务端内存被打爆。导入前先删掉集合重新建索引比反复增量插入然后等 compaction 要快得多。检索时最需要调的是ef它对应 HNSW 查询时的搜索宽度。limit10是返回条数但ef要明显大于limit比如要 Top10 就至少设 64否则图搜索过程中候选集太小真正最近的 10 条反而丢出去。filter里的时间戳和标签条件会在向量检索前做一次过滤减少参与距离计算的向量数量这对提升查询速度是决定性的。每次调完参数对比召回率和 P95 时延这两个指标及格线一般是召回率不低于 95%P95 时延控制在 50ms 内。5. 避坑指南Milvus 上线最常翻车的 5 个排查场景5.1 召回率低得离谱检索参数太小索引还没生效现象明明数据导入成功了查出来的结果跟需求完全对不上甚至相似的商品都排不到前面。原因通常有两个一是 HNSW 的ef或 IVF 的nprobe设得太小搜索宽度不足二是集合还没建好索引就查询了Milvus 只能用暴力扫描而暴力扫描在千万级数据上本来就慢且超时。解决先确认索引状态再逐步加大ef或nprobe每次加一倍观察召回率。如果数据量只有几十万建议直接用 FLAT 索引召回率 100%省去调参的烦恼。5.2 查询节点内存暴涨最后 OOM索引参数与服务内存预算没对齐现象服务跑了一两周后QueryNode 内存持续上涨最终容器被杀。原因HNSW 的M和efConstruction设得太大索引体积超出预期或者加载的集合太多没有及时释放不用的集合。解决上线前先用一小批数据建索引量一下内存占用再线性外推全量数据的索引大小。一个经验公式是HNSW 索引大小大约是向量条数 × 维度 × 4 字节 × (1 M/20)按这个估算内存预算。另外不常用的集合记得调用release_collection把内存让出来。5.3 写入速度上不去但 CPU 也不忙批量太小主键冲突校验太多现象每秒写入只有几百条远低于预期看 CPU 又不忙。原因客户端每批写入数据太小比如一条一条 insert服务端要反复做网络往返和主键去重瓶颈在网络和调度而不是计算。解决把批量调到 1000 条一批或者按 200MB 一个文件用批量导入工具写入前在业务侧先把主键去重避免插入阶段大量主键冲突导致重试。5.4 删了数据还能搜到compaction 没跑删除标记没清干净现象对集合执行了 delete结果检索返回的结果里还包含已删除的旧数据。原因Milvus 的删除是标记式删除数据段要等后台 compaction 才能真正清理删除后立刻查旧数据还会从老 segment 里被检索出来。解决删除操作后触发一次手动 compaction或者等待系统自动调度。排查时用collection.compact()强制合并数据段完事再查一次。这个坑在频繁更新数据的场景特别容易撞上别把删除当成同步操作。5.5 加了 filter 之后性能崩了标量字段没建索引现象不加过滤条件时查询 20ms加上tag in [...]过滤后变成 800ms。原因你只给向量字段建了索引tag和ts没有建标量索引过滤条件只能全段扫描等于白加了过滤。解决给高频过滤字段单独建倒排索引在创建集合时追加一个标量索引。常见做法是给tag建index_type: inverted类型的索引过滤性能能快一个量级以上。另外注意过滤表达式写法尽量把区分度高的字段放前面减少中间结果集。6. 未来一年你会遇到的向量库形态混合检索、Serverless 与成本之争未来最明显的一个方向是混合检索。向量检索擅长语义相似但精确匹配和关键词命中还得靠全文检索所以 Milvus 已经在查询接口里融合了表达式过滤能力下一步的演进方向一定是把向量和标量放在同一个分布式执行计划里做优化而不是像现在这样过滤后再走向量索引。做 RAG 的团队今年就能感受到这个变化知识库里的精确 ID 过滤、时间范围过滤会越来越顺滑。另一个方向是 Serverless 化。存算分离的架构已经铺好了路存储和计算独立扩缩容未来查询节点可以做到按负载自动伸缩低谷期缩到零高峰期拉起来。这会直接改变中小团队的投入方式不用再为了峰值买一整年的机器。成本层面DiskANN 这类磁盘索引会在亿级数据场景里大规模替代 HNSW成本和召回率之间的天平会重新倾斜。现在值得投入的不是等待某个“完美版本”而是把你自己的评估基准建好。我建议每个团队都准备一个固定的评测集几百条真实 query标注好标准答案每次升级版本或换索引都用它跑一遍召回率。没有这个基准你会被官方 benchmark 和社区帖子绕晕。我个人的习惯是把每次向量化都写成“三件套”记录原始数据 ID、Embedding 模型名称和版本、归一化状态。这听起来繁琐但后来从 Milvus 换到另一个引擎时导出的向量数据直接复用省了一整周重跑模型的功夫。另一个教训是别把业务代码跟某个客户端 API 绑死向量导入导出的数据格式尽量中立切换引擎时你会感谢这个决定。希望帮到你。本文还有配套的精品资源点击获取