Ajouter des champs à une collection existanteCompatible with Milvus 2.6.x

Milvus vous permet d'ajouter dynamiquement de nouveaux champs à des collections existantes, ce qui facilite l'évolution de votre schéma de données au fur et à mesure que les besoins de votre application changent. Ce guide vous montre comment ajouter des champs dans différents scénarios à l'aide d'exemples pratiques.

Points à prendre en compte

Avant d'ajouter des champs à votre collection, gardez ces points importants à l'esprit :

  • Vous pouvez ajouter des champs scalaires (INT64, VARCHAR, FLOAT, DOUBLE, etc.). Les champs vectoriels ne peuvent pas être ajoutés à des collections existantes.

  • Les nouveaux champs doivent être nullables (nullable=True) pour tenir compte des entités existantes qui n'ont pas de valeurs pour le nouveau champ.

  • L'ajout de champs aux collections chargées augmente l'utilisation de la mémoire.

  • Le nombre total de champs par collection est limité. Pour plus d'informations, reportez-vous à la section Limites de Milvus.

  • Les noms de champ doivent être uniques parmi les champs statiques.

  • Vous ne pouvez pas ajouter un champ $meta pour activer la fonctionnalité de champ dynamique pour les collections qui n'ont pas été créées à l'origine avec enable_dynamic_field=True.

Conditions préalables

Ce guide suppose que vous disposez

  • Une instance Milvus en cours d'exécution

  • Le SDK Milvus est installé

  • Une collection existante

Reportez-vous à la section Créer une collection pour la création d'une collection et les opérations de base.

Utilisation de base

from pymilvus import MilvusClient, DataType

# Connect to your Milvus server
client = MilvusClient(
    uri="http://localhost:19530"  # Replace with your Milvus server URI
)
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.client.ConnectConfig;

ConnectConfig config = ConnectConfig.builder()
        .uri("http://localhost:19530")
        .build();
MilvusClientV2 client = new MilvusClientV2(config);
import { MilvusClient } from '@zilliz/milvus2-sdk-node';

const milvusClient = new MilvusClient({
    address: 'localhost:19530'
});
// go
# restful
export CLUSTER_ENDPOINT="localhost:19530"

Scénario 1 : Ajouter rapidement des champs nullables

La manière la plus simple d'étendre votre collection est d'ajouter des champs nullables. C'est parfait lorsque vous avez besoin d'ajouter rapidement de nouveaux attributs à vos données.

# Add a nullable field to an existing collection
# This operation:
# - Returns almost immediately (non-blocking)
# - Makes the field available for use with minimal delay
# - Sets NULL for all existing entities
client.add_collection_field(
    collection_name="product_catalog",
    field_name="created_timestamp",  # Name of the new field to add
    data_type=DataType.INT64,        # Data type must be a scalar type
    nullable=True                    # Must be True for added fields
    # Allows NULL values for existing entities
)
import io.milvus.v2.service.collection.request.AddCollectionFieldReq;

client.addCollectionField(AddCollectionFieldReq.builder()
        .collectionName("product_catalog")
        .fieldName("created_timestamp")
        .dataType(DataType.Int64)
        .isNullable(true)
        .build());
await client.addCollectionField({
    collection_name: 'product_catalog',
    field: {
        name: 'created_timestamp',
        dataType: 'Int64',
        nullable: true
     }
});
// go
# restful
curl -X POST "http://localhost:19530/v2/vectordb/collections/fields/add" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "collectionName": "product_catalog",
    "schema": {
      "fieldName": "created_timestamp",
      "dataType": "Int64",
      "nullable": true
    }
  }'

Comportement attendu :

  • Lesentités existantes auront NULL pour le nouveau champ.

  • Les nouvelles entités peuvent avoir soit NULL, soit des valeurs réelles.

  • Ladisponibilité des champs est quasi immédiate, avec un délai minimal dû à la synchronisation interne du schéma.

  • Interrogeable immédiatement après la brève période de synchronisation

# Example query result
{
    'id': 1, 
    'created_timestamp': None  # New field shows NULL for existing entities
}
// java
// nodejs
{
    'id': 1, 
    'created_timestamp': None  # New field shows NULL for existing entities
}
// go
# restful
{
  "code": 0,
  "data": {},
  "cost": 0
}

Scénario 2 : ajouter des champs avec des valeurs par défaut

Lorsque vous souhaitez que les entités existantes aient une valeur initiale significative au lieu de NULL, spécifiez des valeurs par défaut.

# Add a field with default value
# This operation:
# - Sets the default value for all existing entities
# - Makes the field available with minimal delay
# - Maintains data consistency with the default value
client.add_collection_field(
    collection_name="product_catalog",
    field_name="priority_level",     # Name of the new field
    data_type=DataType.VARCHAR,      # String type field
    max_length=20,                   # Maximum string length
    nullable=True,                   # Required for added fields
    default_value="standard"         # Value assigned to existing entities
    # Also used for new entities if no value provided
)
client.addCollectionField(AddCollectionFieldReq.builder()
        .collectionName("product_catalog")
        .fieldName("priority_level")
        .dataType(DataType.VarChar)
        .maxLength(20)
        .isNullable(true)
        .build());
await client.addCollectionField({
    collection_name: 'product_catalog',
    field: {
        name: 'priority_level',
        dataType: 'VarChar',
        nullable: true,
        default_value: 'standard',
     }
});
// go
# restful
curl -X POST "http://localhost:19530/v2/vectordb/collections/fields/add" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "collectionName": "product_catalog",
    "schema": {
      "fieldName": "priority_level",
      "dataType": "VarChar",
      "nullable": true,
      "defaultValue": "standard",
      "elementTypeParams": {
        "max_length": "20"
      }
    }
  }'

Comportement attendu :

  • Les entités existantes auront la valeur par défaut ("standard") pour le champ nouvellement ajouté.

  • Les nouvelles entités peuvent remplacer la valeur par défaut ou l'utiliser si aucune valeur n'est fournie.

  • Ladisponibilité des champs est quasi immédiate, avec un délai minimal.

  • Interrogeable immédiatement après la brève période de synchronisation

# Example query result
{
    'id': 1,
    'priority_level': 'standard'  # Shows default value for existing entities
}
// java
{
    'id': 1,
    'priority_level': 'standard'  # Shows default value for existing entities
}
// go
# restful
{
    'id': 1,
    'priority_level': 'standard'  # Shows default value for existing entities
}

FAQ

Puis-je activer la fonctionnalité de schéma dynamique en ajoutant un champ $meta?

Non, vous ne pouvez pas utiliser add_collection_field pour ajouter un champ $meta afin d'activer la fonctionnalité de champ dynamique. Par exemple, le code ci-dessous ne fonctionnera pas :

# ❌ This is NOT supported
client.add_collection_field(
    collection_name="existing_collection",
    field_name="$meta",
    data_type=DataType.JSON  # This operation will fail
)
// ❌ This is NOT supported
client.addCollectionField(AddCollectionFieldReq.builder()
        .collectionName("existing_collection")
        .fieldName("$meta")
        .dataType(DataType.JSON)
        .build());
// ❌ This is NOT supported
await client.addCollectionField({
    collection_name: 'product_catalog',
    field: {
        name: '$meta',
        dataType: 'JSON',
     }
});
// go
# restful
# ❌ This is NOT supported
curl -X POST "http://localhost:19530/v2/vectordb/collections/fields/add" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer <token>" \
  -d '{
    "collectionName": "existing_collection",
    "schema": {
      "fieldName": "$meta",
      "dataType": "JSON",
      "nullable": true
    }
  }'

Pour activer la fonctionnalité de schéma dynamique :

  • Nouvelle collection: Attribuez la valeur True à enable_dynamic_field lors de la création de la collection. Pour plus d'informations, reportez-vous à la section Créer une collection.

  • Collection existante: Attribuez la valeur True à la propriété de niveau collection dynamicfield.enabled. Pour plus d'informations, reportez-vous à la section Modifier une collection.

Que se passe-t-il lorsque j'ajoute un champ portant le même nom qu'une clé de champ dynamique ?

Lorsque les champs dynamiques sont activés dans votre collection ($meta existe), vous pouvez ajouter des champs statiques portant le même nom que les clés de champ dynamique existantes. Le nouveau champ statique masquera la clé du champ dynamique, mais les données dynamiques originales seront conservées.

Pour éviter d'éventuels conflits de noms de champs, réfléchissez au nom du champ à ajouter en vous référant aux champs existants et aux clés de champs dynamiques avant de l'ajouter.

Exemple de scénario :

# Original collection with dynamic field enabled
# Insert data with dynamic field keys
data = [{
    "id": 1,
    "my_vector": [0.1, 0.2, ...],
    "extra_info": "this is a dynamic field key",  # Dynamic field key as string
    "score": 99.5                                 # Another dynamic field key
}]
client.insert(collection_name="product_catalog", data=data)

# Add static field with same name as existing dynamic field key
client.add_collection_field(
    collection_name="product_catalog",
    field_name="extra_info",         # Same name as dynamic field key
    data_type=DataType.INT64,        # Data type can differ from dynamic field key
    nullable=True                    # Must be True for added fields
)

# Insert new data after adding static field
new_data = [{
    "id": 2,
    "my_vector": [0.3, 0.4, ...],
    "extra_info": 100,               # Now must use INT64 type (static field)
    "score": 88.0                    # Still a dynamic field key
}]
client.insert(collection_name="product_catalog", data=new_data)
import com.google.gson.*;
import io.milvus.v2.service.vector.request.InsertReq;
import io.milvus.v2.service.vector.response.InsertResp;

Gson gson = new Gson();
JsonObject row = new JsonObject();
row.addProperty("id", 1);
row.add("my_vector", gson.toJsonTree(new float[]{0.1f, 0.2f, ...}));
row.addProperty("extra_info", "this is a dynamic field key");
row.addProperty("score", 99.5);

InsertResp insertR = client.insert(InsertReq.builder()
        .collectionName("product_catalog")
        .data(Collections.singletonList(row))
        .build());
        
client.addCollectionField(AddCollectionFieldReq.builder()
        .collectionName("product_catalog")
        .fieldName("extra_info")
        .dataType(DataType.Int64)
        .isNullable(true)
        .build());
        
JsonObject newRow = new JsonObject();
newRow.addProperty("id", 2);
newRow.add("my_vector", gson.toJsonTree(new float[]{0.3f, 0.4f, ...}));
newRow.addProperty("extra_info", 100);
newRow.addProperty("score", 88.0);

insertR = client.insert(InsertReq.builder()
        .collectionName("product_catalog")
        .data(Collections.singletonList(newRow))
        .build());
// Original collection with dynamic field enabled
// Insert data with dynamic field keys
const data = [{
    "id": 1,
    "my_vector": [0.1, 0.2, ...],
    "extra_info": "this is a dynamic field key",  // Dynamic field key as string
    "score": 99.5                                 // Another dynamic field key
}]
await client.insert({
    collection_name: "product_catalog", 
    data: data
});

// Add static field with same name as existing dynamic field key
await client.add_collection_field({
    collection_name: "product_catalog",
    field_name: "extra_info",         // Same name as dynamic field key
    data_type: DataType.INT64,        // Data type can differ from dynamic field key
    nullable: true                   // Must be True for added fields
});

// Insert new data after adding static field
const new_data = [{
    "id": 2,
    "my_vector": [0.3, 0.4, ...],
    "extra_info": 100,               # Now must use INT64 type (static field)
    "score": 88.0                    # Still a dynamic field key
}];

await client.insert({
    collection_name:"product_catalog", 
    data: new_data
});
// go
# restful
#!/bin/bash

export MILVUS_HOST="localhost:19530"
export AUTH_TOKEN="your_token_here"
export COLLECTION_NAME="product_catalog"

echo "Step 1: Insert initial data with dynamic fields..."
curl -X POST "http://${MILVUS_HOST}/v2/vectordb/entities/insert" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -d "{
    \"collectionName\": \"${COLLECTION_NAME}\",
    \"data\": [{
      \"id\": 1,
      \"my_vector\": [0.1, 0.2, 0.3, 0.4, 0.5],
      \"extra_info\": \"this is a dynamic field key\",
      \"score\": 99.5
    }]
  }"

echo -e "\n\nStep 2: Add static field with same name as dynamic field..."
curl -X POST "http://${MILVUS_HOST}/v2/vectordb/collections/fields/add" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -d "{
    \"collectionName\": \"${COLLECTION_NAME}\",
    \"schema\": {
      \"fieldName\": \"extra_info\",
      \"dataType\": \"Int64\",
      \"nullable\": true
    }
  }"

echo -e "\n\nStep 3: Insert new data after adding static field..."
curl -X POST "http://${MILVUS_HOST}/v2/vectordb/entities/insert" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -d "{
    \"collectionName\": \"${COLLECTION_NAME}\",
    \"data\": [{
      \"id\": 2,
      \"my_vector\": [0.3, 0.4, 0.5, 0.6, 0.7],
      \"extra_info\": 100,
      \"score\": 88.0
    }]
  }"

Comportement attendu :

  • Les entités existantes auront NULL pour le nouveau champ statique. extra_info

  • Les nouvelles entités doivent utiliser le type de données du champ statique (INT64).

  • Lesvaleurs originales de la clé du champ dynamique sont préservées et accessibles via la syntaxe $meta

  • Le champ statique masque la clé du champ dynamique dans les requêtes normales.

Accès aux valeurs statiques et dynamiques :

# 1. Query static field only (dynamic field key is masked)
results = client.query(
    collection_name="product_catalog",
    filter="id == 1",
    output_fields=["extra_info"]
)
# Returns: {"id": 1, "extra_info": None}  # NULL for existing entity

# 2. Query both static and original dynamic values
results = client.query(
    collection_name="product_catalog", 
    filter="id == 1",
    output_fields=["extra_info", "$meta['extra_info']"]
)
# Returns: {
#     "id": 1,
#     "extra_info": None,                           # Static field value (NULL)
#     "$meta['extra_info']": "this is a dynamic field key"  # Original dynamic value
# }

# 3. Query new entity with static field value
results = client.query(
    collection_name="product_catalog",
    filter="id == 2", 
    output_fields=["extra_info"]
)
# Returns: {"id": 2, "extra_info": 100}  # Static field value
// java
// 1. Query static field only (dynamic field key is masked)
let results = client.query({
    collection_name: "product_catalog",
    filter: "id == 1",
    output_fields: ["extra_info"]
})
// Returns: {"id": 1, "extra_info": None}  # NULL for existing entity

// 2. Query both static and original dynamic values
results = client.query({
    collection_name:"product_catalog", 
    filter: "id == 1",
    output_fields: ["extra_info", "$meta['extra_info']"]
});
// Returns: {
//     "id": 1,
//     "extra_info": None,                           # Static field value (NULL)
//     "$meta['extra_info']": "this is a dynamic field key"  # Original dynamic value
// }

// 3. Query new entity with static field value
results = client.query({
    collection_name: "product_catalog",
    filter: "id == 2", 
    output_fields: ["extra_info"]
})
// Returns: {"id": 2, "extra_info": 100}  # Static field value
// go
# restful
#!/bin/bash

export MILVUS_HOST="localhost:19530"
export AUTH_TOKEN="your_token_here"
export COLLECTION_NAME="product_catalog"

echo "Query 1: Static field only (dynamic field masked)..."
curl -X POST "http://${MILVUS_HOST}/v2/vectordb/entities/query" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -d "{
    \"collectionName\": \"${COLLECTION_NAME}\",
    \"filter\": \"id == 1\",
    \"outputFields\": [\"extra_info\"]
  }"

echo -e "\n\nQuery 2: Both static and original dynamic values..."
curl -X POST "http://${MILVUS_HOST}/v2/vectordb/entities/query" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -d "{
    \"collectionName\": \"${COLLECTION_NAME}\",
    \"filter\": \"id == 1\",
    \"outputFields\": [\"extra_info\", \"\$meta['extra_info']\"]
  }"

echo -e "\n\nQuery 3: New entity with static field value..."
curl -X POST "http://${MILVUS_HOST}/v2/vectordb/entities/query" \
  -H "Content-Type: application/json" \
  -H "Request-Timeout: 10" \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -d "{
    \"collectionName\": \"${COLLECTION_NAME}\",
    \"filter\": \"id == 2\",
    \"outputFields\": [\"extra_info\"]
  }"

Combien de temps faut-il pour qu'un nouveau champ soit disponible ?

Les champs ajoutés sont disponibles presque immédiatement, mais il peut y avoir un bref délai dû à la diffusion interne des modifications de schéma dans le cluster Milvus. Cette synchronisation garantit que tous les nœuds sont au courant de la mise à jour du schéma avant de traiter les requêtes impliquant le nouveau champ.