外部コレクションの作成Compatible with Milvus 3.0.x

外部コレクションとは、Milvusにおけるデータコレクションの一種であり、AWS S3やIcebergなどの外部ストレージシステムやデータベーステーブルから、データをMilvusにコピーすることなくアクセスするものです。これは、Milvusのクエリインターフェースとの互換性を維持しつつ、データレイク上のクエリレイヤーとして機能します。

この機能を利用するには、Storage V3が必要です。有効化の手順や互換性に関する注意事項については、「Storage V3」を参照してください。

概要

一般的なAIデータパイプラインでは、ユーザーはすでにAWS S3などのストレージシステム上に、Parquetやその他の形式でデータを保存している場合があります。Milvusがこの外部に保存されたデータを利用できるようにするには、通常、ETL(Extract-Transform-Load)パイプラインを使用して、Milvus独自のストレージにデータをインポートする必要があります。

この「データをMilvusに持ち込む」というワークフローでは、同期が困難な冗長なデータが生成され、データの一貫性を確保するためのエンジニアリング上のメンテナンス負担が増大します。

Bring data to compute workflow データをコンピュート環境に持ち込む」ワークフロー

これらの問題を解決するため、Milvusは外部コレクション機能を提供しており、データの同期やETLパイプラインを気にすることなく、Milvusから外部に保存されたデータにアクセスできるようになります。

Bring compute to data workflow データをコンピュートに持っていく」ワークフロー

外部コレクションを作成すると、データに直接アクセスでき、データを保存している場所のまま保持できます。バックグラウンドで、Milvusはマニフェストファイルを作成し、Milvusのメタデータと外部データファイル内の行とのマッピングを記録します。マニフェストファイルの準備が整ったら、通常のマネージドコレクションと同様に、外部コレクション内でインデックスを作成できます。

データが変更された場合、手動で1秒未満の更新をトリガーすることでメタデータが更新され、Milvusは常に最新の状態を維持します。

ステップ1:スキーマの作成

マネージドコレクションの作成と同様に、外部コレクションを作成する前にもスキーマを作成する必要があります。ただし、そのスキーマはマネージドコレクションのものと若干異なります。

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
            }
        }
    ]'

外部コレクションのスキーマを作成するには、ソースデータの URI、データ形式、および認証設定を指定する必要があります。

パラメータ名

パラメータの説明

値の例

format

ターゲットとなるソースデータファイルの形式。

parquet

snapshot_id

有効な Iceberg テーブルのスナップショット ID。このパラメータは、formaticeberg_table に設定した場合にのみ適用されます。

473984310232959286

extfs

文字列化された JSON 構造の外部ファイルシステム設定。

--

認証設定には、以下のオプションがあります:

AWS AK/SKを使用

このオプションは、セルフホスト型の 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 を使用

このオプションは、組織が Milvus クラスターと、対象のデータファイルを格納するバケットの管理に異なる AWS アカウントを使用している場合に適用されます。

この場合、バケットの所有者は、以下の条件を満たす IAM ロールを作成する必要があります。

  • AmazonS3FullAccess 、またはバケットへのアクセスに関するよりきめ細かいポリシーをアタッチします。

  • ロールのトラストポリシーの [Condition] フィールドに、独自に定義した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.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: コレクションの作成

スキーマにすべてのフィールドを追加したら、外部コレクションを作成できます。

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: インデックスの作成

マネージドコレクションの場合と同様に、外部コレクションのフィールドに対してインデックスを作成できます。

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: データの更新

コレクションの準備が整ったら、データを更新して、データ用のメタデータとインデックスを作成します。

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を最新の状態に保つために、手動でリフレッシュを再度呼び出す必要があります。

  • 挿入を行わずにすべてのアクティブなメタデータを削除する必要があるリフレッシュは、拒否されます。

次の手順

外部コレクションのリフレッシュが完了したら、コレクションのロードおよびリリースを行い、オンデマンドコンピューティング用のデータベース内のコレクションは検索やクエリを行うためにオンデマンドクラスタにアタッチする必要がある点を除き、管理対象コレクションと同様に、外部コレクションで類似性検索やクエリを実行できます。

検索、クエリ、取得、ハイブリッド検索などの DQL 操作を実行する前に、オンデマンドクラスタのコンピューティングリソースをアタッチするためのセッションを作成する必要があります。

後で、外部データソースに Milvus で公開したい別のフィールドが含まれるようになった場合は、外部コレクションのスキーマにフィールドを追加し、外部コレクションを再度更新してください。詳細については、「外部コレクションのスキーマの変更」を参照してください。