Utilizzo della chiave di partizione
Questa guida illustra l'uso della chiave di partizione per accelerare il recupero dei dati dalla raccolta.
Panoramica
È possibile impostare un campo particolare di una raccolta come chiave di partizione, in modo che Milvus distribuisca le entità in arrivo in diverse partizioni in base ai rispettivi valori di partizione in questo campo. In questo modo, le entità con lo stesso valore di chiave vengono raggruppate in una partizione, accelerando le prestazioni di ricerca ed evitando la scansione di partizioni irrilevanti quando si filtra in base al campo chiave. Rispetto ai metodi di filtraggio tradizionali, la chiave di partizione può migliorare notevolmente le prestazioni delle query.
È possibile utilizzare la chiave di partizione per implementare la multi-tenancy. Per maggiori dettagli sulla multi-tenancy, leggere Multi-tenancy.
Abilitare la chiave di partizione
Per impostare un campo come chiave di partizione, specificare partition_key_field
quando si crea uno schema di raccolta.
Nell'esempio di codice qui sotto, num_partitions
determina il numero di partizioni che verranno create. Per impostazione predefinita, è impostato su 64
. Si consiglia di mantenere il valore predefinito.
Per ulteriori informazioni sui parametri, consultare MilvusClient
, create_schema()
, e add_field()
nel riferimento dell'SDK.
Per ulteriori informazioni sui parametri, fare riferimento a MilvusClientV2
, createSchema()
, e addField()
nel riferimento al programma SDK.
Per ulteriori informazioni sui parametri, fare riferimento a MilvusClient
e createCollection()
nel riferimento dell'SDK.
import random, time
from pymilvus import connections, MilvusClient, DataType
SERVER_ADDR = "http://localhost:19530"
# 1. Set up a Milvus client
client = MilvusClient(
uri=SERVER_ADDR
)
# 2. Create a collection
schema = MilvusClient.create_schema(
auto_id=False,
enable_dynamic_field=True,
partition_key_field="color",
num_partitions=64 # Number of partitions. Defaults to 64.
)
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=5)
schema.add_field(field_name="color", datatype=DataType.VARCHAR, max_length=512)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.DataType;
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
String CLUSTER_ENDPOINT = "http://localhost:19530";
// 1. Connect to Milvus server
ConnectConfig connectConfig = ConnectConfig.builder()
.uri(CLUSTER_ENDPOINT)
.build();
MilvusClientV2 client = new MilvusClientV2(connectConfig);
// 2. Create a collection in customized setup mode
// 2.1 Create schema
CreateCollectionReq.CollectionSchema schema = client.createSchema();
schema.setEnableDynamicField(true);
// 2.2 Add fields to schema
schema.addField(AddFieldReq.builder()
.fieldName("id")
.dataType(DataType.Int64)
.isPrimaryKey(true)
.autoID(false)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("vector")
.dataType(DataType.FloatVector)
.dimension(5)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("color")
.dataType(DataType.VarChar)
.maxLength(512)
.isPartitionKey(true)
.build());
const { MilvusClient, DataType, sleep } = require("@zilliz/milvus2-sdk-node")
const address = "http://localhost:19530"
async function main() {
// 1. Set up a Milvus Client
client = new MilvusClient({address});
// 2. Create a collection
// 2.1 Define fields
const fields = [
{
name: "id",
data_type: DataType.Int64,
is_primary_key: true,
auto_id: false
},
{
name: "vector",
data_type: DataType.FloatVector,
dim: 5
},
{
name: "color",
data_type: DataType.VarChar,
max_length: 512,
is_partition_key: true
}
]
Dopo aver definito i campi, impostare i parametri dell'indice.
index_params = MilvusClient.prepare_index_params()
index_params.add_index(
field_name="id",
index_type="STL_SORT"
)
index_params.add_index(
field_name="color",
index_type="Trie"
)
index_params.add_index(
field_name="vector",
index_type="IVF_FLAT",
metric_type="L2",
params={"nlist": 1024}
)
// 2.3 Prepare index parameters
Map<String, Object> params = new HashMap<>();
params.put("nlist", 1024);
IndexParam indexParamForVectorField = IndexParam.builder()
.fieldName("vector")
.indexType(IndexParam.IndexType.IVF_FLAT)
.metricType(IndexParam.MetricType.IP)
.extraParams(params)
.build();
List<IndexParam> indexParams = new ArrayList<>();
indexParams.add(indexParamForVectorField);
// 2.2 Prepare index parameters
const index_params = [{
field_name: "color",
index_type: "Trie"
},{
field_name: "id",
index_type: "STL_SORT"
},{
field_name: "vector",
index_type: "IVF_FLAT",
metric_type: "IP",
params: { nlist: 1024}
}]
Infine, è possibile creare una collezione.
client.create_collection(
collection_name="test_collection",
schema=schema,
index_params=index_params
)
// 2.4 Create a collection with schema and index parameters
CreateCollectionReq customizedSetupReq = CreateCollectionReq.builder()
.collectionName("test_collection")
.collectionSchema(schema)
.indexParams(indexParams)
.build();
client.createCollection(customizedSetupReq);
// 2.3 Create a collection with fields and index parameters
res = await client.createCollection({
collection_name: "test_collection",
fields: fields,
index_params: index_params,
})
console.log(res.error_code)
// Output
//
// Success
//
Elencare le partizioni
Una volta che un campo di una collezione viene usato come chiave di partizione, Milvus crea il numero di partizioni specificato e le gestisce per conto dell'utente. Pertanto, non è più possibile manipolare le partizioni di questa collezione.
Lo snippet seguente dimostra che 64 partizioni in una raccolta una volta che uno dei suoi campi viene usato come chiave di partizione.
Inserire i dati
Una volta che la raccolta è pronta, iniziare a inserire i dati come segue:
Preparare i dati
# 3. Insert randomly generated vectors
colors = ["green", "blue", "yellow", "red", "black", "white", "purple", "pink", "orange", "brown", "grey"]
data = []
for i in range(1000):
current_color = random.choice(colors)
current_tag = random.randint(1000, 9999)
data.append({
"id": i,
"vector": [ random.uniform(-1, 1) for _ in range(5) ],
"color": current_color,
"tag": current_tag,
"color_tag": f"{current_color}_{str(current_tag)}"
})
print(data[0])
// 3. Insert randomly generated vectors
List<String> colors = Arrays.asList("green", "blue", "yellow", "red", "black", "white", "purple", "pink", "orange", "brown", "grey");
List<JsonObject> data = new ArrayList<>();
Gson gson = new Gson();
Random rand = new Random();
for (int i=0; i<1000; i++) {
String current_color = colors.get(rand.nextInt(colors.size()-1));
int current_tag = rand.nextInt(8999) + 1000;
JsonObject row = new JsonObject();
row.addProperty("id", (long) i);
row.add("vector", gson.toJsonTree(Arrays.asList(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), rand.nextFloat())));
row.addProperty("color", current_color);
row.addProperty("tag", current_tag);
row.addProperty("color_tag", current_color + "_" + (rand.nextInt(8999) + 1000));
data.add(row);
}
System.out.println(data.get(0));
// 3. Insert randomly generated vectors
const colors = ["green", "blue", "yellow", "red", "black", "white", "purple", "pink", "orange", "brown", "grey"]
var data = []
for (let i = 0; i < 1000; i++) {
const current_color = colors[Math.floor(Math.random() * colors.length)]
const current_tag = Math.floor(Math.random() * 8999 + 1000)
data.push({
id: i,
vector: [Math.random(), Math.random(), Math.random(), Math.random(), Math.random()],
color: current_color,
tag: current_tag,
color_tag: `${current_color}_${current_tag}`
})
}
console.log(data[0])
È possibile visualizzare la struttura dei dati generati controllando la prima voce.
{
id: 0,
vector: [
0.1275656405044483,
0.47417858592773277,
0.13858264437643286,
0.2390904907020377,
0.8447862593689635
],
color: 'blue',
tag: 2064,
color_tag: 'blue_2064'
}
Inserire i dati
Utilizzare il metodo insert()
per inserire i dati nell'insieme.
Utilizzare il metodo insert()
per inserire i dati nell'insieme.
Utilizzare il metodo insert()
per inserire i dati nell'insieme.
res = client.insert(
collection_name="test_collection",
data=data
)
print(res)
# Output
#
# {
# "insert_count": 1000,
# "ids": [
# 0,
# 1,
# 2,
# 3,
# 4,
# 5,
# 6,
# 7,
# 8,
# 9,
# "(990 more items hidden)"
# ]
# }
// 3.1 Insert data into the collection
InsertReq insertReq = InsertReq.builder()
.collectionName("test_collection")
.data(data)
.build();
InsertResp insertResp = client.insert(insertReq);
System.out.println(insertResp.getInsertCnt());
// Output:
// 1000
res = await client.insert({
collection_name: "test_collection",
data: data,
})
console.log(res.insert_cnt)
// Output
//
// 1000
//
Utilizzare la chiave di partizione
Dopo aver indicizzato e caricato l'insieme e aver inserito i dati, è possibile eseguire una ricerca di similarità utilizzando la chiave di partizione.
Per ulteriori informazioni sui parametri, consultare la sezione search()
nel riferimento dell'SDK.
Per ulteriori informazioni sui parametri, fare riferimento a search()
nel riferimento al programma SDK.
Per ulteriori informazioni sui parametri, fare riferimento a search()
nel riferimento al programma SDK.
note
Per eseguire una ricerca di somiglianza utilizzando la chiave di partizione, è necessario includere uno dei seguenti elementi nell'espressione booleana della richiesta di ricerca:
expr='<partition_key>=="xxxx"'
expr='<partition_key> in ["xxx", "xxx"]'
Sostituire <partition_key>
con il nome del campo designato come chiave di partizione.
# 4. Search with partition key
query_vectors = [[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]]
res = client.search(
collection_name="test_collection",
data=query_vectors,
filter="color == 'green'",
search_params={"metric_type": "L2", "params": {"nprobe": 10}},
output_fields=["id", "color_tag"],
limit=3
)
print(res)
# Output
#
# [
# [
# {
# "id": 970,
# "distance": 0.5770174264907837,
# "entity": {
# "id": 970,
# "color_tag": "green_9828"
# }
# },
# {
# "id": 115,
# "distance": 0.6898155808448792,
# "entity": {
# "id": 115,
# "color_tag": "green_4073"
# }
# },
# {
# "id": 899,
# "distance": 0.7028976678848267,
# "entity": {
# "id": 899,
# "color_tag": "green_9897"
# }
# }
# ]
# ]
// 4. Search with partition key
List<BaseVector> query_vectors = Collections.singletonList(new FloatVec(new float[]{0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f}));
SearchReq searchReq = SearchReq.builder()
.collectionName("test_collection")
.data(query_vectors)
.filter("color == \"green\"")
.topK(3)
.outputFields(Collections.singletonList("color_tag"))
.build();
SearchResp searchResp = client.search(searchReq);
List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
for (List<SearchResp.SearchResult> results : searchResults) {
System.out.println("TopK results:");
for (SearchResp.SearchResult result : results) {
System.out.println(result);
}
}
// Output:
// SearchResp.SearchResult(entity={color_tag=green_4945}, score=1.192079, id=542)
// SearchResp.SearchResult(entity={color_tag=green_4633}, score=0.9138917, id=144)
// SearchResp.SearchResult(entity={color_tag=green_8038}, score=0.8381896, id=962)
// 4. Search with partition key
const query_vectors = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = await client.search({
collection_name: "test_collection",
data: query_vectors,
filter: "color == 'green'",
output_fields: ["color_tag"],
limit: 3
})
console.log(res.results)
// Output
//
// [
// { score: 2.402090549468994, id: '135', color_tag: 'green_2694' },
// { score: 2.3938629627227783, id: '326', color_tag: 'green_7104' },
// { score: 2.3235254287719727, id: '801', color_tag: 'green_3162' }
// ]
//
Casi d'uso tipici
È possibile utilizzare la funzione di chiave di partizione per ottenere migliori prestazioni di ricerca e abilitare la multi-tenancy. Questo può essere fatto assegnando un valore specifico per ogni tenancy come campo della chiave di partizione per ogni entità. Durante la ricerca o l'interrogazione della raccolta, è possibile filtrare le entità in base al valore specifico del tenant includendo il campo della chiave di partizione nell'espressione booleana. Questo approccio garantisce l'isolamento dei dati per tenant ed evita la scansione di partizioni non necessarie.