🚀 Попробуйте Zilliz Cloud, полностью управляемый Milvus, бесплатно — ощутите 10-кратное увеличение производительности! Попробовать сейчас>

milvus-logo
LFAI
  • Home
  • Blog
  • Milvus 2.0 - взгляд на новые возможности

Milvus 2.0 - взгляд на новые возможности

  • Engineering
January 27, 2022
Yanliang Qiao

Прошло полгода с момента выхода первого релиз-кандидата Milvus 2.0. Теперь мы с гордостью объявляем о выходе Milvus 2.0. Пожалуйста, следуйте за мной шаг за шагом, чтобы взглянуть на некоторые из новых функций, которые поддерживает Milvus.

Удаление сущностей

Milvus 2.0 поддерживает удаление сущностей, позволяя пользователям удалять векторы на основе первичных ключей (ID) векторов. Они больше не будут беспокоиться о просроченных или недействительных данных. Давайте попробуем.

  1. Подключитесь к Milvus, создайте новую коллекцию и вставьте в нее 300 строк случайно сгенерированных 128-мерных векторов.
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. Прежде чем приступить к удалению, проверьте, существуют ли сущности, которые вы хотите удалить, с помощью поиска или запроса, и сделайте это дважды, чтобы убедиться в достоверности результата.
# 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. Удалите сущность с cus_id 76, а затем выполните поиск и запрос для этой сущности.
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

Почему удаленную сущность все еще можно найти? Если вы ознакомились с исходным кодом Milvus, то обнаружили, что удаление в Milvus является асинхронным и логическим, что означает, что сущности не будут удалены физически. Вместо этого к ним будет прикреплена метка "удалено", так что никакие запросы на поиск или запрос не смогут их получить. Кроме того, по умолчанию Milvus выполняет поиск на уровне согласованности Bounded Staleness. Поэтому удаленные сущности все еще можно найти до синхронизации данных в узле данных и узле запросов. Попробуйте выполнить поиск или запрос по удаленной сущности через несколько секунд, и вы обнаружите, что ее больше нет в результатах.

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

Уровень согласованности

Приведенный выше эксперимент показывает, как уровень согласованности влияет на видимость удаленных данных. Пользователи могут гибко настраивать уровень согласованности для Milvus, чтобы адаптировать его к различным сценариям обслуживания. Milvus 2.0 поддерживает четыре уровня согласованности:

  • CONSISTENCY_STRONG: GuaranteeTs устанавливается как идентичный самой новой системной метке времени, и узлы запросов ждут, пока время обслуживания не перейдет к самой новой системной метке времени, а затем обрабатывают запрос на поиск или запрос.
  • CONSISTENCY_EVENTUALLY: GuaranteeTs устанавливается незначительно меньшим, чем самая новая системная временная метка, чтобы пропустить проверку согласованности. Узлы запросов сразу же выполняют поиск по существующему представлению данных.
  • CONSISTENCY_BOUNDED: GuaranteeTs устанавливается относительно меньшим, чем самая новая системная метка времени, и узлы запроса выполняют поиск в допустимом, менее обновленном представлении данных.
  • CONSISTENCY_SESSION: Клиент использует в качестве GuaranteeTs временную метку последней операции записи, так что каждый клиент может, по крайней мере, получить данные, вставленные самостоятельно.

В предыдущем RC-выпуске Milvus принял Strong в качестве согласованности по умолчанию. Однако, принимая во внимание тот факт, что большинство пользователей менее требовательны к согласованности, чем к производительности, Milvus меняет согласованность по умолчанию на Bounded Staleness, что позволяет в большей степени сбалансировать их требования. В будущем мы еще больше оптимизируем конфигурацию GuaranteeTs, которая в текущем выпуске может быть достигнута только при создании коллекции. Дополнительные сведения о GuaranteeTs см. в разделе Гарантийная метка времени в поисковых запросах.

Приведет ли снижение согласованности к повышению производительности? Вы никогда не сможете найти ответ, пока не попробуете.

  1. Измените приведенный выше код, чтобы записать задержку поиска.
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. Выполните поиск с идентичной шкалой данных и параметрами, за исключением того, что consistency_level установлен как 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. Поиск в коллекции, в которой consistency_level задан как 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. Очевидно, что средняя задержка поиска в коллекции CONSISTENCY_BOUNDED на 200 мс меньше, чем в коллекции CONSISTENCY_STRONG.

Являются ли удаленные сущности сразу невидимыми, если уровень согласованности установлен как Strong? Ответ: да. Вы можете попробовать сделать это самостоятельно.

Передача

Работая с потоковыми наборами данных, многие пользователи привыкли создавать индекс и загружать коллекцию перед вставкой в нее данных. В предыдущих выпусках Milvus пользователям приходилось загружать коллекцию вручную после построения индекса, чтобы заменить исходные данные индексом, что было медленно и трудоемко. Функция handoff позволяет Milvus 2.0 автоматически загружать проиндексированный сегмент для замены потоковых данных, которые достигают определенных порогов индексации, что значительно повышает производительность поиска.

  1. Построение индекса и загрузка коллекции перед вставкой новых сущностей.
# 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. Вставьте 50 000 строк сущностей 200 раз (для удобства используются одинаковые партии векторов, но это не повлияет на результат).
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. Проверьте информацию о сегменте загрузки в узле запроса во время и после вставки.
# did this in another python console
utility.get_query_segment_info("hello_milmil_handoff")
  1. Вы увидите, что все уплотненные сегменты, загруженные в узел запроса, проиндексированы.
[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
...

Что еще

В дополнение к вышеперечисленным функциям в Milvus 2.0 появились такие новые возможности, как уплотнение данных, динамический баланс нагрузки и другие. Пожалуйста, наслаждайтесь своим исследовательским путешествием с Milvus!

В ближайшем будущем мы поделимся с вами серией блогов, посвященных дизайну новых функций в Milvus 2.0.

Найти нас на:

Try Managed Milvus for Free

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

Get Started

Like the article? Spread the word

Продолжить чтение