Добавление полей в существующую коллекциюCompatible with Milvus 2.6.x

Milvus позволяет динамически добавлять новые поля в существующие коллекции, что упрощает развитие схемы данных по мере изменения потребностей приложения. В этом руководстве на практических примерах показано, как добавлять поля в различных сценариях.

Соображения

Прежде чем добавлять поля в коллекцию, обратите внимание на следующие важные моменты:

  • Вы можете добавлять скалярные поля (INT64, VARCHAR, FLOAT, DOUBLE и т. д.). Векторные поля нельзя добавлять в существующие коллекции.

  • Новые поля должны быть nullable (nullable=True), чтобы учесть существующие сущности, у которых нет значений для нового поля.

  • Добавление полей в загруженные коллекции увеличивает использование памяти.

  • Существует максимальное ограничение на общее количество полей в коллекции. Подробности см. в разделе "Лимиты Milvus".

  • Имена полей должны быть уникальными среди статических полей.

  • Нельзя добавить поле $meta, чтобы включить функцию динамических полей для коллекций, которые изначально не были созданы с помощью enable_dynamic_field=True.

Предварительные условия

Это руководство предполагает, что у вас есть:

  • работающий экземпляр Milvus

  • установлен Milvus SDK

  • Существующая коллекция

Обратитесь к разделу "Создание коллекции", чтобы узнать о создании коллекции и основных операциях.

Базовое использование

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"

Сценарий 1: Быстрое добавление нулевых полей

Самый простой способ расширить коллекцию - это добавить в нее нулевые поля. Это идеальный вариант, когда вам нужно быстро добавить новые атрибуты к вашим данным.

# 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 "Authorization: Bearer <token>" \
  -d '{
    "collectionName": "product_catalog",
    "schema": {
      "fieldName": "created_timestamp",
      "dataType": "Int64",
      "nullable": true
    }
  }'

Ожидаемое поведение:

  • Существующие сущности будут иметь NULL для нового поля.

  • Новые сущности могут иметь либо NULL, либо фактические значения

  • Доступность полей происходит практически сразу с минимальной задержкой из-за внутренней синхронизации схемы

  • Возможность запросов сразу после короткого периода синхронизации

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

Сценарий 2: Добавление полей со значениями по умолчанию

Если вы хотите, чтобы существующие сущности имели значимое начальное значение вместо NULL, укажите значения по умолчанию.

# 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 "Authorization: Bearer <token>" \
  -d '{
    "collectionName": "product_catalog",
    "schema": {
      "fieldName": "priority_level",
      "dataType": "VarChar",
      "nullable": true,
      "defaultValue": "standard",
      "elementTypeParams": {
        "max_length": "20"
      }
    }
  }'

Ожидаемое поведение:

  • Существующие сущности будут иметь значение по умолчанию ("standard") для вновь добавленного поля.

  • Новые сущности могут переопределить значение по умолчанию или использовать его, если значение не указано.

  • Доступность поля происходит практически сразу с минимальной задержкой

  • Возможность запросов сразу после короткого периода синхронизации

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

ЧАСТО ЗАДАВАЕМЫЕ ВОПРОСЫ

Могу ли я включить функциональность динамической схемы, добавив поле $meta?

Нет, вы не можете использовать add_collection_field для добавления поля $meta, чтобы включить функциональность динамических полей. Например, приведенный ниже код не будет работать:

# ❌ 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 "Authorization: Bearer <token>" \
  -d '{
    "collectionName": "existing_collection",
    "schema": {
      "fieldName": "$meta",
      "dataType": "JSON",
      "nullable": true
    }
  }'

Чтобы включить функциональность динамической схемы:

  • Новая коллекция: Установите enable_dynamic_field в True при создании коллекции. Подробнее см. в разделе Создание коллекции

  • Существующая коллекция: Установите для свойства уровня коллекции dynamicfield.enabled значение True. Подробнее см. в разделе Изменение коллекции.

Что произойдет, если я добавлю поле с тем же именем, что и ключ динамического поля?

Если в коллекции включено динамическое поле (существует$meta ), можно добавить статические поля с тем же именем, что и ключи существующих динамических полей. Новое статическое поле будет маскировать ключ динамического поля, но исходные динамические данные сохранятся.

Чтобы избежать возможных конфликтов в названиях полей, перед добавлением поля продумайте его название, обратившись к существующим полям и ключам динамических полей.

Пример сценария:

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

Ожидаемое поведение:

  • Существующие сущности будут иметь NULL для нового статического поля. extra_info

  • Новые сущности должны использовать тип данных статического поля (INT64).

  • Оригинальные значения ключей динамических полей сохраняются и доступны через синтаксис $meta.

  • Статическое поле маскирует ключ динамического поля в обычных запросах

Доступ к статическим и динамическим значениям:

# 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 "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 "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 "Authorization: Bearer ${AUTH_TOKEN}" \
  -d "{
    \"collectionName\": \"${COLLECTION_NAME}\",
    \"filter\": \"id == 2\",
    \"outputFields\": [\"extra_info\"]
  }"

Сколько времени требуется для того, чтобы новое поле стало доступным?

Добавленные поля становятся доступными практически сразу, но может быть небольшая задержка из-за внутренней трансляции изменений схемы по кластеру Milvus. Эта синхронизация гарантирует, что все узлы будут знать об обновлении схемы перед обработкой запросов с новым полем.