Milvus 3.0 外表能力解读:如何让数据留在S3与湖上 ,Milvus 以只读模式检索

2026-09-223 分钟阅读

在不少 AI 系统中,embedding 和元数据会直接保存在数据湖中,由上游 Pipeline 负责生成和更新,并在数据平台统一进行版本管理。例如,商品数据处理时,通常会把商品属性和多模态 embedding 写成 Parquet 文件存到 S3;大模型检索或训练的语料,也可能直接维护在 Iceberg 或 Lance 表中。

但传统向量数据库数据,往往需要将数据存储在本地,构建一份专门用于在线业务的副本,才能完成高效检索。

这时候,如果想对已经存在数据湖里的文件做高性能向量检索,通常有两个办法:

建设一个ETLPipeline,把数据复制进向量数据库。这样做的优点是可以使用灵活的向量索引,并保证在线检索的高性能,代价则是数据的多副本存储。以及后续每次商品目录变化、embedding 模型升级,字段补写,都要触发的数据同步工作。

直接查询数据湖。优点是无需复制数据;缺点是直接查询湖中的文件时,通常无法直接利用向量数据库为在线检索构建的 HNSW 等 ANN 索引,因此可能退化为大范围甚至全量扫描,难以满足生产环境的低延迟要求。

Milvus 3.0 的External Collection带来了第三种选择。

借助这一能力,我们可以让源数据继续留在 Parquet、Iceberg、Lance、Vortex 或其他受支持的外部格式中。Milvus 只管理指向外部数据的引用、字段映射,以及为检索构建的索引、manifest 等服务状态。主要包括:

external_source,用于标识外部文件或数据表;

external_spec,用于描述源数据格式和存储访问方式;

external_field映射,用于把 Milvus schema 中的字段对应到外部数据集中的列;

Milvus 为检索构建的索引、manifest 和服务状态。

如此一来,使用时,只需要将外部字段映射到 Milvus schema,定义所需索引并执行 Refresh;完成 Load 后,即可继续使用 Milvus 的 search 和 query API。

需要注意的是,并非每次查询都需要从对象存储重新读取数据。索引和高频访问的热数据可以缓存在本地,从而降低远程读取对查询延迟的影响。

这样一来,在线检索不必再创建一份独立的源数据副本,同时仍可以通过 Milvus 的索引和查询引擎提供低延迟检索;Spark、训练流程、评测任务和治理工具仍旧在原本的位置工作,不同工作负载可以建立在同一份数据湖数据之上。

01

External Collection如何支持在线离线多种负载,减少数据拷贝?

对于一个数据团队来说,External Collection的第一层价值在于存储优化,避免团队将几TB的数据在不同Pipeline中重复搬运与存储。

但更深一层的价值则在于,它以非常低的成本保证了不同ipeline中的数据能够保持一致。

依旧是商品目录这个例子。过去,数据平台会产出 Parquet 原始数据,然后一条 ETL 作业转换后灌进向量库形成副本一。接着,推荐团队做离线召回分析,又导一份进自己的 Spark 集群,形成副本二。然后,模型迭代要换 embedding,于是重算、回灌,每份副本都得跟着更新。

每多一个消费方,就多一份拷贝、多一条同步链路、多一处可能对不上的账。为了让这些数据保持同步和一致,团队需要付出大量的工程、协调和机会成本。

但在 External Collection 上,原始数据始终只有一份。Milvus 可以通过 External Collection 为商品搜索、推荐或 Agent 检索提供在线服务。与此同时,其他系统可以直接处理数据湖中的同一份数据:

Spark 可以识别重复商品;

训练流程可以使用新模型生成 embedding;

数据质量任务可以发现格式错误或异常记录;

评测流程可以比较不同模型版本的检索质量;

批处理任务可以生成摘要、标签或新的元数据。

离线处理可以把改进后的数据或新字段重新写回数据湖。下一次 Refresh 完成后,更新后的数据版本即可对 Milvus 查询可见。不再需要单独维护一套 export / import 流程,专门为了在线服务再重建一份数据副本。

治理边界也仍然清晰。Milvus 继续负责 Collection 级的访问控制,并管理访问外部存储所需的身份与凭证;源数据本身的版本、血缘和所有权则仍由数据湖平台管理。

02

External Collection 的数据源支持与安全机制

目前,External Collection已支持 AWS S3、阿里云 OSS、Google Cloud Storage(GCS) 等主流云厂商,以及 MinIO 等 S3 兼容的自建存储。

格式上,External Collection 首批支持以下主流的开放格式与数据源:

实际应用中,你给数据湖选的存储格式,会一路影响线上检索的延迟和成本。通常来说,Lance、Vortex 这类现代列存随机读更快、更省,

考虑到不同Pipeline中数据管理方式的差异,External Collection 还支持字段映射(external field mapping)。例如,源数据中的product_id可以映射为 Milvus 中的id字段,image_vec可以映射为embedding。如果源表字段很多,也不需要把所有列都暴露给 Collection。这意味着数据平台不必为了适配在线服务数据库,就去重命名或重写自己的源数据。

另外,针对 Iceberg、Milvus 快照这类带版本的数据源,External Collection还能指定某个历史快照来建表或刷新。这样你就可以检索「上周二那个版本」的数据,做可复现的评测、回归对比或审计。不必担心做评测时,数据库中的源数据发生更新,影响最终评测效果。与此同时,底层文件也可以在同一时间,也可以继续被数据技术栈中的其他系统使用。

最后,关于数据访问安全问题。External Collection 支持基于 IAM Role 的访问方式以及跨账号 STS AssumeRole,可将身份认证交由云厂商的身份体系管理,避免在配置中长期保存静态 Access Key。(注:这里的存储身份决定的是 Milvus 如何访问源数据。Milvus 内部的授权仍然属于另一个独立的安全边界。)

03

如何创建、建立索引、Refresh 和查询 External Collection

External Collection 的生命周期主要有四个步骤:

定义外部数据源,并把其中的列映射到 Milvus schema;

定义当前工作负载需要的索引;

执行 Refresh,让 Milvus 发现源数据并准备一个可查询的数据版本;

Load Collection,然后照常使用 Milvus 的 search 和 query API。

下面继续以商品目录为例,把它表示成一个 External Collection:

import json
import time
from pymilvus import DataType, MilvusClient

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus",
)

schema = client.create_schema(
    external_source="s3://my-lake/datasets/products/",
    external_spec=json.dumps(
        {
            "format": "parquet",
            "extfs": {
                "cloud_provider": "aws",
                "region": "us-east-1",
                "use_iam": "true",
                "iam_endpoint": "https://sts.us-east-1.amazonaws.com",
            },
        }
    ),
)

schema.add_field(
    field_name="id",
    datatype=DataType.INT64,
    external_field="product_id",
)

schema.add_field(
    field_name="embedding",
    datatype=DataType.FLOAT_VECTOR,
    dim=768,
    external_field="image_vec",
)

schema.add_field(
    field_name="title",
    datatype=DataType.VARCHAR,
    max_length=256,
    external_field="product_name",
)

schema.add_field(
    field_name="stock",
    datatype=DataType.INT64,
    external_field="stock",
)

schema.add_field(
    field_name="rating",
    datatype=DataType.FLOAT,
    external_field="rating",
)

client.create_collection(
    collection_name="products_ext",
    schema=schema,
)

索引使用普通的 Milvus 接口:

index_params = client.prepare_index_params()

index_params.add_index(
    field_name="embedding",
    index_type="HNSW",
    metric_type="COSINE",
)

index_params.add_index(field_name="stock", index_type="AUTOINDEX")
index_params.add_index(field_name="rating", index_type="AUTOINDEX")

client.create_index(
    collection_name="products_ext",
    index_params=index_params,
)

然后对外部数据源执行 Refresh:

job_id = client.refresh_external_collection(
    collection_name="products_ext",
)

while True:
    progress = client.get_refresh_external_collection_progress(job_id=job_id)

    if progress.state == "RefreshCompleted":
        break

    if progress.state == "RefreshFailed":
        raise RuntimeError(progress.reason)

    time.sleep(2)

Refresh 完成后,就可以像普通 Milvus Collection 一样 Load 并进行检索:

client.load_collection("products_ext")

results = client.search(
    collection_name="products_ext",
    data=[query_vec],
    anns_field="embedding",
    filter="stock > 0 and rating >= 4.0",
    limit=10,
    output_fields=["id", "title", "stock", "rating"],
)

比普通的Milvus Collection, search 调用本身的变化不大,区别主要在于数据的整个生命周期从哪里开始。由 Milvus 管理的 Collection,从数据写入或导入 Milvus 开始;External Collection 则从引用一份已经存在于外部的数据开始。

04

Refresh是如何获取外部数据变化的?

从 Milvus 这一侧看,External Collection 是只读的,但底层的数据湖数据集并不需要一直保持不变。

假设商品数据处理流程新增了一批数据、更新了元数据,或者写入了由新模型生成的 embedding。Milvus 不会持续跟踪源路径中出现的每个对象。这些变化需要通过Refresh才会变得可见。

Refresh 会读取外部元数据,解析源数据 fragment,更新把这些 fragment 关联到 Milvus Collection 的 manifest,并准备相应的索引状态。

整个过程可以增量完成:Milvus 会识别没有发生变化的源 fragment,并复用它们已有的 segment 和索引结果。只有新增或发生变化的 fragment 才需要重新处理。因此,即使是一份数 TB 的数据集,只改动其中一小部分,也不需要重新做一次完整的数据导入和完整的索引重建。

Refresh 还为在线服务提供了一个明确的数据版本边界。在新版本准备期间,查询仍然使用上一个已经发布的版本。等 Refresh 完成后,新版本才会作为一个完整版本对外可见,而不会让查询看到旧数据和部分准备完成的新数据混在一起。

这种模式很适合按小时构建的商品目录、每天夜间更新的知识库、周期性的 embedding 更新、模型生成特征的处理流程,以及类似的批处理工作负载。

05

Lazy Loading :如何在内存中只定向加载需要字段的数据

如果在线服务层在回答查询前仍然需要把所有数据加载到本地,那么把源数据记录留在对象存储中也起不到多少作用。

但启用 Milvus Tiered Storage 后,Collection Load 时,QueryNode 一开始可以只保留一些轻量级元数据,例如 schema 信息、索引定义、chunk map,以及指向远程对象的引用。字段数据只有在查询需要时,才会按 chunk 从远端读取;索引也可以一直留在远端,第一次使用时再加载并缓存到本地。经常使用的数据会保持热状态,不常使用的数据则可以被淘汰。

这一点对于字段很多的 AI 数据集来说非常有价值。一条商品记录可能包含多个 embedding、长文本描述、原始 JSON、图片元数据、自动生成的摘要、库存、价格、评分,以及许多其他属性。一次典型的相似度搜索,可能只会用到一个向量字段,再加上库存、价格和评分。没有理由仅仅因为其他字段也属于同一条记录,就让它们长期占用在线服务的内存。

总的来说,External Collection 可以从两个层面缩小在线服务侧的数据占用:

第一层是 schema 级投影。通过external_field,External Collection 可以只暴露应用需要的源数据列。其他列继续留在数据湖中,不进入当前的服务 schema。

第二层是运行时投影。启用 Tiered Storage 后,QueryNode 可以只读取并缓存实际工作负载需要的字段和索引,而不是在 Load 阶段一次性加载所有已映射数据。

这里有一个明确的取舍。如果查询访问了尚未缓存的冷字段或冷索引,第一次访问时可能会产生远程读取的额外开销。可以通过 warm-up 策略预先加载对延迟敏感的字段或索引,同时利用缓存和淘汰策略,避免低频使用的数据长期占用本地资源。

当然,我们的重点并不是要让对象存储表现得像内存一样,而是让内存和本地磁盘围绕检索工作负载真正访问的工作集(working set)来配置,而不是按照源数据集的总规模和总宽度来配置。

而针对不同源数据格式,在按需访问时可能表现出的不同 I/O 特征。External Collection 并不会消除这些存储层面的差异;它只是让 Milvus 可以在这些数据格式之上建立检索层。

06

External Collection 支持哪些搜索和索引能力

External Collection 并不是简单地让 Milvus 指向一个 embedding 文件目录,然后扫描其中的数据。

生产环境中的搜索结果很少只取决于向量相似度。它还可能取决于精确关键词、访问权限、库存、时间戳、类别、价格、来源质量或业务排序信号。

因此,Milvus 会在外部数据上构建检索结构,并通过标准的检索引擎执行查询。根据字段和工作负载,Milvus 可以构建:

用于 ANN 搜索的向量索引;

用于元数据过滤的标量索引;

用于半结构化属性的 JSON 索引;

用于词法检索的 BM25 和全文索引;

Milvus 数据模型支持的 Function 生成字段。

最终支持包括向量检索、关键词、全文检索、标量过滤、混合检索与排序在内的完整生产级检索能力。

更广义地说,External Collection 为存放在数据湖中的数据提供了一条数据库级检索路径,而不只是提供了一种从文件中读取向量的方法。

07

External Collection 适合什么场景,又不适合什么场景

External Collection并不能替代流式写入路径。它更适合下面这些情况:

权威数据已经存放在 Parquet、Vortex、Lance、Iceberg 或其他受支持的外部数据源中;

数据集主要通过批处理生成,而不是高频事务写入;

维护第二份服务副本会带来明显的 ETL、数据新鲜度或治理成本;

多个系统需要使用同一份开放数据;

可以接受以明确的 Refresh 边界来控制在线数据的新鲜度;

希望使用 Milvus 的生产级检索能力,但不希望让 Milvus 接管源数据记录。

下面这些情况,普通 Milvus Collection 仍然更合适:

应用会持续 insert 或 upsert 数据;

delete 需要通过在线写入路径及时生效;

工作负载依赖 External schema 尚不支持的 Collection 能力;

在线服务设计本来就希望把所有需要的数据常驻内存,以避免远程 cache miss。

普通 Milvus Collection 与 External Collection 的主要区别如下:

另外,还有几个边界需要注意:

External Collection 是只读的。源数据的修改发生在 Milvus 之外。

零复制指的是源数据记录。索引、manifest、缓存和计算资源仍然需要成本。

Refresh 需要显式执行。它不是流式同步机制。

外部数据源必须保持可访问。Search、索引构建和 Refresh 仍然依赖存储访问权限和凭证。

需要 Storage V3。在开源 Milvus 3.0 中,使用 External Collection 前必须先启用 Storage V3。

External Collection 不会替代上游处理。Embedding 生成、聚类、去重和数据清洗,仍然由相应的上游系统完成。

总的来说,一个系统可以对快速变化的在线数据使用普通 Milvus Collection,同时对规模大、批量生成、天然存放在数据湖中的数据使用 External Collection。

AI Assistant