기본 벡터 검색 (Basic Vector Search)
벡터 임베딩의 정렬된 순서를 기록한 인덱스 파일을 기반으로, ANN(Approximate Nearest Neighbor, 근사 최근접 이웃) 검색은 수신한 검색 요청에 담긴 쿼리 벡터를 기준으로 벡터 임베딩의 하위 집합을 찾아내고, 쿼리 벡터를 그 하위 그룹 안의 벡터들과 비교해 가장 유사한 결과를 반환해요. ANN 검색 덕분에 Milvus는 효율적인 검색 경험을 제공해요. 이 페이지는 기본적인 ANN 검색을 수행하는 방법을 배우도록 도와줘요.
컬렉션을 만든 뒤 새 필드를 추가하면, 해당 필드가 포함된 검색은 명시적으로 값을 설정하지 않은 엔티티에 대해 정의된 기본값이나 NULL을 반환해요. 자세한 내용은 Alter Collection Schema를 참고하세요.
출처: Milvus 문서
본문
개요 (Overview)
ANN과 kNN(k-Nearest Neighbors) 검색은 벡터 유사도 검색에서 흔히 쓰이는 방법이에요. kNN 검색에서는 검색 요청에 담긴 쿼리 벡터와 벡터 공간의 모든 벡터를 비교한 뒤 가장 유사한 것들을 찾아내야 하기 때문에 시간이 많이 들고 리소스도 많이 소모돼요.
kNN 검색과 달리 ANN 검색 알고리즘은 벡터 임베딩의 정렬된 순서를 기록한 인덱스 파일을 요구해요. 검색 요청이 들어오면 인덱스 파일을 참조로 삼아 쿼리 벡터와 가장 유사한 벡터 임베딩을 포함할 가능성이 높은 하위 그룹을 빠르게 찾을 수 있어요. 그런 다음 지정된 metric type을 사용해 쿼리 벡터와 하위 그룹 내 벡터 사이의 유사도를 측정하고, 쿼리 벡터와의 유사도에 따라 그룹 구성원을 정렬해 top-K 구성원을 찾아낼 수 있어요.
ANN 검색은 사전 구축된 인덱스에 의존하며, 선택한 인덱스 유형에 따라 검색 처리량, 메모리 사용량, 검색 정확도가 달라질 수 있어요. 검색 성능과 정확도 사이의 균형을 맞춰야 해요.
학습 곡선을 낮추기 위해 Milvus는 AUTOINDEX를 제공해요. AUTOINDEX를 사용하면 Milvus가 인덱스를 구축하면서 컬렉션 내 데이터 분포를 분석하고, 그 분석을 바탕으로 가장 최적화된 인덱스 파라미터를 설정해 검색 성능과 정확도 사이의 균형을 잡아줘요.
이 섹션에서는 다음 주제에 대한 상세한 정보를 다뤄요.
- 단일 벡터 검색 (Single-vector search)
- 대량 벡터 검색 (Bulk-vector search)
- 파티션 내 ANN 검색 (ANN search in partitions)
- 출력 필드 사용 (Use output fields)
- limit과 offset 사용 (Use limit and offset)
- level 사용 (Use level)
- 리콜율 얻기 (Get Recall Rate)
- ANN 검색 향상 (Enhancing ANN search)
단일 벡터 검색 (Single-Vector Search)
ANN 검색에서 단일 벡터 검색은 쿼리 벡터 하나만을 다루는 검색을 말해요. 사전 구축된 인덱스와 검색 요청에 담긴 metric type을 기반으로 Milvus는 쿼리 벡터와 가장 유사한 top-K 벡터를 찾아줘요.
이 섹션에서는 단일 벡터 검색을 수행하는 방법을 배워요. 검색 요청은 쿼리 벡터 하나를 담고, Milvus에 IP(Inner Product)를 사용해 쿼리 벡터와 컬렉션 내 벡터 사이의 유사도를 계산해 가장 유사한 세 개를 반환하도록 요청해요.
from pymilvus import MilvusClient
client = MilvusClient(
uri="http://localhost:19530",
token="root:Milvus"
)
# 4. Single vector search
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = client.search(
collection_name="quick_setup",
anns_field="vector",
data=[query_vector],
limit=3,
search_params={"metric_type": "IP"}
)
for hits in res:
for hit in hits:
print(hit)
# [
# [
# {
# "id": 551,
# "distance": 0.08821295201778412,
# "entity": {}
# },
# {
# "id": 296,
# "distance": 0.0800950899720192,
# "entity": {}
# },
# {
# "id": 43,
# "distance": 0.07794742286205292,
# "entity": {}
# }
# ]
# ]
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
import java.util.*;
MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
.uri("http://localhost:19530")
.token("root:Milvus")
.build());
FloatVec queryVector = new FloatVec(new float[]{0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f});
SearchReq searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(Collections.singletonList(queryVector))
.annsField("vector")
.topK(3)
.build();
SearchResp searchResp = client.search(searchReq);
List<List<SearchResp.SearchResult>> searchResults = searchResp.getSearchResults();
for (List<SearchResp.SearchResult> results : searchResults) {
System.out.println("TopK results:");
for (SearchResp.SearchResult result : results) {
System.out.println(result);
}
}
// Output
// TopK results:
// SearchResp.SearchResult(entity={}, score=0.95944905, id=5)
// SearchResp.SearchResult(entity={}, score=0.8689616, id=1)
// SearchResp.SearchResult(entity={}, score=0.866088, id=7)
import (
"context"
"fmt"
"github.com/milvus-io/milvus/client/v2/entity"
"github.com/milvus-io/milvus/client/v2/milvusclient"
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
milvusAddr := "localhost:19530"
token := "root:Milvus"
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
Address: milvusAddr,
APIKey: "root:Milvus",
})
if err != nil {
fmt.Println(err.Error())
// handle error
}
defer client.Close(ctx)
queryVector := []float32{0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"quick_setup", // collectionName
3, // limit
[]entity.Vector{entity.FloatVector(queryVector)},
).WithANNSField("vector"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
for _, resultSet := range resultSets {
fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
fmt.Println("Scores: ", resultSet.Scores)
}
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";
const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});
// 4. Single vector search
var query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
res = await client.search({
collection_name: "quick_setup",
data: query_vector,
limit: 3, // The number of results to return
})
console.log(res.results)
// [
// { score: 0.08821295201778412, id: '551' },
// { score: 0.0800950899720192, id: '296' },
// { score: 0.07794742286205292, id: '43' }
// ]
#include <iostream>
#include <vector>
#include "milvus/MilvusClientV2.h"
auto client = milvus::MilvusClientV2::Create();
auto status = client->Connect(milvus::ConnectParam("http://localhost:19530", "root:Milvus"));
if (!status.IsOk()) {
std::cerr << "Failed to connect: " << status.Message() << std::endl;
return;
}
std::vector<float> queryVector = {
0.35803764F, -0.60234958F, 0.18414013F, -0.26286206F, 0.90294385F
};
auto searchRequest = milvus::SearchRequest()
.WithCollectionName("quick_setup")
.WithAnnsField("vector")
.WithLimit(3)
.WithMetricType(milvus::MetricType::IP)
.AddFloatVector(queryVector);
milvus::SearchResponse searchResponse;
status = client->Search(searchRequest, searchResponse);
if (!status.IsOk()) {
std::cerr << "Search failed: " << status.Message() << std::endl;
return;
}
for (const auto& result : searchResponse.Results().Results()) {
const auto ids = result.Ids().IntIDArray();
for (size_t i = 0; i < result.Scores().size(); ++i) {
std::cout << "id=" << ids[i] << ", score=" << result.Scores()[i] << std::endl;
}
}
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
"collectionName": "quick_setup",
"data": [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
],
"annsField": "vector",
"limit": 3
}'
# {
# "code": 0,
# "data": [
# {
# "distance": 0.08821295201778412,
# "id": 551
# },
# {
# "distance": 0.0800950899720192,
# "id": 296
# },
# {
# "distance": 0.07794742286205292,
# "id": 43
# }
# ]
# }
Milvus는 검색 결과를 쿼리 벡터와의 유사도 점수 기준으로 내림차순 정렬해요. 유사도 점수는 쿼리 벡터까지의 거리라고도 부르며, 그 값의 범위는 사용 중인 metric type에 따라 달라져요.
다음 표는 적용 가능한 metric type과 그에 해당하는 거리 범위를 나열해요.
| Metric Type | 특징 | 거리 범위 |
|---|---|---|
L2 |
값이 작을수록 유사도가 높아요. | [0, ∞) |
IP |
값이 클수록 유사도가 높아요. | [-1, 1] |
COSINE |
값이 클수록 유사도가 높아요. | [-1, 1] |
JACCARD |
값이 작을수록 유사도가 높아요. | [0, 1] |
HAMMING |
값이 작을수록 유사도가 높아요. | [0, dim(vector)] |
대량 벡터 검색 (Bulk-Vector Search)
마찬가지로 검색 요청에 여러 쿼리 벡터를 포함할 수 있어요. Milvus는 쿼리 벡터들에 대해 ANN 검색을 병렬로 수행하고 두 세트의 결과를 반환해요.
# 7. Search with multiple vectors
# 7.1. Prepare query vectors
query_vectors = [
[0.041732933, 0.013779674, -0.027564144, -0.013061441, 0.009748648],
[0.0039737443, 0.003020432, -0.0006188639, 0.03913546, -0.00089768134]
]
# 7.2. Start search
res = client.search(
collection_name="quick_setup",
data=query_vectors,
limit=3,
)
for hits in res:
print("TopK results:")
for hit in hits:
print(hit)
import io.milvus.v2.service.vector.request.SearchReq
import io.milvus.v2.service.vector.request.data.BaseVector;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp
List<BaseVector> queryVectors = Arrays.asList(
new FloatVec(new float[]{0.041732933f, 0.013779674f, -0.027564144f, -0.013061441f, 0.009748648f}),
new FloatVec(new float[]{0.0039737443f, 0.003020432f, -0.0006188639f, 0.03913546f, -0.00089768134f})
);
SearchReq searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(queryVectors)
.topK(3)
.build();
SearchResp searchResp = client.search(searchReq);
queryVectors := []entity.Vector{
entity.FloatVector([]float32{0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592}),
entity.FloatVector([]float32{0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104}),
}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"quick_setup", // collectionName
3, // limit
queryVectors,
).WithConsistencyLevel(entity.ClStrong).
WithANNSField("vector"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
// 7. Search with multiple vectors
const query_vectors = [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]
]
res = await client.search({
collection_name: "quick_setup",
vectors: query_vectors,
limit: 3,
})
console.log(res.results)
std::vector<std::vector<float>> queryVectors = {
{0.041732933F, 0.013779674F, -0.027564144F, -0.013061441F, 0.009748648F},
{0.0039737443F, 0.003020432F, -0.0006188639F, 0.03913546F, -0.00089768134F},
};
auto searchRequest = milvus::SearchRequest()
.WithCollectionName("quick_setup")
.WithAnnsField("vector")
.WithLimit(3)
.WithFloatVectors(std::move(queryVectors));
milvus::SearchResponse searchResponse;
auto status = client->Search(searchRequest, searchResponse);
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
"collectionName": "quick_setup",
"data": [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
[0.19886812562848388, 0.06023560599112088, 0.6976963061752597, 0.2614474506242501, 0.838729485096104]
],
"annsField": "vector",
"limit": 3
}'
기본 키 검색 (Primary-Key Search)
Milvus 2.6.9+와 호환돼요.
쿼리 벡터를 설정하는 대신, 쿼리 벡터가 이미 대상 컬렉션에 존재한다면 기본 키(primary key)를 사용할 수 있어요.
res = client.search(
collection_name="quick_setup",
anns_field="vector",
ids=[551, 296, 43],
limit=3,
search_params={"metric_type": "IP"}
)
for hits in res:
for hit in hits:
print(hit)
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.response.SearchResp;
import java.util.Arrays;
SearchReq searchReq = SearchReq.builder()
.collectionName("quick_setup")
.annsField("vector")
.ids(Arrays.<Object>asList(551L, 296L, 43L))
.limit(3)
.metricType(IndexParam.MetricType.IP)
.build();
SearchResp searchResp = client.search(searchReq);
System.out.println(searchResp.getSearchResults());
const res = await client.search({
collection_name: "quick_setup",
anns_field: "vector",
ids: [551, 296, 43],
limit: 3,
metric_type: "IP",
})
console.log(res.results)
import (
"fmt"
"github.com/milvus-io/milvus/client/v3/column"
"github.com/milvus-io/milvus/client/v3/milvusclient"
)
queryIDs := column.NewColumnInt64("id", []int64{551, 296, 43})
resultSets, err := client.Search(ctx, milvusclient.NewSearchByIDsOption(
"quick_setup", // collectionName
3, // limit
queryIDs,
).WithANNSField("vector").
WithSearchParam("metric_type", "IP"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
auto searchRequest = milvus::SearchRequest()
.WithCollectionName("quick_setup")
.WithAnnsField("vector")
.WithIDs({551, 296, 43})
.WithLimit(3)
.WithMetricType(milvus::MetricType::IP);
# restful
curl -X POST "http://localhost:19530/v2/vectordb/entities/search" \
-H "Content-Type: application/json" \
-H "Request-Timeout: 10" \
-H "Authorization: Bearer ${TOKEN}" \
-d '{
"collectionName": "quick_setup",
"annsField": "vector",
"ids": [551, 296, 43],
"limit": 3,
"searchParams": {
"metric_type": "IP"
}
}'
파티션 내 ANN 검색 (ANN Search in Partition)
컬렉션에 여러 파티션을 만들었다면 검색 범위를 특정 개수의 파티션으로 좁힐 수 있어요. 이 경우 검색 요청에 대상 파티션 이름을 포함해 검색 범위를 지정된 파티션 안으로 제한할 수 있어요. 검색에 포함되는 파티션 수를 줄이면 검색 성능이 향상돼요.
다음 코드 조각은 컬렉션에 PartitionA라는 파티션이 있다고 가정해요.
# 4. Single vector search
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
res = client.search(
collection_name="quick_setup",
partition_names=["partitionA"],
data=[query_vector],
limit=3,
)
for hits in res:
print("TopK results:")
for hit in hits:
print(hit)
import io.milvus.v2.service.vector.request.SearchReq
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp
FloatVec queryVector = new FloatVec(new float[]{0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f});
SearchReq searchReq = SearchReq.builder()
.collectionName("quick_setup")
.partitionNames(Collections.singletonList("partitionA"))
.data(Collections.singletonList(queryVector))
.topK(3)
.build();
SearchResp searchResp = client.search(searchReq);
queryVector := []float32{0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"quick_setup", // collectionName
3, // limit
[]entity.Vector{entity.FloatVector(queryVector)},
).WithConsistencyLevel(entity.ClStrong).
WithPartitions("partitionA").
WithANNSField("vector"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
// 4. Single vector search
var query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
res = await client.search({
collection_name: "quick_setup",
partition_names: ["partitionA"],
data: query_vector,
limit: 3, // The number of results to return
})
console.log(res.results)
auto searchRequest = milvus::SearchRequest()
.WithCollectionName("quick_setup")
.WithAnnsField("vector")
.AddPartitionName("partitionA")
.WithLimit(3)
.AddFloatVector(queryVector);
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
"collectionName": "quick_setup",
"partitionNames": ["partitionA"],
"data": [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
],
"annsField": "vector",
"limit": 3
}'
출력 필드 사용 (Use Output Fields)
기본적으로 검색 결과에서 Milvus는 top-K 벡터 임베딩을 포함한 엔티티의 기본 키 필드 값과 유사도 거리/점수를 포함해요. 벡터 필드와 스칼라 필드를 모두 포함한 대상 필드의 이름을 출력 필드로 검색 요청에 넣으면 검색 결과가 이 엔티티들의 다른 필드 값도 함께 담게 해요.
# 4. Single vector search
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
res = client.search(
collection_name="quick_setup",
data=[query_vector],
limit=3, # The number of results to return
search_params={"metric_type": "IP"},
output_fields=["color"]
)
print(res)
# [
# [
# {
# "id": 551,
# "distance": 0.08821295201778412,
# "entity": {
# "color": "orange_6781"
# }
# },
# {
# "id": 296,
# "distance": 0.0800950899720192,
# "entity": {
# "color": "red_4794"
# }
# },
# {
# "id": 43,
# "distance": 0.07794742286205292,
# "entity": {
# "color": "grey_8510"
# }
# }
# ]
# ]
import io.milvus.v2.service.vector.request.SearchReq
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp
FloatVec queryVector = new FloatVec(new float[]{0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f});
SearchReq searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(Collections.singletonList(queryVector))
.topK(3)
.outputFields(Collections.singletonList("color"))
.build();
SearchResp searchResp = client.search(searchReq);
queryVector := []float32{0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"quick_setup", // collectionName
3, // limit
[]entity.Vector{entity.FloatVector(queryVector)},
).WithConsistencyLevel(entity.ClStrong).
WithANNSField("vector").
WithOutputFields("color"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
// 4. Single vector search
var query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
res = await client.search({
collection_name: "quick_setup",
data: query_vector,
limit: 3, // The number of results to return
output_fields: ["color"]
})
console.log(res.results)
auto searchRequest = milvus::SearchRequest()
.WithCollectionName("quick_setup")
.WithAnnsField("vector")
.WithLimit(3)
.WithMetricType(milvus::MetricType::IP)
.AddOutputField("color")
.AddFloatVector(queryVector);
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
"collectionName": "quick_setup",
"data": [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
],
"annsField": "vector",
"limit": 3,
"outputFields": ["color"]
}'
스칼라 필드로 검색 결과 정렬 (Sort Search Results by Scalar Fields)
Milvus 3.0.x와 호환돼요.
기본적으로 Milvus는 검색 결과를 쿼리 벡터와의 유사도 점수 순서로 정렬해요. 반환된 엔티티가 스칼라 필드 순서를 따르게 하려면 검색 요청에 order_by_fields를 추가하세요.
order_by_fields의 각 항목은 스칼라 필드와 정렬 방향을 지정해요. 오름차순에는 "asc"를, 내림차순에는 "desc"를 사용하세요. order를 생략하면 Milvus는 필드를 오름차순으로 정렬해요.
다음 예시는 검색 결과를 price 기준 낮은 순에서 높은 순으로 정렬해요. 응답에서 필드 값을 확인하려면 정렬 필드를 output_fields에 포함하세요.
res = client.search(
collection_name="product_catalog",
data=query_vectors,
anns_field="embedding",
limit=20,
output_fields=["id", "price", "rating", "category"],
order_by_fields=[
{"field": "price", "order": "asc"}
],
)
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.aggregation.AggDirection;
import io.milvus.v2.service.vector.request.aggregation.OrderByField;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
import java.util.Arrays;
import java.util.Collections;
FloatVec queryVector = new FloatVec(new float[]{0.35803764f, -0.6023496f, 0.18414013f, -0.26286206f, 0.90294385f});
SearchReq searchReq = SearchReq.builder()
.collectionName("product_catalog")
.data(Collections.singletonList(queryVector))
.annsField("embedding")
.limit(20)
.outputFields(Arrays.asList("id", "price", "rating", "category"))
.orderByFields(Collections.singletonList(
OrderByField.builder()
.fieldName("price")
.direction(AggDirection.ASC)
.build()
))
.build();
SearchResp searchResp = client.search(searchReq);
System.out.println(searchResp.getSearchResults());
const res = await client.search({
collection_name: "product_catalog",
data: query_vector,
anns_field: "embedding",
limit: 20,
output_fields: ["id", "price", "rating", "category"],
order_by_fields: [
{ field: "price", order: "asc" }
],
})
console.log(res.results)
import (
"fmt"
"github.com/milvus-io/milvus/client/v3/entity"
"github.com/milvus-io/milvus/client/v3/milvusclient"
)
queryVector := []float32{0.35803764, -0.6023496, 0.18414013, -0.26286206, 0.90294385}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"product_catalog", // collectionName
20, // limit
[]entity.Vector{entity.FloatVector(queryVector)},
).WithANNSField("embedding").
WithOutputFields("id", "price", "rating", "category").
WithSearchParam("order_by_fields", "price:asc"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
auto searchRequest = milvus::SearchRequest()
.WithCollectionName("product_catalog")
.WithAnnsField("embedding")
.WithLimit(20)
.WithOutputFields({"id", "price", "rating", "category"})
.AddOrderByField(milvus::OrderByField(
"price", milvus::AggregationDirection::ASC))
.AddFloatVector(queryVector);
여러 스칼라 필드로도 정렬할 수 있어요. Milvus는 지정한 순서대로 필드를 적용해요. 다음 예시에서 Milvus는 결과를 price 오름차순으로 정렬한 뒤, 같은 price를 가진 엔티티에 대해서는 rating 내림차순으로 정렬해요.
res = client.search(
collection_name="product_catalog",
data=query_vectors,
anns_field="embedding",
limit=20,
output_fields=["id", "price", "rating", "category"],
order_by_fields=[
{"field": "price", "order": "asc"},
{"field": "rating", "order": "desc"},
],
)
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.aggregation.AggDirection;
import io.milvus.v2.service.vector.request.aggregation.OrderByField;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
import java.util.Arrays;
import java.util.Collections;
FloatVec queryVector = new FloatVec(new float[]{0.35803764f, -0.6023496f, 0.18414013f, -0.26286206f, 0.90294385f});
SearchReq searchReq = SearchReq.builder()
.collectionName("product_catalog")
.data(Collections.singletonList(queryVector))
.annsField("embedding")
.limit(20)
.outputFields(Arrays.asList("id", "price", "rating", "category"))
.orderByFields(Arrays.asList(
OrderByField.builder()
.fieldName("price")
.direction(AggDirection.ASC)
.build(),
OrderByField.builder()
.fieldName("rating")
.direction(AggDirection.DESC)
.build()
))
.build();
SearchResp searchResp = client.search(searchReq);
System.out.println(searchResp.getSearchResults());
const res = await client.search({
collection_name: "product_catalog",
data: query_vector,
anns_field: "embedding",
limit: 20,
output_fields: ["id", "price", "rating", "category"],
order_by_fields: [
{ field: "price", order: "asc" },
{ field: "rating", order: "desc" },
],
})
console.log(res.results)
import (
"fmt"
"github.com/milvus-io/milvus/client/v3/entity"
"github.com/milvus-io/milvus/client/v3/milvusclient"
)
queryVector := []float32{0.35803764, -0.6023496, 0.18414013, -0.26286206, 0.90294385}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"product_catalog", // collectionName
20, // limit
[]entity.Vector{entity.FloatVector(queryVector)},
).WithANNSField("embedding").
WithOutputFields("id", "price", "rating", "category").
WithSearchParam("order_by_fields", "price:asc,rating:desc"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
auto searchRequest = milvus::SearchRequest()
.WithCollectionName("product_catalog")
.WithAnnsField("embedding")
.WithLimit(20)
.WithOutputFields({"id", "price", "rating", "category"})
.WithOrderByFields({
milvus::OrderByField("price", milvus::AggregationDirection::ASC),
milvus::OrderByField("rating", milvus::AggregationDirection::DESC),
})
.AddFloatVector(queryVector);
모든 지정된 order-by 필드에서 동일한 값을 가진 엔티티에 대해서는 Milvus는 원래의 유사도 점수 순서를 유지해요.
limit과 offset 사용 (Use Limit and Offset)
검색 요청에 담긴 limit 파라미터가 검색 결과에 포함할 엔티티 수를 결정한다는 걸 눈치챘을 거예요. 이 파라미터는 단일 검색에서 반환할 최대 엔티티 수를 지정하며, 보통 top-K라고 불러요.
페이지네이션 쿼리를 수행하려면 각 쿼리 요청에 Limit과 Offset 파라미터를 담아 반복문으로 여러 Search 요청을 보낼 수 있어요. 구체적으로 Limit 파라미터는 현재 쿼리 결과에 포함하려는 엔티티 수로, Offset은 이미 반환된 엔티티의 총 수로 설정하면 돼요.
아래 표는 한 번에 100개 엔티티를 반환할 때 페이지네이션 쿼리를 위한 Limit과 Offset 파라미터 설정 방법을 보여 줘요.
| 쿼리 | 쿼리당 반환할 엔티티 | 이미 총 반환된 엔티티 |
|---|---|---|
| 1번째 쿼리 | 100 | 0 |
| 2번째 쿼리 | 100 | 100 |
| 3번째 쿼리 | 100 | 200 |
| n번째 쿼리 | 100 | 100 x (n-1) |
단일 ANN 검색에서 limit과 offset의 합은 16,384보다 작아야 한다는 점에 주의하세요.
# 4. Single vector search
query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
res = client.search(
collection_name="quick_setup",
data=[query_vector],
limit=3, # The number of results to return
search_params={
"metric_type": "IP",
"offset": 10 # The records to skip
}
)
import io.milvus.v2.service.vector.request.SearchReq
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp
FloatVec queryVector = new FloatVec(new float[]{0.3580376395471989f, -0.6023495712049978f, 0.18414012509913835f, -0.26286205330961354f, 0.9029438446296592f});
SearchReq searchReq = SearchReq.builder()
.collectionName("quick_setup")
.data(Collections.singletonList(queryVector))
.topK(3)
.offset(10)
.build();
SearchResp searchResp = client.search(searchReq);
queryVector := []float32{0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"quick_setup", // collectionName
3, // limit
[]entity.Vector{entity.FloatVector(queryVector)},
).WithConsistencyLevel(entity.ClStrong).
WithANNSField("vector").
WithOffset(10))
if err != nil {
fmt.Println(err.Error())
// handle error
}
// 4. Single vector search
var query_vector = [0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592],
res = await client.search({
collection_name: "quick_setup",
data: query_vector,
limit: 3, // The number of results to return,
offset: 10 // The record to skip.
})
auto searchRequest = milvus::SearchRequest()
.WithCollectionName("quick_setup")
.WithAnnsField("vector")
.WithLimit(3)
.WithOffset(10)
.AddFloatVector(queryVector);
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
"collectionName": "quick_setup",
"data": [
[0.3580376395471989, -0.6023495712049978, 0.18414012509913835, -0.26286205330961354, 0.9029438446296592]
],
"annsField": "vector",
"limit": 3,
"offset": 10
}'
검색을 위한 타임존 일시 설정 (Temporarily set a timezone for a search)
컬렉션에 TIMESTAMPTZ 필드가 있다면, 검색 호출에서 timezone 파라미터를 설정해 단일 연산에 대해 데이터베이스나 컬렉션의 기본 타임존을 일시적으로 재정의할 수 있어요. 이는 연산 중 TIMESTAMPTZ 값이 표시되고 비교되는 방식을 제어해요.
timezone 값은 유효한 IANA 타임존 식별자(예: Asia/Shanghai, America/Chicago, UTC)여야 해요. TIMESTAMPTZ 필드 사용 방법은 TIMESTAMPTZ Field를 참고하세요.
아래 예시는 검색 연산에 타임존을 일시적으로 설정하는 방법을 보여 줘요.
res = client.search(
collection_name="quick_setup",
anns_field="vector",
data=[query_vector],
limit=3,
search_params={"metric_type": "IP"},
timezone="America/Havana",
)
import io.milvus.v2.common.IndexParam;
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.FloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
import java.util.Collections;
FloatVec queryVector = new FloatVec(new float[]{0.35803764f, -0.6023496f, 0.18414013f, -0.26286206f, 0.90294385f});
SearchReq searchReq = SearchReq.builder()
.collectionName("quick_setup")
.annsField("vector")
.data(Collections.singletonList(queryVector))
.limit(3)
.metricType(IndexParam.MetricType.IP)
.timezone("America/Havana")
.build();
SearchResp searchResp = client.search(searchReq);
System.out.println(searchResp.getSearchResults());
const res = await client.search({
collection_name: "quick_setup",
anns_field: "vector",
data: query_vector,
limit: 3,
metric_type: "IP",
params: { timezone: "America/Havana" },
})
console.log(res.results)
import (
"fmt"
"github.com/milvus-io/milvus/client/v3/entity"
"github.com/milvus-io/milvus/client/v3/milvusclient"
)
queryVector := []float32{0.35803764, -0.6023496, 0.18414013, -0.26286206, 0.90294385}
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"quick_setup", // collectionName
3, // limit
[]entity.Vector{entity.FloatVector(queryVector)},
).WithANNSField("vector").
WithSearchParam("metric_type", "IP").
WithOutputFields("event_time").
WithSearchParam("timezone", "America/Havana"))
if err != nil {
fmt.Println(err.Error())
// handle error
}
auto searchRequest = milvus::SearchRequest()
.WithCollectionName("quick_setup")
.WithAnnsField("vector")
.WithLimit(3)
.WithMetricType(milvus::MetricType::IP)
.AddOutputField("event_time")
.WithTimezone("America/Havana")
.AddFloatVector(queryVector);
# restful
export QUERY_VECTOR='[0.1, 0.2, 0.3, 0.4]'
curl -X POST "http://localhost:19530/v2/vectordb/entities/search" \
-H "Content-Type: application/json" \
-H "Request-Timeout: 10" \
-d '{
"collectionName": "quick_setup",
"annsField": "vector",
"data": ["$QUERY_VECTOR"],
"limit": 3,
"searchParams": {
"metric_type": "IP",
"timezone": "America/Havana"
}
}'
ANN 검색 향상 (Enhancing ANN Search)
AUTOINDEX는 ANN 검색의 학습 곡선을 상당히 낮춰줘요. 하지만 top-K가 커질수록 검색 결과가 항상 정확하지 않을 수 있어요. Milvus는 검색 범위를 줄이고, 검색 결과 관련성을 높이며, 검색 결과를 다양화함으로써 다음과 같은 검색 향상 기능을 제공해요.
-
필터링 검색 (Filtered Search) 검색 요청에 필터링 조건을 포함해 ANN 검색을 수행하기 전에 메타데이터 필터링을 하게 하면, 검색 범위를 전체 컬렉션에서 지정된 필터링 조건과 일치하는 엔티티로만 줄일 수 있어요. 메타데이터 필터링과 필터링 조건에 대한 자세한 내용은 Filtered Search, Filtering Explained 및 관련 주제를 참고하세요.
-
범위 검색 (Range Search) 반환되는 엔티티의 거리나 점수를 특정 범위 안으로 제한해 검색 결과 관련성을 높일 수 있어요. Milvus에서 범위 검색은 쿼리 벡터와 가장 유사한 벡터 임베딩을 중심으로 두 개의 동심원을 그려요. 검색 요청은 두 원의 반지름을 지정하고, Milvus는 바깥 원 안에 속하지만 안쪽 원에는 속하지 않는 모든 벡터 임베딩을 반환해요. 범위 검색에 대한 자세한 내용은 Range Search를 참고하세요.
-
그룹핑 검색 (Grouping Search) 반환된 엔티티가 특정 필드에서 같은 값을 가진다면 검색 결과가 벡터 공간의 모든 벡터 임베딩 분포를 대표하지 못할 수 있어요. 검색 결과를 다양화하려면 그룹핑 검색 사용을 고려해 보세요. 그룹핑 검색에 대한 자세한 내용은 Grouping Search를 참고하세요.
-
하이브리드 검색 (Hybrid Search) 컬렉션에는 서로 다른 임베딩 모델로 생성된 벡터 임베딩을 저장하기 위한 여러 벡터 필드를 포함할 수 있어요. 이렇게 하면 하이브리드 검색으로 이 벡터 필드들의 검색 결과를 재순위화해 리콜율을 높일 수 있어요. 하이브리드 검색에 대한 자세한 내용은 Hybrid Search를 참고하세요.
-
검색 반복자 (Search Iterator) 단일 ANN 검색은 최대 16,384개의 엔티티를 반환해요. 단일 검색에서 더 많은 엔티티를 반환해야 한다면 검색 반복자 사용을 고려해 보세요. 검색 반복자에 대한 자세한 내용은 Search Iterator를 참고하세요.
-
전문 검색 (Full-Text Search) 전문 검색은 텍스트 데이터셋에서 특정 용어나 구문을 포함한 문서를 검색한 뒤 관련성에 따라 결과를 순위화하는 기능이에요. 이 기능은 정확한 용어를 놓칠 수 있는 시맨틱 검색의 한계를 극복해 가장 정확하고 맥락에 맞는 결과를 받을 수 있게 해줘요. 또한 원시 텍스트 입력을 받아 텍스트 데이터를 자동으로 스파스 임베딩으로 변환하므로 벡터 임베딩을 수동으로 생성할 필요 없이 벡터 검색을 단순화해요. 전문 검색에 대한 자세한 내용은 Full Text Search를 참고하세요.
-
텍스트 매치 (Text Match) Milvus의 키워드 매치는 특정 용어를 기반으로 정확한 문서 검색을 가능하게 해요. 이 기능은 주로 필터링 검색에서 특정 조건을 충족시키기 위해 사용되며, 스칼라 필터링을 포함해 쿼리 결과를 정제하고 스칼라 기준을 충족하는 벡터 내에서 유사도 검색을 허용할 수 있어요. 키워드 매치에 대한 자세한 내용은 Keyword Match를 참고하세요.
-
파티션 키 사용 (Use Partition Key) 메타데이터 필터링에 여러 스칼라 필드를 포함하고 다소 복잡한 필터링 조건을 사용하면 검색 효율에 영향을 줄 수 있어요. 스칼라 필드를 파티션 키로 설정하고 검색 요청에 파티션 키가 포함된 필터링 조건을 사용하면 지정된 파티션 키 값에 해당하는 파티션 안으로 검색 범위를 제한하는 데 도움이 돼요. 파티션 키에 대한 자세한 내용은 Use Partition Key를 참고하세요.
-
mmap 사용 (Use mmap) mmap 설정에 대한 자세한 내용은 Use mmap을 참고하세요.
-
클러스터링 컴팩션 (Clustering Compaction) 클러스터링 컴팩션에 대한 자세한 내용은 Clustering Compaction을 참고하세요.
-
재순위화 사용 (Use reranking) 검색 결과 관련성을 향상시키기 위해 ranker를 사용하는 방법은 Decay Ranker Overview와 Model Ranker Overview를 참고하세요.
더 알아보기 (Learn more)
- Filtered Search — 메타데이터 필터링
- Hybrid Search — 다중 벡터 필드 하이브리드 검색
- TIMESTAMPTZ Field — TIMESTAMPTZ 필드 사용
- Milvus 공식 문서 — 검색·쿼리 관련 자료