🚀 Zilliz Cloudを無料で試す、完全管理型のMilvus—10倍の高速パフォーマンスを体験しよう!今すぐ試す>>

milvus-logo
LFAI

Milvus 2.0 - 新機能の紹介

  • Engineering
January 27, 2022
Yanliang Qiao

Milvus 2.0の最初のリリース候補から半年が経ちました。この度、Milvus 2.0の一般提供を開始する運びとなりました。Milvusの新機能を順を追ってご紹介いたします。

エンティティ削除

Milvus2.0はエンティティ削除に対応しており、ベクターの主キー(ID)に基づいてベクターを削除することができます。これにより、期限切れや無効なデータを心配する必要がなくなります。試してみましょう。

  1. Milvusに接続し、新しいコレクションを作成し、ランダムに生成された128次元ベクトルを300行挿入します。
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. 削除に進む前に、検索またはクエリで削除したいエンティティが存在することを確認し、結果が信頼できることを確認するために2回実行する。
# 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内での削除は非同期かつ論理的であることがわかります。つまり、エンティティは物理的には削除されません。その代わりに "deleted "マークが付けられ、検索やクエリ要求がそれらを取得することはありません。さらに、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の整合性レベルは、様々なサービスシナリオに適応させるために、ユーザが柔軟に調整することができます。Milvus2.0では、4つの一貫性レベルをサポートしている:

  • CONSISTENCY_STRONG GuaranteeTs は最新のシステムタイムスタンプと同一に設定され、クエリノードはサービスタイムが最新のシステムタイムスタンプに進むまで待機し、検索またはクエリ要求を処理する。
  • CONSISTENCY_EVENTUALLY:GuaranteeTs は最新のシステム・タイムスタンプより著しく小さく設定され、整合性チェックをスキップする。クエリ・ノードは既存のデータ・ビューを即座に検索する。
  • CONSISTENCY_BOUNDED:GuaranteeTs が最新のシステム・タイムスタンプより相対的に小さく設定され、クエリ・ノードは許容範囲内で更新の少ないデータ・ビューを検索する。
  • CONSISTENCY_SESSION:クライアントは最後の書き込み操作のタイムスタンプをGuaranteeTs 、各クライアントが少なくとも自分自身で挿入されたデータを検索できるようにする。

以前のRCリリースでは、Milvusはデフォルトの一貫性としてStrongを採用していた。しかし、Milvusは、多くのユーザが性能よりも一貫性をあまり要求していないという事実を考慮し、デフォルトの一貫性をBounded Stalenessに変更した。将来的には、GuaranteeTsの設定をさらに最適化する予定である。GuaranteeTs の詳細については、検索リクエストのGuarantee Timestampを参照してください。

一貫性が低ければパフォーマンスが向上するのでしょうか?答えは試してみないとわかりません。

  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_levelCONSISTENCY_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_levelCONSISTENCY_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 コレクションの平均検索待ち時間は、CONSISTENCY_STRONG コレクションの平均検索待ち時間より200ms短い。

一貫性レベルがStrongに設定されている場合、削除されたエンティティはすぐに見えなくなりますか?答えはYesである。まだ自分で試すことができます。

ハンドオフ

ストリーミングデータセットを扱う場合、多くのユーザはデータを挿入する前にインデックスを作成し、コレクションをロードすることに慣れています。Milvusの以前のリリースでは、生データをインデックスに置き換えるために、インデックス構築後に手作業でコレクションをロードする必要がありました。ハンドオフ機能により、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

続けて読む