🚀 Experimente o Zilliz Cloud, o Milvus totalmente gerenciado, gratuitamente—experimente um desempenho 10x mais rápido! Experimente Agora>>

milvus-logo
LFAI
  • Home
  • Blog
  • Milvus 2.0 - Uma visão geral das novas funcionalidades

Milvus 2.0 - Uma visão geral das novas funcionalidades

  • Engineering
January 27, 2022
Yanliang Qiao

Já passou meio ano desde a primeira release candidate do Milvus 2.0. Agora temos o orgulho de anunciar a disponibilidade geral do Milvus 2.0. Por favor, siga-me passo a passo para ter um vislumbre de algumas das novas funcionalidades que o Milvus suporta.

Eliminação de entidades

O Milvus 2.0 suporta a eliminação de entidades, permitindo aos utilizadores eliminar vectores com base nas chaves primárias (IDs) dos vectores. Os utilizadores deixam de se preocupar com os dados expirados ou inválidos. Vamos experimentar.

  1. Ligue-se ao Milvus, crie uma nova coleção e insira 300 linhas de vectores 128-dimensionais gerados aleatoriamente.
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. Antes de proceder à eliminação, verifique se as entidades que pretende eliminar existem por pesquisa ou consulta e faça-o duas vezes para se certificar de que o resultado é fiável.
# 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. Elimine a entidade com o cus_id 76 e, em seguida, pesquise e consulte esta entidade.
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

Porque é que a entidade eliminada ainda pode ser recuperada? Se consultou o código fonte do Milvus, verificou que a eliminação no Milvus é assíncrona e lógica, o que significa que as entidades não são eliminadas fisicamente. Em vez disso, serão anexadas com uma marca de "apagadas" para que nenhum pedido de pesquisa ou consulta as recupere. Além disso, por defeito, o Milvus efectua pesquisas com o nível de consistência Bounded Staleness. Portanto, as entidades eliminadas ainda podem ser recuperadas antes de os dados serem sincronizados no nó de dados e no nó de consulta. Se tentar pesquisar ou consultar a entidade eliminada após alguns segundos, verificará que ela já não se encontra no resultado.

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

Nível de consistência

A experiência acima mostra-nos como o nível de consistência influencia a visibilidade imediata dos dados recentemente eliminados. Os utilizadores podem ajustar o nível de consistência do Milvus de forma flexível para o adaptar a vários cenários de serviço. O Milvus 2.0 suporta quatro níveis de consistência:

  • CONSISTENCY_STRONG GuaranteeTs é definido como idêntico ao carimbo de data/hora mais recente do sistema, e os nós de consulta aguardam até que o tempo de serviço avance para o carimbo de data/hora mais recente do sistema e, em seguida, processam o pedido de pesquisa ou consulta.
  • CONSISTENCY_EVENTUALLYA verificação de consistência é ignorada quando GuaranteeTs é definido como insignificantemente menor do que o carimbo de data/hora mais recente do sistema. Os nós de consulta pesquisam imediatamente na vista de dados existente.
  • CONSISTENCY_BOUNDED GuaranteeTs é definido como relativamente mais pequeno do que o carimbo de data/hora mais recente do sistema, e os nós de consulta pesquisam numa vista de dados tolerável e menos actualizada.
  • CONSISTENCY_SESSION: O cliente utiliza o carimbo de data/hora da última operação de escrita como GuaranteeTs, para que cada cliente possa, pelo menos, recuperar os dados inseridos por si próprio.

Na versão RC anterior, o Milvus adopta a consistência Strong por defeito. No entanto, tendo em conta o facto de a maioria dos utilizadores ser menos exigente em termos de consistência do que de desempenho, Milvus alterou a consistência por defeito para Bounded Staleness, o que permite equilibrar melhor os seus requisitos. No futuro, iremos otimizar ainda mais a configuração dos GuaranteeTs, o que só pode ser conseguido durante a criação da coleção na versão atual. Para obter mais informações sobre GuaranteeTs, consulte Carimbo de data/hora de garantia em solicitações de pesquisa.

Uma consistência menor levará a um melhor desempenho? Nunca se sabe a resposta até que se experimente.

  1. Modifique o código acima para registrar a latência da pesquisa.
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. Pesquise com a escala de dados e os parâmetros idênticos, exceto que consistency_level é definido como 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. Pesquise em uma coleção com consistency_level definido como 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. Claramente, a latência média de pesquisa na coleção CONSISTENCY_BOUNDED é 200ms mais curta do que na coleção CONSISTENCY_STRONG.

As entidades excluídas ficam imediatamente invisíveis se o nível de consistência for definido como Forte? A resposta é Sim. Ainda pode tentar fazer isto por si próprio.

Transferência

Ao trabalhar com um conjunto de dados em streaming, muitos utilizadores estão habituados a construir um índice e a carregar a coleção antes de inserir dados na mesma. Em versões anteriores do Milvus, os utilizadores têm de carregar a coleção manualmente após a construção do índice para substituir os dados brutos pelo índice, o que é lento e trabalhoso. A funcionalidade handoff permite ao Milvus 2.0 carregar automaticamente o segmento indexado para substituir os dados em fluxo que atingem determinados limites de indexação, melhorando consideravelmente o desempenho da pesquisa.

  1. Construir o índice e carregar a coleção antes de inserir mais entidades.
# 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. Insira 50.000 linhas de entidades 200 vezes (os mesmos lotes de vectores são utilizados por uma questão de conveniência, mas isso não afectará o resultado).
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. Verifique as informações do segmento de carregamento no nó de consulta durante e após a inserção.
# did this in another python console
utility.get_query_segment_info("hello_milmil_handoff")
  1. Verificará que todos os segmentos selados carregados no nó de consulta são indexados.
[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
...

E mais

Para além das funcionalidades acima referidas, foram introduzidas no Milvus 2.0 novas caraterísticas como a Compactação de Dados, o Equilíbrio Dinâmico de Carga, entre outras. Por favor, aproveite a sua viagem exploratória com Milvus!

Num futuro próximo, partilharemos consigo uma série de blogues que apresentarão o design das novas funcionalidades do Milvus 2.0.

Encontre-nos em:

Try Managed Milvus for Free

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

Get Started

Like the article? Spread the word

Continue Lendo