Milvus 生产落地案例
生产落地案例
案例一:企业知识库的 RAG 召回层
业务背景:一家公司要把内部上万份文档做成问答助手,文档切块后大约几十万到百万级 chunk,需要按用户所属部门做权限隔离,并且要在提问后很短时间内给出引用来源。
怎么用:文档切块后用文本 embedding 模型统一转成向量写入 Milvus;用 tenant_id 和 department 作为标量字段,检索时拼进 filter 表达式做权限过滤,避免越权召回;再用 category、更新时间等字段支持「只看近半年」这类业务筛选。索引选 HNSW、度量选 COSINE,因为文本 embedding 的长短差异会让模长失真,余弦只看方向更稳。
关键配置:M 取 16 到 32 之间,efConstruction 取 200 左右(建索引是一次性成本,可以给足),查询期 ef 从 64 起步向上扫;一致性级别默认用 Bounded,只有在「用户刚上传文档马上要能搜到」的路径上单独指定 Strong。为了控制内存,把一些不常查的标量字段做成动态字段,减少静态字段的开销。
效果观测:核心看两个指标,一是 recall(用人工标注的查询-文档对算命中率),二是 P95 延迟。这两者随 ef 上升此消彼长,做法是固定一组真实查询,扫 ef 画曲线,选在召回已经趋平、延迟还没陡增的位置。这里没法给出通用数值,因为不同数据规模与机器配置差异很大,必须自己在真实数据集上压测。此外要盯着内存占用与 segment 数量,它们往往比延迟更早出问题。
踩过的坑:一是索引还在 building 阶段就发了流量,搜索报错,解决办法是把索引构建状态纳入发布检查,状态未完成不允许切流量;二是最初按「每篇文档一次 insert」写数据,产生了极大量的小 segment,查询延迟随文档数增长而恶化,改成攒批写入并依赖 compaction 合并后显著改善;三是 delete 掉的旧版本文档偶尔还能被召回,原因是默认一致性级别下删除标记尚未生效,对这类敏感路径改为 Strong 读;四是换了 embedding 模型后忘了统一维度,写入直接报维度不匹配,后来把模型名与维度写进配置并在启动时校验,从源头拦住。
案例二:多租户电商的相似商品推荐
业务背景:一个 SaaS 电商平台上有大量互相隔离的店铺,每个店铺都要「看了又看」的相似商品推荐,店铺之间数据不能串,且不同店铺的商品规模相差几个数量级——头部店铺上百万商品,长尾店铺只有几百个。
怎么用:商品主图用视觉模型转成向量,标题用文本模型转成向量,两条召回通道各建一个向量字段,最后在应用层做融合排序。租户隔离不用建几百个 collection,而是用 partition key 机制,让 Milvus 按租户自动路由到对应的逻辑分区,既避免了写放大,也避免了全库扫描。小店铺的查询天然落在小分区上,延迟反而比大店铺更低。
关键配置:视觉向量维度较高,内存压力大,对头部大租户单独评估了 DiskANN——把大部分索引放 SSD、内存只放关键结构,用可接受的延迟换取内存容量的大幅下降。度量上视觉与文本向量都做了归一化,用 IP 与 COSINE 均可,团队最终统一用了 COSINE 以免两套语义混淆。查询期按租户规模分档设 ef:小租户用小的 ef 保证延迟,大租户用大 ef 保召回。
效果观测:除了常规的召回与延迟,这个场景额外盯了两个业务指标——推荐位的点击率与加购率,用于反向验证向量召回的语义质量;以及每台机器的内存水位,作为扩容触发条件。实践中发现,模型迭代对业务指标的影响远大于索引参数微调,所以把调参收敛到「满足延迟 SLO 的前提下召回最大化」,不追求极致。
踩过的坑:一是早期按租户建 collection,租户数上千后元数据操作变得很慢,创建和加载 collection 都成了瓶颈,改用 partition key 后彻底解决;二是大促期间写入量激增,同时在建索引导致资源竞争,查询延迟抖动明显,后来的做法是把索引重建放到流量低谷期并限流;三是向量维度高加上 HNSW 的 M 设得偏大,单机内存被撑满触发容器重启,把 M 调回适中并引入 DiskANN 后稳定;四是不同店铺用了不同版本的视觉模型,导致跨店铺比对的语义基线不一致,最终强制全平台统一模型版本,并在数据里冗余记录模型版本用于回溯。

