🚀 Prova Zilliz Cloud, la versione completamente gestita di Milvus, gratuitamente—sperimenta prestazioni 10 volte più veloci! Prova Ora>>

milvus-logo
LFAI
  • Home
  • Blog
  • Milvus 2.0 - Uno sguardo alle nuove funzionalità

Milvus 2.0 - Uno sguardo alle nuove funzionalità

  • Engineering
January 27, 2022
Yanliang Qiao

È trascorso mezzo anno dalla prima release candidate di Milvus 2.0. Ora siamo orgogliosi di annunciare la disponibilità generale di Milvus 2.0. Seguitemi passo dopo passo per dare un'occhiata ad alcune delle nuove funzionalità supportate da Milvus.

Eliminazione delle entità

Milvus 2.0 supporta l'eliminazione delle entità, consentendo agli utenti di eliminare i vettori in base alle chiavi primarie (ID) dei vettori stessi. Non dovranno più preoccuparsi dei dati scaduti o non validi. Proviamo.

  1. Collegarsi a Milvus, creare una nuova collezione e inserire 300 righe di vettori 128-dimensionali generati a caso.
from pymilvus import connections, utility, Collection, DataType, FieldSchema, CollectionSchema
# connect to milvus
host = 'x.x.x.x'
connections.add_connection(default={"host": host, "port": 19530})
connections.connect(alias='default')
# create a collection with customized primary field: id_field
dim = 128
id_field = FieldSchema(name="cus_id", dtype=DataType.INT64, is_primary=True)
age_field = FieldSchema(name="age", dtype=DataType.INT64, description="age")
embedding_field = FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=dim)
schema = CollectionSchema(fields=[id_field, age_field, embedding_field],
                          auto_id=False, description="hello MilMil")
collection_name = "hello_milmil"
collection = Collection(name=collection_name, schema=schema)
import random
# insert data with customized ids
nb = 300
ids = [i for i in range(nb)]
ages = [random.randint(20, 40) for i in range(nb)]
embeddings = [[random.random() for _ in range(dim)] for _ in range(nb)]
entities = [ids, ages, embeddings]
ins_res = collection.insert(entities)
print(f"insert entities primary keys: {ins_res.primary_keys}")
insert entities primary keys: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299]
  1. Prima di procedere all'eliminazione, verificare che le entità da eliminare esistano tramite una ricerca o una query e farlo due volte per assicurarsi che il risultato sia affidabile.
# search
nq = 10
search_vec = [[random.random() for _ in range(dim)] for _ in range(nq)]
search_params = {"metric_type": "L2", "params": {"nprobe": 16}}
limit = 3
# search 2 times to verify the vector persists
for i in range(2):
    results = collection.search(search_vec, embedding_field.name, search_params, limit)
    ids = results[0].ids
    print(f"search result ids: {ids}")
    expr = f"cus_id in {ids}"
    # query to verify the ids exist
    query_res = collection.query(expr)
    print(f"query results: {query_res}")
search result ids: [76, 2, 246]
query results: [{'cus_id': 246}, {'cus_id': 2}, {'cus_id': 76}]
search result ids: [76, 2, 246]
query results: [{'cus_id': 246}, {'cus_id': 2}, {'cus_id': 76}]
  1. Eliminare l'entità con il cus_id 76, quindi cercare e interrogare questa entità.
print(f"trying to delete one vector: id={ids[0]}")
collection.delete(expr=f"cus_id in {[ids[0]]}")
results = collection.search(search_vec, embedding_field.name, search_params, limit)
ids = results[0].ids
print(f"after deleted: search result ids: {ids}")
expr = f"cus_id in {ids}"
# query to verify the id exists
query_res = collection.query(expr)
print(f"after deleted: query res: {query_res}")
print("completed")
trying to delete one vector: id=76
after deleted: search result ids: [76, 2, 246]
after deleted: query res: [{'cus_id': 246}, {'cus_id': 2}, {'cus_id': 76}]
completed

Perché l'entità cancellata è ancora recuperabile? Se avete controllato il codice sorgente di Milvus, scoprirete che la cancellazione all'interno di Milvus è asincrona e logica, il che significa che le entità non vengono cancellate fisicamente. Al contrario, saranno contrassegnate da un segno "cancellato", in modo che nessuna richiesta di ricerca o di interrogazione possa recuperarle. Inoltre, per impostazione predefinita, Milvus effettua ricerche con il livello di consistenza Bounded Staleness. Pertanto, le entità cancellate sono ancora recuperabili prima che i dati siano sincronizzati nel nodo dati e nel nodo query. Se si prova a cercare o interrogare l'entità cancellata dopo qualche secondo, si scoprirà che non è più presente nei risultati.

expr = f"cus_id in {[76, 2, 246]}"
# query to verify the id exists
query_res = collection.query(expr)
print(f"after deleted: query res: {query_res}")
print("completed")
after deleted: query res: [{'cus_id': 246}, {'cus_id': 2}]
completed

Livello di coerenza

L'esperimento precedente mostra come il livello di consistenza influenzi la visibilità immediata dei dati appena cancellati. Gli utenti possono regolare il livello di consistenza di Milvus in modo flessibile per adattarlo a vari scenari di servizio. Milvus 2.0 supporta quattro livelli di coerenza:

  • CONSISTENCY_STRONG GuaranteeTs è impostato come identico al timestamp più recente del sistema, e i nodi di interrogazione attendono che il tempo di servizio proceda fino al timestamp più recente del sistema, per poi elaborare la richiesta di ricerca o di interrogazione.
  • CONSISTENCY_EVENTUALLY GuaranteeTs è impostato su un valore insignificante rispetto al timestamp più recente del sistema per saltare il controllo di coerenza. I nodi di interrogazione effettuano immediatamente una ricerca sulla vista dati esistente.
  • CONSISTENCY_BOUNDED GuaranteeTs è impostato relativamente più piccolo del timestamp più recente del sistema e i nodi di interrogazione effettuano la ricerca su una vista dati tollerabile e meno aggiornata.
  • CONSISTENCY_SESSION: Il client utilizza il timestamp dell'ultima operazione di scrittura come GuaranteeTs, in modo che ogni client possa almeno recuperare i dati inseriti da solo.

Nella precedente versione RC, Milvus adotta Strong come consistenza predefinita. Tuttavia, tenendo conto del fatto che la maggior parte degli utenti è meno esigente in termini di consistenza che di prestazioni, Milvus ha modificato la consistenza predefinita in Bounded Staleness, che può bilanciare maggiormente le loro esigenze. In futuro, ottimizzeremo ulteriormente la configurazione delle GuaranteeT, che nella versione attuale può essere ottenuta solo durante la creazione della collezione. Per ulteriori informazioni su GuaranteeTs, vedere Garanzia Timestamp nelle richieste di ricerca.

Una minore consistenza porta a migliori prestazioni? La risposta non si può trovare finché non si prova.

  1. Modificare il codice precedente per registrare la latenza di ricerca.
for i in range(5):
    start = time.time()
    results = collection.search(search_vec, embedding_field.name, search_params, limit)
    end = time.time()
    print(f"search latency: {round(end-start, 4)}")
    ids = results[0].ids
    print(f"search result ids: {ids}")
  1. Cercate con la stessa scala di dati e gli stessi parametri, tranne che consistency_level è impostato come CONSISTENCY_STRONG.
collection_name = "hello_milmil_consist_strong"
collection = Collection(name=collection_name, schema=schema,
                        consistency_level=CONSISTENCY_STRONG)
search latency: 0.3293
search latency: 0.1949
search latency: 0.1998
search latency: 0.2016
search latency: 0.198
completed
  1. Cercare in una collezione con consistency_level impostato come CONSISTENCY_BOUNDED.
collection_name = "hello_milmil_consist_bounded"
collection = Collection(name=collection_name, schema=schema,
                        consistency_level=CONSISTENCY_BOUNDED)
search latency: 0.0144
search latency: 0.0104
search latency: 0.0107
search latency: 0.0104
search latency: 0.0102
completed
  1. È evidente che la latenza media di ricerca nella raccolta CONSISTENCY_BOUNDED è inferiore di 200 ms rispetto a quella della raccolta CONSISTENCY_STRONG.

Le entità eliminate sono immediatamente invisibili se il livello di coerenza è impostato come Forte? La risposta è sì. Si può comunque provare da soli.

Handoff

Lavorando con set di dati in streaming, molti utenti sono abituati a costruire un indice e a caricare la collezione prima di inserirvi i dati. Nelle versioni precedenti di Milvus, gli utenti dovevano caricare manualmente la collezione dopo la creazione dell'indice per sostituire i dati grezzi con l'indice, il che era lento e laborioso. La funzione handoff consente a Milvus 2.0 di caricare automaticamente il segmento indicizzato per sostituire i dati in streaming che raggiungono determinate soglie di indicizzazione, migliorando notevolmente le prestazioni di ricerca.

  1. Creare l'indice e caricare la collezione prima di inserire altre entità.
# index
index_params = {"index_type": "IVF_SQ8", "metric_type": "L2", "params": {"nlist": 64}}
collection.create_index(field_name=embedding_field.name, index_params=index_params)
# load
collection.load()
  1. Inserire 50.000 righe di entità per 200 volte (per comodità vengono utilizzati gli stessi lotti di vettori, ma ciò non influisce sul risultato).
import random
# insert data with customized ids
nb = 50000
ids = [i for i in range(nb)]
ages = [random.randint(20, 40) for i in range(nb)]
embeddings = [[random.random() for _ in range(dim)] for _ in range(nb)]
entities = [ids, ages, embeddings]
for i in range(200):
    ins_res = collection.insert(entities)
    print(f"insert entities primary keys: {ins_res.primary_keys}")
  1. Controllare le informazioni sul segmento di caricamento nel nodo query durante e dopo l'inserimento.
# did this in another python console
utility.get_query_segment_info("hello_milmil_handoff")
  1. Si noterà che tutti i segmenti sigillati caricati nel nodo di query sono indicizzati.
[segmentID: 430640405514551298
collectionID: 430640403705757697
partitionID: 430640403705757698
mem_size: 394463520
num_rows: 747090
index_name: "_default_idx"
indexID: 430640403745079297
nodeID: 7
state: Sealed
, segmentID: 430640405514551297
collectionID: 430640403705757697
partitionID: 430640403705757698
mem_size: 397536480
num_rows: 752910
index_name: "_default_idx"
indexID: 430640403745079297
nodeID: 7
state: Sealed
...

Cosa c'è di più

Oltre alle funzionalità sopra descritte, in Milvus 2.0 sono state introdotte nuove caratteristiche come la Compattazione dei dati, il Bilanciamento dinamico del carico e altre ancora. Vi invitiamo a godervi il vostro viaggio esplorativo con Milvus!

Nel prossimo futuro condivideremo con voi una serie di blog che introdurranno la progettazione delle nuove funzionalità di Milvus 2.0.

Trovateci su:

Try Managed Milvus for Free

Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.

Get Started

Like the article? Spread the word

Continua a Leggere