建立外部資料集Compatible with Milvus 3.0.x
外部集合是 Milvus 中的資料集合類型之一,可直接存取來自外部儲存系統或資料庫表(例如 AWS S3 和 Iceberg)的資料,而無需將資料複製到 Milvus 中。它作為資料湖上的查詢層運作,同時維持與 Milvus 查詢介面的相容性。
此功能需搭配 Storage V3 使用。有關啟用說明及相容性考量,請參閱Storage V3。
概述
在典型的 AI 資料管線中,使用者可能已將資料以 Parquet 或其他格式儲存於其儲存系統(例如 AWS S3)中。為了讓 Milvus 能使用這些外部儲存的資料,使用者通常需要透過萃取-轉換-載入(ETL)管線,將資料匯入 Milvus 自身的儲存系統中。
這種「將資料帶入 Milvus」的工作流程會產生難以同步的冗餘資料,並增加工程團隊為確保資料一致性所須承擔的維護負擔。
將資料導入運算的工作流程
為解決這些問題,Milvus 提供「外部資料集」功能,讓您能直接從 Milvus 存取外部儲存的資料,無需擔心資料同步與 ETL 管線的問題。
「 將運算帶到資料」工作流程
外部集合建立後,即可直接存取您的資料,並將其保留在您原始儲存的位置。在後台,Milvus 會建立清單檔案,用以記錄 Milvus 元資料與外部資料檔案中各列之間的對應關係。待清單檔案準備就緒後,您便能如同在任何受管集合中一樣,於外部集合中建立索引。
當您的資料發生變更時,只需手動觸發不到一秒的刷新操作,即可更新元資料,確保 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、資料格式以及驗證設定。
參數名稱 |
參數說明 |
範例值 |
|---|---|---|
|
目標來源資料檔案的格式。 |
|
|
有效的 Iceberg 資料表快照 ID。此參數僅在您將 ` |
|
|
以字串化 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"
}
}
參數名稱 |
參數說明 |
範例值 |
|---|---|---|
|
存取金鑰 ID |
|
|
存取金鑰值 |
|
|
雲端區域 ID |
|
|
雲端供應商 ID |
|
|
是否使用 SSL 建立連線。 |
|
|
是否使用虛擬主機來存取您的儲存桶。 詳情請參閱這篇文章。 |
|
使用 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"
}
}
參數名稱 |
參數說明 |
範例值 |
|---|---|---|
|
是否使用 AWS IAM。 若要使用此選項,請將此參數設定為 |
|
|
有效的 AWS STS 端點。 詳情請參閱這篇文章。 |
|
|
雲端區域 ID |
|
|
雲端服務供應商 ID |
|
|
是否使用 SSL 建立連線。 |
|
使用 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"
}
}
參數名稱 |
參數說明 |
範例值 |
|---|---|---|
|
雲端供應商 ID |
|
|
雲端區域 ID |
|
|
是否使用 SSL 建立連線。 |
|
|
是否使用 AWS IAM。 請將此選項設定為 |
|
|
從儲存桶擁有者處取得的 IAM 角色 ARN。 |
|
|
從儲存桶擁有者處取得的外部 ID。 |
-- |
|
Milvus 擷取臨時認證憑證的間隔(單位:秒)。 |
|
步驟 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 中公開的另一個欄位,請在外部集合的資料結構中新增該欄位,並再次刷新外部集合。詳細資訊請參閱《變更外部集合資料結構》。