Milvus 2.0 - 새로운 기능 살펴보기
Milvus 2.0의 첫 번째 릴리스 후보가 공개된 지 반년이 지났습니다. 이제 Milvus 2.0의 정식 출시를 발표하게 되어 자랑스럽게 생각합니다. Milvus가 지원하는 몇 가지 새로운 기능을 단계별로 살펴보시기 바랍니다.
엔티티 삭제
Milvus 2.0은 엔티티 삭제를 지원하여 사용자가 벡터의 기본 키(ID)를 기반으로 벡터를 삭제할 수 있습니다. 이제 더 이상 만료되거나 유효하지 않은 데이터에 대해 걱정할 필요가 없습니다. 직접 해보겠습니다.
- 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]
- 삭제를 진행하기 전에 삭제하려는 엔티티가 검색 또는 쿼리를 통해 존재하는지 확인하고, 결과가 신뢰할 수 있는지 확인하기 위해 두 번 수행합니다.
# 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}]
- 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는 기본적으로 제한된 유효성 일관성 수준에서 검색을 수행합니다. 따라서 삭제된 엔티티는 데이터 노드와 쿼리 노드에서 데이터가 동기화되기 전에도 여전히 검색할 수 있습니다. 몇 초 후에 삭제된 엔티티를 검색하거나 쿼리하면 더 이상 결과에서 찾을 수 없습니다.
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은 4단계의 일관성 수준을 지원합니다:
CONSISTENCY_STRONG
GuaranteeTs
은 최신 시스템 타임스탬프와 동일하게 설정되며, 쿼리 노드는 서비스 시간이 최신 시스템 타임스탬프로 진행될 때까지 기다린 후 검색 또는 쿼리 요청을 처리합니다.CONSISTENCY_EVENTUALLY
GuaranteeTs
은 최신 시스템 타임스탬프보다 약간 작게 설정하여 일관성 검사를 건너뜁니다. 쿼리 노드는 기존 데이터 보기에서 즉시 검색합니다.CONSISTENCY_BOUNDED
GuaranteeTs
은 최신 시스템 타임스탬프보다 상대적으로 작게 설정되며, 쿼리 노드는 허용 가능한, 덜 업데이트된 데이터 보기에서 검색합니다.CONSISTENCY_SESSION
: 클라이언트는 마지막 쓰기 작업의 타임스탬프를GuaranteeTs
으로 사용하여 각 클라이언트가 최소한 자체적으로 삽입한 데이터를 검색할 수 있도록 합니다.
이전 RC 릴리스에서 Milvus는 기본 일관성으로 Strong을 채택했습니다. 그러나 대부분의 사용자가 성능보다 일관성에 대한 요구가 덜하다는 점을 고려하여 Milvus는 기본 일관성을 요구 사항의 균형을 더 많이 맞출 수 있는 Bounded Staleness로 변경했습니다. 향후에는 현재 릴리스에서는 컬렉션 생성 중에만 달성할 수 있는 GuaranteeT의 구성을 더욱 최적화할 예정입니다. 자세한 내용은 GuaranteeTs
에서 검색 요청의 타임스탬프 보장을 참조하세요.
일관성을 낮추면 성능이 향상되나요? 해보기 전까지는 답을 찾을 수 없습니다.
- 위의 코드를 수정하여 검색 지연 시간을 기록해 보세요.
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}")
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
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
- 분명히
CONSISTENCY_BOUNDED
컬렉션의 평균 검색 지연 시간이CONSISTENCY_STRONG
컬렉션의 검색 지연 시간보다 200ms 더 짧습니다.
일관성 수준을 강함으로 설정하면 삭제된 엔터티가 즉시 보이지 않나요? 대답은 '예'입니다. 직접 시도해 볼 수 있습니다.
핸드오프
스트리밍 데이터 집합으로 작업할 때, 많은 사용자들은 데이터를 삽입하기 전에 인덱스를 만들고 컬렉션을 로드하는 데 익숙합니다. 이전 버전의 Milvus에서는 사용자가 인덱스 구축 후 수동으로 컬렉션을 로드하여 원시 데이터를 인덱스로 대체해야 했기 때문에 느리고 번거로웠습니다. 밀버스 2.0은 핸드오프 기능을 통해 인덱싱된 세그먼트를 자동으로 로드하여 특정 임계값에 도달한 스트리밍 데이터를 인덱스로 대체함으로써 검색 성능을 크게 향상시켰습니다.
- 더 많은 엔티티를 삽입하기 전에 인덱스를 구축하고 컬렉션을 로드하세요.
# 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()
- 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}")
- 삽입 중과 삽입 후에 쿼리 노드에서 로딩 세그먼트 정보를 확인합니다.
# did this in another python console
utility.get_query_segment_info("hello_milmil_handoff")
- 쿼리 노드에 로드된 모든 봉인된 세그먼트가 색인된 것을 확인할 수 있습니다.
[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와 함께 즐거운 탐험을 시작하세요!
조만간 밀버스 2.0의 새로운 기능의 설계를 소개하는 블로그 시리즈를 통해 여러분과 함께 공유할 예정입니다.
우리를 찾아주세요:
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word