创建外部CollectionCompatible with Milvus 3.0.x

外部 Collection 是 Milvus 中的一种数据 Collection,它可直接访问来自外部存储系统或数据库表(如 AWS S3 和 Iceberg)的数据,而无需将数据复制到 Milvus 中。它作为数据湖上的查询层,同时保持与 Milvus 查询接口的兼容性。

此功能需要 Storage V3 支持。有关启用说明和兼容性注意事项,请参阅Storage V3。

概述

在典型的 AI 数据管道中,用户可能已将数据以 Parquet 或其他格式存储在存储系统(如 AWS S3)中。为了让 Milvus 能够使用这些外部存储的数据,用户通常需要通过提取-转换-加载(ETL)管道将其导入 Milvus 的自有存储中。

这种“将数据带入 Milvus”的工作流会产生难以同步的冗余数据,并增加了确保数据一致性所需的工程维护负担。

Bring data to compute workflow 将数据引入计算工作流

为解决这些问题,Milvus 提供了外部 Collections 功能,让您能够直接从 Milvus 访问外部存储的数据,无需担心数据同步和 ETL 管道的问题。

Bring compute to data workflow “将计算带到数据”工作流

外部 Collection 创建后,即可直接访问您的数据,并将数据保留在您存储的原始位置。在后台,Milvus 会生成清单文件,用于记录 Milvus 元数据与外部数据文件中各行之间的映射关系。清单文件准备就绪后,您就可以像在任何托管 Collection 中一样,在外部 Collection 中创建索引。

当数据发生变化时,手动触发亚秒级刷新即可更新元数据,确保 Milvus 始终保持最新状态。

步骤 1:创建 Schema

与创建托管 Collection 一样,在创建外部 Collection 之前,您也需要先创建 Schema。不过,该 Schema 与托管 Collection 的 Schema 略有不同。

from pymilvus import MilvusClient, DataType

schema = MilvusClient.create_schema(
    external_source='s3://s3.<region-id>.amazonaws.com/<bucket>/',
    external_spec='{
        "format": "parquet",
        "extfs": {
            ...
        }
    }'
)
import com.google.gson.JsonObject;
import io.milvus.v2.service.collection.request.CreateCollectionReq;

JsonObject externalSpec = new JsonObject();
externalSpec.addProperty("format", "parquet");
externalSpec.add("extfs", new JsonObject());

CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder()
        .externalSource("s3://s3.<region-id>.amazonaws.com/<bucket>/")
        .externalSpec(externalSpec)
        .build();
import (
    "github.com/milvus-io/milvus/client/v2/entity"
    client "github.com/milvus-io/milvus/client/v2/milvusclient"
)

schema := entity.NewSchema().
    WithName("product_embeddings").
    WithExternalSource("s3://my-bucket/embeddings/").
    WithExternalSpec(`{"format": "parquet", "extfs": { ... }}`)
// node
export fields='[
        {
            "fieldName": "product_id",
            "dataType": "Int64",
            "isPrimary": true
        },
        {
            "fieldName": "embedding",
            "dataType": "FloatVector",
            "elementTypeParams": {
                "dim": "768"
            }
        },
        {
            "fieldName": "product_name",
            "dataType": "VarChar",
            "elementTypeParams": {
                "max_length": 512
            }
        }
    ]'

要为外部Collection创建Schema,您需要指定源数据 URI、数据格式以及身份验证设置。

参数名称

参数描述

示例值

format

目标源数据文件的格式。

parquet

snapshot_id

一个有效的 Iceberg 表快照 ID。此参数仅在将 `format ` 设置为 `iceberg_table` 时适用。

473984310232959286

extfs

以字符串形式的 JSON 结构表示的外部文件系统设置。

--

您可以通过以下选项设置身份验证设置:

使用 AWS 访问密钥/安全密钥

此选项适用于自托管的 MinIO,或您已拥有用于工作的 AK/SK 的场景。

{
    "format": "...",
    "extfs": {
        "access_key_id":     "AKIA..",
        "access_key_value":  "u4Lh...",
        "region":            "us-west-2",
        "cloud_provider":    "aws",
        "use_ssl":           "true",
        "use_virtual_host":  "true"
    }
}

参数名称

参数说明

示例值

extfs.access_key_id

访问密钥 ID

AKIA...

extfs.access_key_value

访问密钥值

u7LH...

extfs.region

云区域 ID

us-west-2

extfs.cloud_provider

云服务提供商 ID

aws

extfs.use_ssl

是否使用 SSL 建立连接。

true

extfs.use_virtual_host

是否使用虚拟托管访问您的存储桶。

有关详细信息,请参阅这篇文章。

true

使用 AWS IAM

此选项适用于 Milvus 在 EC2 实例或 EKS 集群上运行的场景。在此情况下,您无需硬编码 AK/SK。

{
    "format": "...",
    "extfs": {
        "use_iam":           "true",
        "iam_endpoint":      "https://sts.<region>.amazonaws.com",
        "region":            "us-west-2",
        "cloud_provider":    "aws",
        "use_ssl":           "true"
    }
}

参数名称

参数 描述

示例值

extfs.use_iam

是否使用 AWS IAM。

对于此选项,请将其设置为"true" 。

true

extfs.iam_endpoint

一个有效的 AWS STS 端点。

有关详细信息,请参阅本文。

https:*//*sts.<region>.amazonaws.com

extfs.region

云区域 ID

us-west-2

extfs.cloud_provider

云提供商 ID

aws

extfs.use_ssl

是否使用 SSL 建立连接。

true

使用 Milvus 全局凭据

当您将外部数据存储在 Milvus 存储桶中时,此选项适用,且可直接使用milvus.yaml 中指定的全局 MinIO 设置来访问数据。

{
    "format": "...",
    "extfs": {
        "storage_type": "remote"
    }
}

使用 IAM 角色 ARN

当您的组织使用不同的 AWS 账户来管理 Milvus 集群和存储目标数据文件的存储桶时,应使用此选项。

在这种情况下,存储桶所有者应创建一个 IAM 角色,该角色

  • 该角色应绑定AmazonS3FullAccess 或更精细的存储桶访问策略。

  • 在角色的“信任策略”的“条件”字段中包含一个自定义的sts:ExternalId 。

随后,存储桶所有者应向您提供该 IAM 角色的 ARN 和外部 ID,以便您使用这些值调用 `sts:AssumeRole ` 来承接该 IAM 角色。

以下是一个示例权限策略,可附加到 IAM 角色上并包含允许的权限。您可以根据需求进行调整。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetBucketLocation"
            ],
            "Resource": "arn:aws:s3:::SOURCE-DATA-BUCKET"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::SOURCE-DATA-BUCKET/*"
        }
    ]
}

此外,与该 IAM 角色关联的信任策略定义了谁被允许承担该角色。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::ACCOUNT_RUNNING_MILVUS:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "YOUR_UNIQUE_EXTERNAL_ID"
        }
      }
    }
  ]
}

获取 IAM 角色 ARN 和外部 ID 后,您可以按以下方式设置external_spec 参数:

{
    "format": "...",
    "extfs": {
        "cloud_provider": "aws",
        "region": "us-west-2",
        "storage_type": "remote",
        "use_ssl": "true",
        "use_iam": "true",
        "role_arn": "arn:aws:iam::306787000000:role/lentitude-bucket-role",
        "external_id": "YOUR_UNIQUE_EXTERNAL_ID",
        "load_frequency": "900"
    }
}

参数名称

参数描述

示例值

extfs.cloud_provider

云服务提供商 ID

aws

extfs.region

云区域 ID

us-west-2

extfs.use_ssl

是否使用 SSL 建立连接。

true

extfs.use_iam

是否使用 AWS IAM。

请将此选项设置为"true" 。

true

extfs.role_arn

从存储桶所有者处获取的 IAM 角色 ARN。

arn:aws:iam::306787000000:role/...

extfs.external_id

从存储桶所有者处获取的外部 ID。

--

extfs.load_frequency

Milvus 获取临时身份验证凭据的间隔(单位:秒)。

900

步骤 2:添加字段

准备好Schema后,您可以按以下方式添加字段:

schema.add_field(
    field_name="product_id",
    datatype=DataType.INT64,
    external_field="id" # field name in the external data file
)
schema.add_field(
    field_name="product_name",
    datatype=DataType.VARCHAR,
    max_length=512,
    external_field="name"
)
schema.add_field(
    field_name="embedding",
    datatype=DataType.FLOAT_VECTOR,
    dim=768,
    external_field="vector"
)
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;

schema.addField(AddFieldReq.builder()
        .fieldName("product_id")
        .dataType(DataType.Int64)
        .externalField("id")
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("product_name")
        .dataType(DataType.VarChar)
        .maxLength(512)
        .externalField("name")
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("embedding")
        .dataType(DataType.FloatVector)
        .dimension(768)
        .externalField("vector")
        .build());
import (
    "github.com/milvus-io/milvus/client/v2/entity"
    client "github.com/milvus-io/milvus/client/v2/milvusclient"
)

schema = schema.
    WithField(
        entity.NewField().
            WithName("product_id").
            WithDataType(entity.FieldTypeInt64).
            WithExternalField("id"),
    ).
    WithField(
        entity.NewField().
            WithName("product_name").
            WithDataType(entity.FieldTypeVarChar).
            WithMaxLength(512).
            WithExternalField("name"),
    ).
    WithField(
        entity.NewField().
            WithName("embedding").
            WithDataType(entity.FieldTypeFloatVector).
            WithDim(768).
            WithExternalField("vector"),
    )
// node
export schema="{
    \"externalSource\": \"volume://my_volume/path/to/a/folder\",
    \"externalSpec\": \"{\\\"format\\\": \\\"parquet\\\"}\",
    \"fields\": $fields
}"

步骤 3:创建 Collection

将所有字段添加到Schema后,即可创建外部Collection。

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

client.create_collection(
    collection_name="test_collection",
    schema=schema
)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;

ConnectConfig connectConfig = ConnectConfig.builder()
        .uri("http://localhost:19530")
        .token("root:Milvus")
        .build();

MilvusClientV2 client = new MilvusClientV2(connectConfig);

CreateCollectionReq createReq = CreateCollectionReq.builder()
        .collectionName("test_collection")
        .collectionSchema(schema)
        .build();
client.createCollection(createReq);
import (
    "github.com/milvus-io/milvus/client/v2/entity"
    client "github.com/milvus-io/milvus/client/v2/milvusclient"
)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

milvusAddr := "http://localhost:19530"
token := "root:Milvus"

client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
    Address: milvusAddr,
    APIKey: token
})

err = client.CreateCollection(ctx, milvusclient.NewCreateCollectionOption("test_collection", schema))

if err != nil {
    fmt.Println(err.Error())
    // handle error
}
// node
curl --request POST \
--url "${PROJECT_ENDPOINT}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d "{
    \"dbName\": \"my_database\",
    \"collectionName\": \"test_collection\",
    \"schema\": $schema
}"

步骤 4:创建索引

您可以像在管理 Collections 中一样,为外部 Collection 字段创建索引。

index_params = client.prepare_index_params()
# Add indexes
index_params.add_index(
    field_name="embedding",
    index_type="AUTOINDEX",
    metric_type="COSINE"
)
index_params.add_index(
    field_name="product_name",
    index_type="AUTOINDEX"
)
client.create_index(
    db_name="my_database",
    collection_name="test_collection",
    index_params=index_params
)
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.index.request.CreateIndexReq;
import java.util.*;

IndexParam indexParamForIdField = IndexParam.builder()
        .fieldName("product_name")
        .indexType(IndexParam.IndexType.AUTOINDEX)
        .build();
IndexParam indexParamForVectorField = IndexParam.builder()
        .fieldName("embedding")
        .indexType(IndexParam.IndexType.AUTOINDEX)
        .metricType(IndexParam.MetricType.COSINE)
        .build();
List<IndexParam> indexParams = new ArrayList<>();
indexParams.add(indexParamForIdField);
indexParams.add(indexParamForVectorField);
CreateIndexReq createIndexReq = CreateIndexReq.builder()
        .dbName("my_database")
        .collectionName("test_collection")
        .indexParams(indexParams)
        .build();
client.createIndex(createIndexReq);
import (
    "github.com/milvus-io/milvus/client/v2/entity"
    "github.com/milvus-io/milvus/client/v2/index"
    "github.com/milvus-io/milvus/client/v2/milvusclient"
)

collectionName := "test_collection"
indexOptions := []milvusclient.CreateIndexOption{
    milvusclient.NewCreateIndexOption(collectionName, "embedding", index.NewAutoIndex(entity.COSINE)),
    milvusclient.NewCreateIndexOption(collectionName, "product_name", index.NewAutoIndex(index.AUTOINDEX)),
}
indexTask, err := client.CreateIndex(ctx, indexOptions)
if err != nil {
    // handler err
}
err = indexTask.Await(ctx)
if err != nil {
    // handler err
}
client.createIndex({
    db_name: "my_database",
    collection_name: "test_collection",
    field_name: "product_name",
    index_type: "AUTOINDEX"
})
client.createIndex({
    db_name: "my_database",
    collection_name: "test_collection",
    field_name: "embedding",
    index_type: "AUTOINDEX",
    metric_type: "COSINE"
})
export indexParams='[
        {
            "fieldName": "embedding",
            "indexName": "my_vector",
            "indexType": "AUTOINDEX"
        },
        {
            "fieldName": "product_name",
            "indexName": "my_id",
            "indexType": "AUTOINDEX"
        }
    ]'

curl --request POST \
--url "${PROJECT_ENDPOINT}/v2/vectordb/indexes/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d "{
    \"dbName\": \"my_database\",
    \"collectionName\": \"test_collection\",
    \"indexParams\": $indexParams
}"

步骤 5:刷新数据

Collection准备就绪后,请刷新该Collection以生成数据的元数据和索引。

job_id = client.refresh_external_collection(
    db_name="my_database",
    collection_name="test_collection"
)
while True:
    progress = client.get_refresh_external_collection_progress(job_id=job_id)
    print(f"  {progress.state}: {progress.progress}%")
    if progress.state == "RefreshCompleted":
        elapsed = progress.end_time - progress.start_time
        print(f"  Completed in {elapsed}ms")
        break
    elif progress.state == "RefreshFailed":
        print(f"  Failed: {progress.reason}")
        break
    time.sleep(2)
import io.milvus.v2.service.utility.request.GetRefreshExternalCollectionProgressReq;
import io.milvus.v2.service.utility.request.ListRefreshExternalCollectionJobsReq;
import io.milvus.v2.service.utility.request.RefreshExternalCollectionReq;
import io.milvus.v2.service.utility.response.GetRefreshExternalCollectionProgressResp;
import io.milvus.v2.service.utility.response.ListRefreshExternalCollectionJobsResp;
import io.milvus.v2.service.utility.response.RefreshExternalCollectionJobInfo;
import io.milvus.v2.service.utility.response.RefreshExternalCollectionResp;

while (true) {
    GetRefreshExternalCollectionProgressResp resp = client.getRefreshExternalCollectionProgress(
            GetRefreshExternalCollectionProgressReq.builder()
                    .jobId(jobId)
                    .build());
    RefreshExternalCollectionJobInfo jobInfo = resp.getJobInfo();
    if ("RefreshCompleted".equals(jobInfo.getState())) {
        long elapsed = jobInfo.getEndTime() - jobInfo.getStartTime();
        System.out.printf("  Refresh completed in %dms%n", elapsed);
        break;
    } else if ("RefreshFailed".equals(jobInfo.getState())) {
        System.out.printf("  Refresh failed: %s%n", jobInfo.getReason());
    }
    TimeUnit.SECONDS.sleep(2);
}
refreshResult, err := client.RefreshExternalCollection(ctx,
    client.NewRefreshExternalCollectionOption("test_collection"))
jobID := refreshResult.JobID
for {
    progress, _ := client.GetRefreshExternalCollectionProgress(ctx,
        client.NewGetRefreshExternalCollectionProgressOption(jobID))
    fmt.Printf("State: %s\n", progress.State)
    if progress.State == entity.RefreshStateCompleted {
        fmt.Println("Refresh completed!")
        break
    }
    if progress.State == entity.RefreshStateFailed {
        fmt.Printf("Refresh failed: %s\n", progress.Reason)
        break
    }
    time.Sleep(2 * time.Second)
}
// node
curl --request POST \
--url "${PROJECT_ENDPOINT}/v2/vectordb/jobs/external_collection/refresh" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d "{
    \"dbName\": \"my_database\",
    \"collectionName\": \"test_collection\",
    \"externalSource\": \"volume://my_volume/path/to/a/folder\",
    \"externalSpec\": \"{\\\"format\\\": \\\"parquet\\\"}\"
}"

刷新操作是异步的,因此您需要设置一个循环来监控其进度。

  • 刷新操作会扫描数据文件的元数据,并据此生成清单文件。该过程通常需要 150-250 毫秒。

  • 清单文件记录了 Milvus 中的元数据与外部文件中行之间的映射关系。

  • 如果源数据发生更新,您需要手动再次调用刷新操作,以保持 Milvus 的数据最新。

  • 如果刷新操作需要删除所有活跃元数据且不插入任何新数据,则该操作将被拒绝。

后续步骤

刷新外部集合后,您可以加载和释放该 Collection,并在外部 Collection 中执行相似度搜索和查询,就像在任何托管 Collection 中一样,但需注意:用于按需计算的数据库中的 Collection 必须附加到按需集群上才能进行搜索和查询。

在执行 DQL 操作(如搜索、查询、获取和混合搜索)之前,您需要创建一个会话以挂载按需计算集群的计算资源。

如果外部数据源后续包含另一个您希望在 Milvus 中暴露的字段,请向外部 Collection Schema 添加该字段,并再次刷新外部 Collection。有关详细信息,请参阅《修改外部 Collection Schema》。

翻译自DeepL

想要更快、更简单、更好用的 Milvus SaaS服务 ?

Zilliz Cloud是基于Milvus的全托管向量数据库,拥有更高性能,更易扩展,以及卓越性价比

免费试用 Zilliz Cloud
反馈

此页对您是否有帮助?