Insérer des entités

L'opération upsert est un moyen pratique d'insérer ou de mettre à jour des entités dans une collection.

Vue d'ensemble

Vous pouvez utiliser upsert pour insérer une nouvelle entité ou mettre à jour une entité existante, selon que la clé primaire fournie dans la requête upsert existe ou non dans la collection. Si la clé primaire n'est pas trouvée, une opération d'insertion est effectuée. Dans le cas contraire, une opération de mise à jour est effectuée.

Dans Milvus, un upsert fonctionne en mode prioritaire ou en mode fusion.

Insertion en mode prioritaire

Une demande d'insertion qui fonctionne en mode prioritaire combine une insertion et une suppression. Lorsqu'une demande upsert pour une entité existante est reçue, Milvus insère les données contenues dans la charge utile de la demande et supprime l'entité existante avec la clé primaire d'origine spécifiée dans les données en même temps.

Upsert In Override Mode Insertion en mode prioritaire

Si la collection cible a activé autoid sur son champ primaire, Milvus génère une nouvelle clé primaire pour les données transportées dans la charge utile de la demande avant de les insérer.

Pour les champs dont l'option nullable est activée, vous pouvez les omettre dans la requête upsert s'ils ne nécessitent aucune mise à jour.

Insertion en mode fusionCompatible with Milvus v2.6.2+

Vous pouvez également utiliser l'indicateur partial_update pour qu'une demande d'insertion fonctionne en mode fusion. Cela vous permet de n'inclure dans la requête que les champs qui doivent être mis à jour.

Upsert In Merge Mode Upsert en mode fusion

Pour effectuer une fusion, définissez partial_update sur True dans la requête upsert avec la clé primaire et les champs à mettre à jour avec leurs nouvelles valeurs.

Lors de la réception d'une telle demande, Milvus exécute une requête avec une cohérence forte pour récupérer l'entité, met à jour les valeurs des champs en fonction des données de la demande, insère les données modifiées, puis supprime l'entité existante avec la clé primaire d'origine transmise dans la demande.

Comportements d'insertion : remarques particulières

Vous devez tenir compte de plusieurs remarques particulières avant d'utiliser la fonction de fusion. Les cas suivants supposent que vous avez une collection avec deux champs scalaires nommés title et issue, ainsi qu'une clé primaire id et un champ vectoriel appelé vector.

  • Champs Upsert avec nullable activé.

    Supposons que le champ issue puisse être nul. Lorsque vous réinsérez ces champs, notez que :

    • Si vous omettez le champ issue dans la requête upsert et que vous désactivez partial_update, le champ issue sera mis à jour en null au lieu de conserver sa valeur initiale.

    • Pour conserver la valeur originale du champ issue, vous devez soit activer partial_update et omettre le champ issue, soit inclure le champ issue avec sa valeur originale dans la requête upsert.

  • Suppression des clés dans le champ dynamique.

    Supposons que vous ayez activé la clé dynamique dans la collection d'exemples et que les paires clé-valeur du champ dynamique d'une entité soient similaires à {"author": "John", "year": 2020, "tags": ["fiction"]}.

    Lorsque vous insérez l'entité avec des clés telles que author, year, ou tags, ou que vous ajoutez d'autres clés, notez ce qui suit :

    • Si vous effectuez un upsert alors que partial_update est désactivé, le comportement par défaut est de passer outre. Cela signifie que la valeur du champ dynamique sera remplacée par tous les champs non définis par le schéma inclus dans la demande et par leurs valeurs.

      Par exemple, si les données incluses dans la requête sont {"author": "Jane", "genre": "fantasy"}, les paires clé-valeur du champ dynamique de l'entité cible seront mises à jour en conséquence.

    • Si vous effectuez un upsert avec l'option partial_update, le comportement par défaut est la fusion. Cela signifie que la valeur du champ dynamique sera fusionnée avec tous les champs non définis par le schéma inclus dans la demande et leurs valeurs.

      Par exemple, si les données incluses dans la demande sont {"author": "John", "year": 2020, "tags": ["fiction"]}, les paires clé-valeur du champ dynamique de l'entité cible deviendront {"author": "John", "year": 2020, "tags": ["fiction"], "genre": "fantasy"} après l'insertion.

  • Insertion d'un champ JSON.

    Supposons que la collection d'exemples possède un champ JSON défini par le schéma nommé extras, et que les paires clé-valeur de ce champ JSON d'une entité sont similaires à {"author": "John", "year": 2020, "tags": ["fiction"]}.

    Lorsque vous réinsérez le champ extras d'une entité avec des données JSON modifiées, notez que le champ JSON est traité comme un tout et que vous ne pouvez pas mettre à jour des clés individuelles de manière sélective. En d'autres termes, le champ JSON ne supporte pas l'upsert en mode fusion.

Limites et restrictions

Sur la base du contenu ci-dessus, il existe plusieurs limites et restrictions à respecter :

  • La requête upsert doit toujours inclure les clés primaires des entités cibles.

  • La collection cible doit être chargée et disponible pour les requêtes.

  • Tous les champs spécifiés dans la demande doivent exister dans le schéma de la collection cible.

  • Les valeurs de tous les champs spécifiés dans la requête doivent correspondre aux types de données définis dans le schéma.

  • Pour tout champ dérivé d'un autre à l'aide de fonctions, Milvus supprime le champ dérivé pendant la conversion pour permettre un nouveau calcul.

Suppression d'entités dans une collection

Dans cette section, nous allons extraire des entités dans une collection nommée my_collection. Cette collection ne comporte que deux champs, nommés id, vector, title et issue. Le champ id est le champ primaire, tandis que les champs title et issue sont des champs scalaires.

Les trois entités, si elles existent dans la collection, seront remplacées par celles incluses dans la demande d'insertion.

from pymilvus import MilvusClient

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

data=[
    {
        "id": 0, 
        "vector": [-0.619954382375778, 0.4479436794798608, -0.17493894838751745, -0.4248030059917294, -0.8648452746018911],
        "title": "Artificial Intelligence in Real Life", 
        "issue": "vol.12"
    }, {
        "id": 1, 
        "vector": [0.4762662251462588, -0.6942502138717026, -0.4490002642657902, -0.628696575798281, 0.9660395877041965], 
        "title": "Hollow Man", 
        "issue": "vol.19"
    }, {
        "id": 2, 
        "vector": [-0.8864122635045097, 0.9260170474445351, 0.801326976181461, 0.6383943392381306, 0.7563037341572827], 
        "title": "Treasure Hunt in Missouri", 
        "issue": "vol.12"
    }
]

res = client.upsert(
    collection_name='my_collection',
    data=data
)

print(res)

# Output
# {'upsert_count': 3}
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.service.vector.request.UpsertReq;
import io.milvus.v2.service.vector.response.UpsertResp;

import java.util.*;

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

Gson gson = new Gson();
List<JsonObject> data = Arrays.asList(
        gson.fromJson("{\"id\": 0, \"vector\": [-0.619954382375778, 0.4479436794798608, -0.17493894838751745, -0.4248030059917294, -0.8648452746018911], \"title\": \"Artificial Intelligence in Real Life\", \"issue\": \"\vol.12\"}", JsonObject.class),
        gson.fromJson("{\"id\": 1, \"vector\": [0.4762662251462588, -0.6942502138717026, -0.4490002642657902, -0.628696575798281, 0.9660395877041965], \"title\": \"Hollow Man\", \"issue\": \"vol.19\"}", JsonObject.class),
        gson.fromJson("{\"id\": 2, \"vector\": [-0.8864122635045097, 0.9260170474445351, 0.801326976181461, 0.6383943392381306, 0.7563037341572827], \"title\": \"Treasure Hunt in Missouri\", \"issue\": \"vol.12\"}", JsonObject.class),
);

UpsertReq upsertReq = UpsertReq.builder()
        .collectionName("my_collection")
        .data(data)
        .build();

UpsertResp upsertResp = client.upsert(upsertReq);
System.out.println(upsertResp);

// Output:
//
// UpsertResp(upsertCnt=3)
const { MilvusClient, DataType } = require("@zilliz/milvus2-sdk-node")

const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});

data = [
    {id: 0, vector: [-0.619954382375778, 0.4479436794798608, -0.17493894838751745, -0.4248030059917294, -0.8648452746018911], title: "Artificial Intelligence in Real Life", issue: "vol.12"},
    {id: 1, vector: [0.4762662251462588, -0.6942502138717026, -0.4490002642657902, -0.628696575798281, 0.9660395877041965], title: "Hollow Man", issue: "vol.19"},
    {id: 2, vector: [-0.8864122635045097, 0.9260170474445351, 0.801326976181461, 0.6383943392381306, 0.7563037341572827], title: "Treasure Hunt in Missouri", issue: "vol.12"},
]

res = await client.upsert({
    collection_name: "my_collection",
    data: data,
})

console.log(res.upsert_cnt)

// Output
// 
// 3
// 
import (
    "context"
    "fmt"

    "github.com/milvus-io/milvus/client/v2/column"
    "github.com/milvus-io/milvus/client/v2/milvusclient"
)

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

milvusAddr := "localhost:19530"
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
    Address: milvusAddr,
})
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
defer client.Close(ctx)

titleColumn := column.NewColumnString("title", []string{
    "Artificial Intelligence in Real Life", "Hollow Man", "Treasure Hunt in Missouri", 
})

issueColumn := column.NewColumnString("issue", []string{
    "vol.12", "vol.19", "vol.12"
})

_, err = client.Upsert(ctx, milvusclient.NewColumnBasedInsertOption("my_collection").
    WithInt64Column("id", []int64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}).
    WithFloatVectorColumn("vector", 5, [][]float32{
        {0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592},
        {0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104},
        {0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592},
    }).
    WithColumns(titleColumn, issueColumn),
)
if err != nil {
    fmt.Println(err.Error())
    // handle err
}
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/upsert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
    "data": [
        {"id": 0, "vector": [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592], "title": "Artificial Intelligence in Real Life", "issue": "vol.12"},
        {"id": 1, "vector": [0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104], "title": "Hollow Man", "issue": "vol.19"},
        {"id": 2, "vector": [0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592], "title": "Treasure Hunt in Missouri", "issue": "vol.12"},
],
    "collectionName": "my_collection"
}'

# {
#     "code": 0,
#     "data": {
#         "upsertCount": 3,
#         "upsertIds": [
#             0,
#             1,
#             2,
#         ]
#     }
# }

Insertion d'entités dans une partition

Vous pouvez également insérer des entités dans une partition spécifiée. Les extraits de code suivants supposent que vous avez une partition nommée PartitionA dans votre collection.

Les trois entités, si elles existent dans la partition, seront remplacées par celles incluses dans la requête.

data=[
    {
        "id": 10, 
        "vector": [0.06998888224297328, 0.8582816610326578, -0.9657938677934292, 0.6527905683627726, -0.8668460657158576], 
        "title": "Layour Design Reference", 
        "issue": "vol.34"
    },
    {
        "id": 11, 
        "vector": [0.6060703043917468, -0.3765080534566074, -0.7710758854987239, 0.36993888322346136, 0.5507513364206531], 
        "title": "Doraemon and His Friends", 
        "issue": "vol.2"
    },
    {
        "id": 12, 
        "vector": [-0.9041813104515337, -0.9610546012461163, 0.20033003106083358, 0.11842506351635174, 0.8327356724591011], 
        "title": "Pikkachu and Pokemon", 
        "issue": "vol.12"
    },
]

res = client.upsert(
    collection_name="my_collection",
    data=data,
    partition_name="partitionA"
)

print(res)

# Output
# {'upsert_count': 3}
import io.milvus.v2.service.vector.request.UpsertReq;
import io.milvus.v2.service.vector.response.UpsertResp;

Gson gson = new Gson();
List<JsonObject> data = Arrays.asList(
        gson.fromJson("{\"id\": 10, \"vector\": [0.06998888224297328, 0.8582816610326578, -0.9657938677934292, 0.6527905683627726, -0.8668460657158576], \"title\": \"Layour Design Reference\", \"issue\": \"vol.34\"}", JsonObject.class),
        gson.fromJson("{\"id\": 11, \"vector\": [0.6060703043917468, -0.3765080534566074, -0.7710758854987239, 0.36993888322346136, 0.5507513364206531], \"title\": \"Doraemon and His Friends\", \"issue\": \"vol.2\"}", JsonObject.class),
        gson.fromJson("{\"id\": 12, \"vector\": [-0.9041813104515337, -0.9610546012461163, 0.20033003106083358, 0.11842506351635174, 0.8327356724591011], \"title\": \"Pikkachu and Pokemon\", \"issue\": \"vol.12\"}", JsonObject.class),
);

UpsertReq upsertReq = UpsertReq.builder()
        .collectionName("my_collection")
        .partitionName("partitionA")
        .data(data)
        .build();

UpsertResp upsertResp = client.upsert(upsertReq);
System.out.println(upsertResp);

// Output:
//
// UpsertResp(upsertCnt=3)
const { MilvusClient, DataType } = require("@zilliz/milvus2-sdk-node")

// 6. Upsert data in partitions
data = [
    {id: 10, vector: [0.06998888224297328, 0.8582816610326578, -0.9657938677934292, 0.6527905683627726, -0.8668460657158576], title: "Layour Design Reference", issue: "vol.34"},
    {id: 11, vector: [0.6060703043917468, -0.3765080534566074, -0.7710758854987239, 0.36993888322346136, 0.5507513364206531], title: "Doraemon and His Friends", issue: "vol.2"},
    {id: 12, vector: [-0.9041813104515337, -0.9610546012461163, 0.20033003106083358, 0.11842506351635174, 0.8327356724591011], title: "Pikkachu and Pokemon", issue: "vol.12"},
]

res = await client.upsert({
    collection_name: "my_collection",
    data: data,
    partition_name: "partitionA"
})

console.log(res.upsert_cnt)

// Output
// 
// 3
// 
titleColumn = column.NewColumnString("title", []string{
    "Layour Design Reference", "Doraemon and His Friends", "Pikkachu and Pokemon", 
})
issueColumn = column.NewColumnString("issue", []string{
    "vol.34", "vol.2", "vol.12", 
})

_, err = client.Upsert(ctx, milvusclient.NewColumnBasedInsertOption("my_collection").
    WithPartition("partitionA").
    WithInt64Column("id", []int64{10, 11, 12, 13, 14, 15, 16, 17, 18, 19}).
    WithFloatVectorColumn("vector", 5, [][]float32{
        {0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592},
        {0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104},
        {0.43742130801983836, -0.5597502546264526, 0.6457887650909682, 0.7894058910881185, 0.20785793220625592},
    }).
    WithColumns(titleColumn, issueColumn),
)
if err != nil {
    fmt.Println(err.Error())
    // handle err
}
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/upsert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
    "data": [
        {"id": 10, "vector": [0.06998888224297328, 0.8582816610326578, -0.9657938677934292, 0.6527905683627726, -0.8668460657158576], "title": "Layour Design Reference", "issue": "vol.34"},
        {"id": 11, "vector": [0.6060703043917468, -0.3765080534566074, -0.7710758854987239, 0.36993888322346136, 0.5507513364206531], "title": "Doraemon and His Friends", "issue": "vol.2"},
        {"id": 12, "vector": [-0.9041813104515337, -0.9610546012461163, 0.20033003106083358, 0.11842506351635174, 0.8327356724591011], "title": "Pikkachu and Pokemon", "issue": "vol.12"},
    ],
    "collectionName": "my_collection",
    "partitionName": "partitionA"
}'

# {
#     "code": 0,
#     "data": {
#         "upsertCount": 3,
#         "upsertIds": [
#             10,
#             11,
#             12,
#         ]
#     }
# }

Suppression d'entités en mode fusionCompatible with Milvus v2.6.2+

L'exemple de code suivant montre comment effectuer un upsert d'entités avec des mises à jour partielles. Fournissez uniquement les champs nécessitant des mises à jour et leurs nouvelles valeurs, ainsi que le drapeau explicite de mise à jour partielle.

Dans l'exemple suivant, le champ issue des entités spécifiées dans la requête d'upsert sera mis à jour avec les valeurs incluses dans la requête.

Lorsque vous effectuez une upsert en mode fusion, assurez-vous que les entités concernées par la requête ont le même ensemble de champs. Supposons qu'il y ait deux entités ou plus à upster, comme le montre l'extrait de code suivant, il est important qu'elles incluent des champs identiques pour éviter les erreurs et maintenir l'intégrité des données.

data=[
    {
        "id": 1,
        "issue": "vol.14"
    },
    {
        "id": 2, 
        "issue": "vol.7"
    }
]

res = client.upsert(
    collection_name="my_collection",
    data=data,
    partial_update=True
)

print(res)

# Output
# {'upsert_count': 2}
JsonObject row1 = new JsonObject();
row1.addProperty("id", 1);
row1.addProperty("issue", "vol.14");

JsonObject row2 = new JsonObject();
row2.addProperty("id", 2);
row2.addProperty("issue", "vol.7");

UpsertReq upsertReq = UpsertReq.builder()
        .collectionName("my_collection")
        .data(Arrays.asList(row1, row2))
        .partialUpdate(true)
        .build();

UpsertResp upsertResp = client.upsert(upsertReq);
System.out.println(upsertResp);

// Output:
//
// UpsertResp(upsertCnt=2)
pkColumn := column.NewColumnInt64("id", []int64{1, 2})
issueColumn = column.NewColumnString("issue", []string{
    "vol.17", "vol.7",
})

_, err = client.Upsert(ctx, milvusclient.NewColumnBasedInsertOption("my_collection").
    WithColumns(pkColumn, issueColumn).
    WithPartialUpdate(true),
)
if err != nil {
    fmt.Println(err.Error())
    // handle err
}
const data=[
    {
        "id": 1,
        "issue": "vol.14"
    },
    {
        "id": 2, 
        "issue": "vol.7"
    }
];

const res = await client.upsert({
    collection_name: "my_collection",
    data,
    partial_update: true
});

console.log(res)

// Output
// 
// 2
// 
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

export COLLECTION_NAME="my_collection"
export UPSERT_DATA='[
  {
    "id": 1,
    "issue": "vol.14"
  },
  {
    "id": 2,
    "issue": "vol.7"
  }
]'

curl -X POST "http://localhost:19530/v2/vectordb/entities/upsert" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer ${TOKEN}" \
  -d "{
    \"collectionName\": \"${COLLECTION_NAME}\",
    \"data\": ${UPSERT_DATA},
    \"partialUpdate\": true
  }"

# {
#     "code": 0,
#     "data": {
#         "upsertCount": 2,
#         "upsertIds": [
#              3,
#             12,
#         ]
#     }
# }