기존 컬렉션에 필드 추가Compatible with Milvus 2.6.x
Milvus를 사용하면 기존 컬렉션에 새 필드를 동적으로 추가할 수 있으므로 애플리케이션 요구 사항의 변화에 따라 데이터 스키마를 쉽게 발전시킬 수 있습니다. 이 가이드는 실제 예제를 사용하여 다양한 시나리오에서 필드를 추가하는 방법을 보여줍니다.
고려 사항
컬렉션에 필드를 추가하기 전에 다음과 같은 중요한 사항을 염두에 두세요:
스칼라 필드(
INT64,VARCHAR,FLOAT,DOUBLE등)를 추가할 수 있습니다. 벡터 필드는 기존 컬렉션에 추가할 수 없습니다.새 필드에 대한 값이 없는 기존 엔티티를 수용하려면 새 필드를 null 가능(nullable=True)으로 설정해야 합니다.
로드된 컬렉션에 필드를 추가하면 메모리 사용량이 증가합니다.
컬렉션당 총 필드 수에는 최대 제한이 있습니다. 자세한 내용은 밀버스 제한을 참조하세요.
필드 이름은 정적 필드 중에서 고유해야 합니다.
원래
enable_dynamic_field=True으로 만들지 않은 컬렉션에는 동적 필드 기능을 활성화하기 위해$meta필드를 추가할 수 없습니다.
전제 조건
이 가이드에서는 다음이 있다고 가정합니다:
실행 중인 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: null 가능한 필드를 빠르게 추가하기
컬렉션을 확장하는 가장 간단한 방법은 null 가능한 필드를 추가하는 것입니다. 이 방법은 데이터에 새 속성을 빠르게 추가해야 할 때 적합합니다.
# 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
}
FAQ
$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 클러스터 전체에 브로드캐스팅되는 과정에서 잠시 지연될 수 있습니다. 이 동기화를 통해 모든 노드가 새 필드와 관련된 쿼리를 처리하기 전에 스키마 업데이트를 인지할 수 있습니다.