Crea una raccolta esternaCompatible with Milvus 3.0.x

Una raccolta esterna è un tipo di raccolta dati in Milvus che accede ai dati provenienti da sistemi di archiviazione esterni o tabelle di database, come AWS S3 e Iceberg, senza copiarli in Milvus. Funge da livello di query sui data lake, mantenendo al contempo la compatibilità con le interfacce di query di Milvus.

Questa funzionalità richiede Storage V3. Per le istruzioni di abilitazione e le considerazioni sulla compatibilità, consultare Storage V3.

Panoramica

In una tipica pipeline di dati per l’intelligenza artificiale, gli utenti potrebbero aver già archiviato i propri dati in formato Parquet o in altri formati sul proprio sistema di archiviazione, come AWS S3. Per consentire a Milvus di utilizzare questi dati archiviati esternamente, gli utenti devono solitamente importarli nell’archivio proprio di Milvus utilizzando pipeline ETL (Extract-Transform-Load).

Questo flusso di lavoro "porta i tuoi dati su Milvus" crea dati ridondanti difficili da sincronizzare e aumenta il carico di manutenzione tecnica necessario per garantire la coerenza dei dati.

Bring data to compute workflow Flusso di lavoro "porta i dati nell’ambiente di elaborazione "

Per risolvere questi problemi, Milvus offre raccolte esterne che consentono di accedere ai dati archiviati esternamente direttamente da Milvus senza doversi preoccupare della sincronizzazione dei dati e delle pipeline ETL.

Bring compute to data workflow Flusso di lavoro "Porta l’elaborazione ai dati "

Una volta creata, una raccolta esterna può accedere direttamente ai dati e conservarli nello stesso luogo in cui sono archiviati. In background, Milvus crea file di manifesto per registrare le mappature tra i metadati di Milvus e le righe nei file di dati esterni. Una volta pronti i file di manifesto, è possibile creare indici nella raccolta esterna come si farebbe in qualsiasi raccolta gestita.

Quando i dati cambiano, l’attivazione manuale di un aggiornamento in meno di un secondo aggiorna i metadati, mantenendo Milvus sempre aggiornato.

Passaggio 1: Creare lo schema

Come per la creazione di una collezione gestita, anche prima di creare una collezione esterna è necessario creare uno schema. Tuttavia, lo schema è leggermente diverso da quello di una collezione gestita.

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

Per creare lo schema di una collezione esterna, è necessario specificare l’URI dei dati di origine, il formato dei dati e le impostazioni di autenticazione.

Nome parametro

Descrizione del parametro

Valore di esempio

format

Formato dei file di dati di origine di destinazione.

parquet

snapshot_id

Un ID snapshot della tabella Iceberg valido. Questo parametro si applica solo quando si imposta " format " su " iceberg_table".

473984310232959286

extfs

Impostazioni del file system esterno in una struttura JSON convertita in stringa.

--

Sono disponibili le seguenti opzioni per configurare le impostazioni di autenticazione:

Utilizza AWS AK/SK

Questa opzione si applica a MinIO self-hosted o allo scenario in cui si dispone di AK/SK per lavoro.

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

Nome parametro

Descrizione del parametro

Valore di esempio

extfs.access_key_id

ID chiave di accesso

AKIA...

extfs.access_key_value

Valore della chiave di accesso

u7LH...

extfs.region

ID della regione cloud

us-west-2

extfs.cloud_provider

ID del provider cloud

aws

extfs.use_ssl

Se si utilizza SSL per stabilire le connessioni.

true

extfs.use_virtual_host

Se utilizzare l'hosting virtuale per l'accesso al proprio bucket.

Per ulteriori dettagli, consultare questo articolo.

true

Utilizza AWS IAM

Questa opzione si applica allo scenario in cui Milvus viene eseguito su un'istanza EC2 o su un cluster EKS. In questo caso, non è necessario specificare in modo statico l'AK/SK.

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

Nome del parametro

Descrizione del parametro

Valore di esempio

extfs.use_iam

Se utilizzare AWS IAM.

Per questa opzione, impostare il valore su "true".

true

extfs.iam_endpoint

Un endpoint AWS STS valido.

Per ulteriori dettagli, consultare questo articolo.

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

extfs.region

ID della regione cloud

us-west-2

extfs.cloud_provider

ID del provider cloud

aws

extfs.use_ssl

Se si utilizza SSL per stabilire le connessioni.

true

Utilizza le credenziali globali di Milvus

Questa opzione è applicabile quando si archiviano dati esterni nel bucket di Milvus e le impostazioni globali di MinIO specificate in milvus.yaml possono essere utilizzate direttamente per accedere ai dati.

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

Utilizza ARN del ruolo IAM

Questa opzione è applicabile quando la propria organizzazione utilizza account AWS diversi per gestire il cluster Milvus e il bucket che contiene i file di dati di destinazione.

In questo caso, il proprietario del bucket deve creare un ruolo IAM che

  • associ una politica di accesso al bucket AmazonS3FullAccess o una politica più dettagliata.

  • Includa un’ sts:ExternalId a autodefinita nel campo Condizione della Trust Policy del ruolo.

Successivamente, il proprietario del bucket dovrebbe fornirvi l’ARN del ruolo IAM e l’ID esterno, in modo che possiate richiamare sts:AssumeRole con tali valori per assumere il ruolo IAM.

Di seguito è riportato un esempio di politica di autorizzazione da allegare al ruolo IAM con le autorizzazioni consentite. È possibile modificarla in base alle proprie esigenze.

{
    "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/*"
        }
    ]
}

Inoltre, la politica di fiducia associata al ruolo IAM definisce chi è autorizzato ad assumerlo.

{
  "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"
        }
      }
    }
  ]
}

Una volta ottenuti l’ARN del ruolo IAM e l’ID esterno, è possibile configurare il parametro external_spec come segue:

{
    "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"
    }
}

Nome parametro

Descrizione del parametro

Valore di esempio

extfs.cloud_provider

ID del provider cloud

aws

extfs.region

ID della regione cloud

us-west-2

extfs.use_ssl

Indica se si utilizza SSL per stabilire le connessioni.

true

extfs.use_iam

Se utilizzare AWS IAM.

Per questa opzione, impostare il valore su " "true" ".

true

extfs.role_arn

ARN del ruolo IAM ottenuto dal proprietario del bucket.

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

extfs.external_id

ID esterno ottenuto dal proprietario del bucket.

--

extfs.load_frequency

Intervallo, espresso in secondi, con cui Milvus recupera le credenziali di autenticazione temporanee.

900

Passaggio 2: Aggiungi campi

Una volta pronto lo schema, è possibile aggiungere i campi come segue:

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
}"

Passaggio 3: Creare una raccolta

Dopo aver aggiunto tutti i campi allo schema, è possibile creare la raccolta esterna.

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
}"

Passaggio 4: Creare gli indici

È possibile creare indici per i campi delle collezioni esterne proprio come si fa nelle collezioni gestite.

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
}"

Passaggio 5: Aggiornare i dati

Una volta che la raccolta è pronta, aggiornarla per creare i metadati e gli indici relativi ai dati.

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\\\"}\"
}"

L'operazione di aggiornamento è asincrona, pertanto è necessario impostare un'iterazione per monitorarne lo stato di avanzamento.

  • L'operazione di aggiornamento esegue la scansione dei metadati dei file di dati e genera i file di manifesto di conseguenza. Di solito richiede 150-250 ms.

  • I file di manifesto registrano la mappatura tra i metadati in Milvus e le righe nei file esterni.

  • Se i dati di origine vengono aggiornati, è necessario richiamare manualmente l'aggiornamento per mantenere Milvus aggiornato.

  • Un aggiornamento che richiede la rimozione di tutti i metadati attivi senza alcun inserimento viene rifiutato.

Passi successivi

Una volta aggiornata la raccolta esterna, è possibile caricare e rilasciare la raccolta ed eseguire ricerche di similarità e query nella raccolta esterna come si farebbe in qualsiasi raccolta gestita, con la differenza che le raccolte in un database per l’elaborazione on-demand devono essere collegate a un cluster on-demand per le ricerche e le query.

Prima di eseguire operazioni DQL, quali ricerca, query, get e ricerca ibrida, è necessario creare una sessione per collegare le risorse di calcolo di un cluster on-demand.

Se in seguito l’origine dati esterna dovesse contenere un altro campo che si desidera esporre in Milvus, aggiungere un campo allo schema della raccolta esterna e aggiornare nuovamente la raccolta esterna. Per ulteriori dettagli, consultare Modifica dello schema della raccolta esterna.