Menambahkan Field ke Koleksi yang Sudah AdaCompatible with Milvus 2.6.x

Milvus memungkinkan Anda untuk menambahkan field baru secara dinamis ke koleksi yang sudah ada, sehingga memudahkan untuk mengembangkan skema data Anda seiring dengan perubahan kebutuhan aplikasi Anda. Panduan ini menunjukkan kepada Anda bagaimana cara menambahkan field dalam berbagai skenario dengan menggunakan contoh-contoh praktis.

Pertimbangan

Sebelum menambahkan field ke koleksi Anda, ingatlah beberapa hal penting berikut ini:

  • Anda dapat menambahkan field skalar (INT64, VARCHAR, FLOAT, DOUBLE, dll.). Bidang vektor tidak dapat ditambahkan ke koleksi yang sudah ada.

  • Field baru harus dapat dinullkan (nullable = True) untuk mengakomodasi entitas yang sudah ada yang tidak memiliki nilai untuk field baru.

  • Menambahkan field ke koleksi yang sudah dimuat akan meningkatkan penggunaan memori.

  • Ada batas maksimum pada total field per koleksi. Untuk detailnya, lihat Batas Milvus.

  • Nama bidang harus unik di antara bidang-bidang statis.

  • Anda tidak dapat menambahkan bidang $meta untuk mengaktifkan fungsionalitas bidang dinamis untuk koleksi yang awalnya tidak dibuat dengan enable_dynamic_field=True.

Prasyarat

Panduan ini mengasumsikan bahwa Anda memiliki:

  • Instance Milvus yang sedang berjalan

  • Milvus SDK yang sudah terinstal

  • Koleksi yang sudah ada

Lihat Membuat Koleksi untuk mengetahui pembuatan koleksi dan operasi dasar.

Penggunaan dasar

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"

Skenario 1: Menambahkan field yang dapat dinullkan dengan cepat

Cara paling sederhana untuk memperluas koleksi Anda adalah dengan menambahkan bidang yang dapat dinullkan. Cara ini sangat cocok ketika Anda perlu menambahkan atribut baru dengan cepat ke data Anda.

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

Perilaku yang diharapkan:

  • Entitas yang sudah ada akan memiliki NULL untuk bidang baru

  • Entitas baru dapat memiliki nilai NULL atau nilai aktual

  • Ketersediaan bidang terjadi hampir seketika dengan penundaan minimal karena sinkronisasi skema internal

  • Dapat ditanyakan segera setelah periode sinkronisasi singkat

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

Skenario 2: Menambahkan bidang dengan nilai default

Ketika Anda ingin entitas yang ada memiliki nilai awal yang berarti, bukan NULL, tentukan nilai default.

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

Perilaku yang diharapkan:

  • Entitas yang sudah ada akan memiliki nilai default ("standard") untuk bidang yang baru ditambahkan

  • Entitas baru dapat mengganti nilai default atau menggunakannya jika tidak ada nilai yang disediakan

  • Ketersediaan bidang terjadi segera dengan penundaan minimal

  • Dapat ditanyakan segera setelah periode sinkronisasi singkat

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

PERTANYAAN UMUM

Dapatkah saya mengaktifkan fungsionalitas skema dinamis dengan menambahkan bidang $meta?

Tidak, Anda tidak dapat menggunakan add_collection_field untuk menambahkan bidang $meta untuk mengaktifkan fungsionalitas bidang dinamis. Sebagai contoh, kode di bawah ini tidak akan berfungsi:

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

Untuk mengaktifkan fungsionalitas skema dinamis:

  • Koleksi baru: Tetapkan enable_dynamic_field ke True saat membuat koleksi. Untuk detailnya, lihat Membuat Koleksi

  • Koleksi yang sudah ada: Atur properti tingkat koleksi dynamicfield.enabled ke True. Untuk detailnya, lihat Memodifikasi Koleksi.

Apa yang terjadi jika saya menambahkan bidang dengan nama yang sama dengan kunci bidang dinamis?

Bila koleksi Anda memiliki bidang dinamis yang diaktifkan ($meta ada), Anda bisa menambahkan bidang statis yang memiliki nama yang sama dengan kunci bidang dinamis yang ada. Bidang statis baru akan menutupi kunci bidang dinamis, namun data dinamis asli tetap dipertahankan.

Untuk menghindari kemungkinan konflik dalam nama field, pertimbangkan nama untuk field yang akan ditambahkan dengan mengacu pada field yang ada dan kunci field dinamis sebelum benar-benar menambahkannya.

Contoh skenario:

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

Perilaku yang diharapkan:

  • Entitas yang sudah ada akan memiliki NULL untuk bidang statis yang baru extra_info

  • Entitas baru harus menggunakan tipe data bidang statis (INT64)

  • Nilai kunci bidang dinamis asli dipertahankan dan dapat diakses melalui sintaks $meta

  • Bidang statis menutupi kunci bidang dinamis dalam kueri normal

Mengakses nilai statis dan dinamis:

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

Berapa lama waktu yang dibutuhkan agar field baru tersedia?

Field yang ditambahkan akan segera tersedia, tetapi mungkin ada penundaan singkat karena perubahan skema internal yang disiarkan di seluruh cluster Milvus. Sinkronisasi ini memastikan semua node mengetahui pembaruan skema sebelum memproses kueri yang melibatkan bidang baru.