向量库不再囤数据:Milvus 3.0 零拷贝直读数据湖

做 RAG 的人大概都经历过这么个流程:数据在 S3 上的 Parquet 文件里躺着,你得先把它读出来,跑一遍 Embedding 模型,再把向量写进 Milvus。原始数据一份,向量数据又一份。数据湖一份,向量库一份。

然后问题就来了。数据更新了怎么办?向量过期了怎么办?两份数据怎么同步?半夜跑批的 ETL 管道挂了谁来兜底?

Milvus 3.0 给的答案是:别复制了,我直接读你的数据湖。

这就是它说的 lake-native 架构。核心能力叫 External Collections------在你的 Lance、Iceberg、Parquet、Vortex 文件上直接建索引、直接做向量检索,原始数据一个字节都不用搬进 Milvus。

听起来简单,但背后的工程量不轻。咱们拆开看。

一、先搞清楚「旧模式」痛在哪

传统向量库的运作方式是「自建仓库」:你得把向量数据从数据湖里导出来,灌进 Milvus 自己的存储里。Milvus 管自己的数据叫 native collection------数据进来后就是 Milvus 的了,格式、存储、生命周期都归它管。

这套模式在数据量小的时候没毛病。但量一大,三个问题就冒出来了。

第一,存储翻倍。 1 亿条向量,原始数据存一份,Embedding 存一份,Milvus 内部索引再存一份。做 RAG 的团队对这点应该深有体会------向量库的存储成本经常比原始数据还贵。

第二,同步是真痛。 数据湖里的数据更新了(比如用户信息变了、文档新增了),你得重新跑一遍 Embedding 管线,再写进 Milvus。这个链路越长,数据新鲜度越差。凌晨跑的批处理到第二天用户查到的还是昨天的数据,做实时搜索的团队对此最头疼。

第三,治理被割裂。 数据湖有数据湖的权限体系,向量库有向量库的权限体系。治理团队要管两套,审计要查两份。数据在两个系统间漂移,血缘追踪基本靠人工维护。

这三个问题的根源是同一个:数据被复制了一份。

二、External Collections:不搬数据,只建索引

Milvus 3.0 的 External Collections 做的事情,换成人话就是:你在 S3 上有一堆 Parquet 文件,Milvus 过来看一眼,说「我在这些文件上帮你建个向量索引吧,你查的时候走我的 API,我直接从你的文件里取数据」。

数据不动,索引在 Milvus 这边。

它支持四种文件格式作为数据源:

格式 定位 典型场景
Lance 专为 ML / 向量优化的列存格式 向量数据原生存储
Iceberg 湖仓表格式 已有 Iceberg 湖仓直接接入
Parquet 通用列存 数据湖最常见的存量格式
Vortex Milvus 新存储格式(Arrow 兼容) Loon 引擎默认存储格式

用法上,External Collections 和 native collections 走同一套 API。你写查询代码的时候不用区分「这个 collection 是外部的还是内部的」,接口完全一致。这对存量系统迁移很友好------不用重写业务代码。

数据更新时也不用你操心。Milvus 做增量同步:数据湖新增了文件片段,Milvus 那边的索引会自动感知并刷新。你只管往湖里写数据,检索这边自己跟上。

实操提示 :External Collections 目前是只读的------你可以在上面建索引、做检索,但不能通过 Milvus 往里写数据。写入还是走数据湖自己的链路(Spark、Flink、或者你的 ETL 管道)。Milvus 负责的是「读」这一侧。

三、Loon 引擎:把点读 I/O 砍了两个量级

零拷贝听着好,但有个现实问题:对象存储(S3 / GCS / Azure Blob)的点读延迟远高于本地存储。向量检索本质上是大范围扫描 + 局部精确读取,如果每次点读都去拉一个完整的对象存储文件,延迟会很难看。

Milvus 3.0 对此的解法是引入新存储引擎 Loon。Loon 是 manifest-based 的------不直接去读原始数据文件,而是先读一份「清单」(manifest),清单告诉你向量在哪个文件的哪个位置,然后做精准读取。

效果有多明显?官方给的数据是:

指标 数值
对象存储点读 I/O 9.4MB → 0.07MB
点读 I/O 降幅 约 135×
稀疏索引体积瘦身 约 3×

这意味着以前做一次点读可能要拉 9MB 的数据,现在只需要 70KB。在 S3 这种按请求次数和传输量计费的环境里,这不光是省延迟,还省钱。

Loon 默认用 Vortex 作为存储格式。Vortex 是开放格式,兼容 Arrow 列存协议,不绑定 Milvus 生态------你的其他 Arrow 兼容工具(DuckDB、DataFusion、Polars 等)也能直接读。

四、不只是零拷贝:检索引擎也升级了

lake-native 是 3.0 最大的架构变化,但不是唯一的变化。Milvus 这次还在检索引擎上做了不少加法。

服务端排序和聚合

以前做向量检索后排序,你得在应用代码里做:先从 Milvus 拉回 top-K 结果,再按业务字段(发布时间、评分、价格等)重新排一遍。3.0 把这个操作搬进了引擎------直接在 Milvus 服务端做 ORDER BY、聚合、分面查询(faceted search)。

好处是省了网络往返和 over-fetching。以前要取 1000 条再在应用层筛 50 条,现在在引擎里一步到位。

StructList:一个实体多个向量

一篇文章可以拆成多个 chunk,每个 chunk 有自己的向量。一张图片可以切成多个 patch,每个 patch 一个向量。以前这些得拆成多条记录管理,3.0 的 StructList 允许一个实体下面挂多个向量,统一管理、统一检索。

而且 StructList 支持 late-interaction 模型------ColBERT、ColPali 这类「先细粒度匹配再聚合」的检索范式直接原生支持,不用自己拼。

稀疏索引瘦了 3 倍

混合检索(dense + sparse)是 RAG 的标配。3.0 重新设计了稀疏索引,体积缩小约 3 倍,召回率持平。另外还加了 MinHash 生成、可空向量字段、全文搜索自定义词典等工程细节。

五、快照:让离线评估不停生产

做向量检索系统最怕的事情之一:你想跑一批离线评估(比如测试新的索引参数对召回率的影响),但生产环境在不停写入。你怎么保证评估跑在一份稳定的数据上?

传统做法是拷一份数据出来。Milvus 3.0 的快照功能给了另一种选择------创建一个时间点的只读视图,几乎不占额外存储空间(底层是写时复制 / 指针引用)。你的离线任务在快照上跑,生产写入照常进行。

配合新出的 Spark DataSource V2 连接器,Spark / Databricks / EMR 管道可以直接把 Milvus 当数据源读写。评估、去重、聚类这些批处理任务可以在 Spark 里跑,结果直接写回 Milvus。在线检索和离线分析共享同一份数据底座,不用再来回导。

六、这对你的架构选型意味着什么

说了这么多,落到实际选型上。lake-native 架构到底适合谁?

适合的场景

  • 数据所有权在数据湖------合规或治理要求「数据不能离开湖」,向量库只能做索引层。比如金融、医疗行业的数据治理策略。
  • 已有 Iceberg / Parquet 湖仓------不想为向量检索单独再搞一套存储,直接在现有湖数据上建索引。
  • 多部署共享一份数据------测试、评估、灰度等多个 Milvus 实例指向同一份湖数据,各建各的索引,数据只存一份。
  • RAG 管线想简化同步------数据写入湖后 Milvus 自动感知,不想再维护一套同步管道。

不太适合的场景

  • 超低延迟在线检索------对象存储的点读延迟再怎么优化也快不过本地 NVMe。如果 P99 延迟要求在个位数毫秒,native collection 仍是首选。
  • 高频写入------External Collections 只读,写入还是走湖。如果你的系统是高频写高频读的实时场景,lake-native 的同步链路可能跟不上。
  • 数据量很小------几百万条向量,直接灌进 Milvus 原生存储更省事,没必要绕一圈数据湖。

判断标准:如果数据已经在湖里了、并且量级不算小,用 External Collections 省掉一份拷贝。如果数据是专门为向量检索生成的、对延迟敏感,用 native collection 更直接。两者可以共存------同一个 Milvus 实例里,热数据走 native,冷数据和湖仓数据走 external。

七、和 Paimon 2.0 放在一起看

有意思的是,Milvus 3.0 和差不多同期发布的 Apache Paimon 2.0 在解决同一个问题,但方向不同。

Paimon 2.0 的思路是「把向量列塞进湖表」------在同一个表里同时存 BLOB、VECTOR、VARIANT,索引也在表内建。数据不用离开湖表,索引和查询引擎在表格式层面做。好处是事务一致性更强,坏处是它本质还是表格式,检索能力不如专业向量库丰富。

Milvus 3.0 的思路是「向量库主动靠近数据湖」------数据还是在湖里(Iceberg / Parquet / Lance),Milvus 只做索引和检索。好处是检索引擎更专业(StructList、稀疏索引、late-interaction),坏处是数据湖和向量库毕竟是两个系统,事务边界没有 Paimon 那么天然。

两条路殊途同归,目标都是干掉「数据在湖仓和向量库之间复制一份」这件事。你的架构选哪个,取决于你更信任「让表格式管向量」还是「让专业向量库读湖」。

八、上手路径

如果你已有数据在 S3 上的 Parquet 文件,想试 External Collections,最小步骤是:

  1. 部署 Milvus 3.0------Docker 或 K8s,配好 S3 兼容的对象存储。
  2. 创建 External Collection------指定数据路径和格式,Milvus 自动扫描文件结构。
  3. 建索引------在 External Collection 上建向量索引(IVF、HNSW 等),索引数据存在 Milvus 侧。
  4. 走标准 API 查询------和 native collection 完全一样的查询接口。

快照和 Spark 连接器是可选的------如果你的离线评估管线已经用 Spark,接上就行;如果没有,先用快照功能做时间点隔离也够用。

Milvus 3.0 是 Apache 2.0 协议,支持 S3 / GCS / Azure Blob,Python / Go / Node.js SDK 先行,Java 紧随其后。完整发布说明和源码都在官网(milvus.io)。


lake-native 这个方向,本质上是让向量检索从「自带存储的独立系统」变成「湖仓之上的索引层」。对做 RAG 和语义搜索的团队来说,这意味着数据管线可以更简单,同步可以更少,存储成本可以更低。

至于延迟,Loon 引擎把点读 I/O 砍了两个量级,虽然比不上本地 NVMe,但对绝大多数检索场景已经够用。真正的取舍在前面说过了:延迟敏感走 native,数据在湖走 external。两个可以共存,不用二选一。

向量检索的终局形态是------数据在哪里,检索就在哪里。


你的 RAG 管线还在为「向量同步」熬夜吗?如果是你,选「向量库读湖」还是「湖表管向量」?评论区聊聊。