Milvus 2.0 - Ein Blick auf die neuen Funktionen
Es ist ein halbes Jahr her, dass der erste Release Candidate von Milvus 2.0 veröffentlicht wurde. Nun sind wir stolz, die allgemeine Verfügbarkeit von Milvus 2.0 bekannt zu geben. Bitte folgen Sie mir Schritt für Schritt, um einen Blick auf einige der neuen Funktionen zu werfen, die Milvus unterstützt.
Löschung von Entitäten
Milvus 2.0 unterstützt die Löschung von Entitäten, so dass Benutzer Vektoren auf der Grundlage der Primärschlüssel (IDs) der Vektoren löschen können. Sie müssen sich keine Sorgen mehr über abgelaufene oder ungültige Daten machen. Probieren wir es aus.
- Stellen Sie eine Verbindung zu Milvus her, erstellen Sie eine neue Sammlung und fügen Sie 300 Zeilen mit zufällig erzeugten 128-dimensionalen Vektoren ein.
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]
- Bevor Sie mit dem Löschen fortfahren, überprüfen Sie, ob die zu löschenden Entitäten per Suche oder Abfrage existieren, und führen Sie den Vorgang zweimal durch, um sicherzustellen, dass das Ergebnis zuverlässig ist.
# 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}]
- Löschen Sie die Entität mit der cus_id von 76, und suchen Sie dann nach dieser Entität.
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
Warum ist die gelöschte Entität noch auffindbar? Wenn Sie sich den Quellcode von Milvus angesehen haben, werden Sie feststellen, dass die Löschung in Milvus asynchron und logisch erfolgt, was bedeutet, dass die Entitäten nicht physisch gelöscht werden. Stattdessen werden sie mit einer "deleted"-Markierung versehen, so dass keine Such- oder Abfrageanfragen sie abrufen können. Darüber hinaus sucht Milvus standardmäßig auf der Konsistenzstufe Bounded Staleness. Daher sind die gelöschten Entitäten noch abrufbar, bevor die Daten im Datenknoten und im Abfrageknoten synchronisiert werden. Versuchen Sie, die gelöschte Entität nach ein paar Sekunden zu suchen oder abzufragen, dann werden Sie feststellen, dass sie nicht mehr im Ergebnis enthalten ist.
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
Konsistenzniveau
Das obige Experiment zeigt uns, wie die Konsistenzstufe die sofortige Sichtbarkeit der neu gelöschten Daten beeinflusst. Benutzer können die Konsistenzstufe für Milvus flexibel einstellen, um es an verschiedene Dienstszenarien anzupassen. Milvus 2.0 unterstützt vier Konsistenzniveaus:
CONSISTENCY_STRONG
GuaranteeTs
ist identisch mit dem neuesten Systemzeitstempel, und Abfrageknoten warten, bis die Servicezeit auf den neuesten Systemzeitstempel übergeht, und bearbeiten dann die Such- oder Abfrageanfrage.CONSISTENCY_EVENTUALLY
GuaranteeTs
wird unwesentlich kleiner als der neueste Systemzeitstempel gesetzt, um die Konsistenzprüfung zu überspringen. Abfrageknoten suchen sofort in der vorhandenen Datenansicht.CONSISTENCY_BOUNDED
GuaranteeTs
wird relativ kleiner als der neueste Systemzeitstempel gesetzt, und Abfrageknoten suchen in einer tolerierbaren, weniger aktualisierten Datenansicht.CONSISTENCY_SESSION
: Der Client verwendet den Zeitstempel des letzten Schreibvorgangs alsGuaranteeTs
, so dass jeder Client zumindest die von ihm selbst eingefügten Daten abrufen kann.
In der letzten RC-Version nimmt Milvus Strong als Standardkonsistenz an. Angesichts der Tatsache, dass die meisten Benutzer weniger Wert auf Konsistenz als auf Leistung legen, ändert Milvus die Standardkonsistenz in Bounded Staleness, was ihren Anforderungen besser gerecht wird. In Zukunft werden wir die Konfiguration der GuaranteeTs weiter optimieren, was in der aktuellen Version nur während der Erstellung der Sammlung möglich ist. Weitere Informationen über GuaranteeTs
finden Sie unter Guarantee Timestamp in Suchanfragen.
Führt eine geringere Konsistenz zu einer besseren Leistung? Die Antwort können Sie erst finden, wenn Sie es ausprobieren.
- Ändern Sie den obigen Code, um die Suchlatenz aufzuzeichnen.
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}")
- Suchen Sie mit der identischen Datenskala und den identischen Parametern, mit der Ausnahme, dass
consistency_level
alsCONSISTENCY_STRONG
eingestellt ist.
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
- Suchen Sie in einer Sammlung, bei der
consistency_level
alsCONSISTENCY_BOUNDED
eingestellt ist.
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
- Es ist offensichtlich, dass die durchschnittliche Suchlatenz in der Sammlung
CONSISTENCY_BOUNDED
um 200 ms kürzer ist als in der SammlungCONSISTENCY_STRONG
.
Sind die gelöschten Entitäten sofort unsichtbar, wenn die Konsistenzstufe auf Stark eingestellt ist? Die Antwort lautet Ja. Sie können dies trotzdem selbst ausprobieren.
Handoff
Bei der Arbeit mit Streaming-Datensätzen sind viele Benutzer daran gewöhnt, einen Index zu erstellen und die Sammlung zu laden, bevor sie Daten in sie einfügen. In früheren Versionen von Milvus mussten die Benutzer die Sammlung nach der Indexerstellung manuell laden, um die Rohdaten durch den Index zu ersetzen, was langsam und mühsam ist. Mit der Handoff-Funktion kann Milvus 2.0 automatisch indizierte Segmente laden, um die Streaming-Daten zu ersetzen, die bestimmte Schwellenwerte der Indizierung erreichen, was die Suchleistung erheblich verbessert.
- Bauen Sie den Index auf und laden Sie die Sammlung, bevor Sie weitere Entitäten einfügen.
# 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()
- Fügen Sie 50.000 Zeilen von Entitäten 200-mal ein (der Einfachheit halber werden dieselben Vektorenstapel verwendet, aber das Ergebnis wird dadurch nicht beeinflusst).
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}")
- Überprüfen Sie die Informationen über die geladenen Segmente im Abfrageknoten während und nach dem Einfügen.
# did this in another python console
utility.get_query_segment_info("hello_milmil_handoff")
- Sie werden feststellen, dass alle versiegelten Segmente, die in den Abfrageknoten geladen werden, indiziert sind.
[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
...
Und noch mehr
Zusätzlich zu den oben genannten Funktionalitäten wurden in Milvus 2.0 neue Funktionen wie Datenverdichtung, dynamischer Lastausgleich und mehr eingeführt. Wir wünschen Ihnen viel Spaß bei Ihrer Entdeckungsreise mit Milvus!
In naher Zukunft werden wir Ihnen in einer Reihe von Blogs das Design der neuen Funktionen in Milvus 2.0 vorstellen.
Finden Sie uns auf:
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word