Qdrant에서의 하이브리드 검색
Qdrant에서의 하이브리드 검색 (Hybrid Search in Qdrant)
검색 결과가 그럴듯해 보여도 틀릴 수 있어요. 밀집(dense) 검색은 주제는 맞는 문서를 돌려주지만, 쿼리에 복사된 정확한 식별자는 놓칠 수 있어요. 희소(sparse) 검색은 쿼리가 코퍼스에서 쓰지 않는 용어로 문서를 표현할 때 관련 문서를 놓칠 수 있죠. 어느 쪽이든 여러분의 로그에는 '성공한 쿼리'만 기록돼요.
하이브리드 검색은 같은 쿼리에 대해 밀집 검색과 희소 검색을 모두 실행한 뒤, 그 결과 목록을 합쳐요. 밀집 검색은 의미론적 유사도를 더해서, 패러프레이즈(다른 표현의 서로 다른 문장)가 함께 랭크될 수 있게 해요. 희소 검색은 정확한 단어와 식별자를 위한 가중치 기반 용어 매칭을 더해 주죠.
어느 한쪽 리트리버 단독과 비교하면, 하이브리드 검색은 저장·인덱싱·쿼리 작업을 추가해요. 그 이득이 비용만 한지 추측하지 말고 측정해 보세요.
출처: 공식문서
밀집과 희소 검색은 서로 다른 것을 놓쳐요
밀집 검색은 쿼리와 각 문서를 임베딩한 뒤 벡터 유사도로 문서를 랭크해요. 모델이 패러프레이즈를 서로 가까이 둘 수 있지만, 의미가 비슷한 문서 사이에서는 정확한 문자열의 영향력이 약해질 수 있어요.
희소 검색은 텍스트를 가중치 붙은 용어로 표현하고 쿼리와 문서의 겹침을 점수로 매겨요. BM25는 그 가중치를 용어 빈도, 역문서 빈도, 문서 길이로부터 정해요. 모델 추론은 필요 없어요.
상품 검색 예제가 그 차이를 구체적으로 보여 줘요. 각 쿼리에 대해 한 리트리버는 관련 상품을 먼저 랭크하지만, 다른 리트리버는 무관한 상품을 먼저 랭크해요.
| Query | Dense Retrieval | Sparse Retrieval |
|---|---|---|
| french molding | french curves 6'' h x 6'' w x 1'' d rosette applique (Relevant) | french bread mold toast tray non-stick tray baking tray (Irrelevant) |
| wayfair comforters | wayfair basics comforter set (Relevant) | wayfair basics peva shower curtain liner (Irrelevant) |
| bathroom vanity knobs | carran 30'' single bathroom vanity set (Irrelevant) | damask mushroom knob (Relevant) |
| farmhouse cabinet | rustic storage cabinet (Irrelevant) | farmhouse 2 door accent cabinet (Relevant) |
“french molding”의 경우, 희소 검색이 “french”와 “mold”라는 용어를 따라 잘못된 상품으로 향해요. “bathroom vanity knobs”의 경우 밀집 검색이 올바른 카테고리를 찾는 반면, 희소 검색은 “knobs”를 따라 관련 상품으로 향해요.
학습된 희소 모델은 희소 쪽이 매칭하는 대상을 바꿔요. miniCOIL은 BM25의 용어 매칭을 유지하지만 각 용어를 맥락에 따라 재가중하므로, 스포츠 목록의 “bat”과 야생동물 안내서의 “bat”이 더 이상 가중치를 공유하지 않아요. SPLADE는 텍스트에서 쓰지 않은 관련 용어를 추가해요. 이는 동의어를 복구하고 희소 검색을 밀집 리트리버가 이미 커버하는 영역에 더 가깝게 만들어 줘요. 쿼리 시점에 모델이 필요 없는 BM25부터 시작하고, 채택하기 전에 학습된 모델을 그것과 대조해 측정해 보세요.
퓨전이 두 랭킹을 하나로 합쳐요
Qdrant에서 prefetch는 검색을 실행하고 후보를 메인 쿼리에 넘겨요. 하이브리드 검색은 하나의 prefetch를 밀집 검색에, 다른 하나를 희소 검색에 사용해요. 퓨전은 그들의 후보 목록을 하나의 정렬로 결합하죠.
밀집 유사도와 BM25 점수는 서로 다른 스케일을 사용해요. 밀집 유사도는 경계가 있는 반면, BM25의 크기는 얼마나 많은 쿼리 용어가 매칭되고 코퍼스에서 얼마나 드문가에 달려 있어요. 원시 점수에 고정된 가중치를 주면 한 쿼리에서는 균형을 이루지만 다른 쿼리에서는 BM25가 지배하게 할 수 있어요. 어느 한 원시 점수 가중치로도 두 쿼리 사이에서 같은 균형을 유지할 수 없어요.

밀집 스케일은 비슷하게 유지되지만 BM25 스케일은 쿼리마다 움직인다.
RRF는 점수 크기를 버려서 스케일 불일치를 피해요. DBSF는 쿼리마다 각 점수 분포를 정규화하죠.
Reciprocal Rank Fusion, 줄여서 RRF는 각 문서가 각 목록에서 어디에 위치했는지만 읽어요. 그래서 코사인 유사도 0.7과 BM25 점수 12.4를 값끼리 직접 비교하지 않고 결합할 수 있어요. Cormack, Clarke, Buettcher가 2009년에 이 방법을 소개했고, 지금도 랭크된 목록을 결합하는 표준 방식으로 남아 있어요.
Distribution-Based Score Fusion, 줄여서 DBSF는 각 목록을 평균 점수와 점수 퍼짐으로 재조정한 뒤 재조정된 점수를 더해요. 이는 점수 간극의 크기를 보존하므로, 한 리트리버의 강한 리드가 최종 랭킹에 영향을 줄 수 있어요.
어느 방법도 보편적으로 이기지는 못해요. RRF부터 시작하고, DBSF를 같은 라벨링된 쿼리와 비교해 보세요.
Formula Query는 다른 목적을 serve해요. 검색 점수와 payload 값에 대한 표현식으로 검색된 후보를 재스코어링하죠. 예를 들어 수식 하나로 최근 상품이나 재고 있는 상품을 부스트할 수 있어요. 정규화되지 않은 밀집·BM25 점수를 직접 비교 가능하게 만들지는 않아요. Custom scoring에서 표현식 문법을 다룹니다.
퓨전은 재정렬만 해요. 두 prefetch가 돌려준 합집합 위에서 작동하므로, 둘 중 어느 것도 찾지 못한 문서는 결과 어디에도 나타날 수 없어요.
관련 문서가 prefetch 컷오프 아래에 있다면, 한쪽이나 양쪽 prefetch limit을 늘리면 퓨전이 그걸 노출시킬 수 있어요. limit을 키우면 검색 작업이 늘어나고, 더 깊이에서 리트리버가 여전히 그 문서를 놓친다면 도움이 안 돼요. Candidate depth에서 limit을 테스트하는 법을, 하이브리드 쿼리 문서에서 prefetch가 퓨전을 먹여 살리는 방식을 다룹니다.

창백한 문서들은 결코 검색되지 않았다. 정답이 그중 하나라면, 어떤 퓨전 방법도 그곳에 닿지 못한다.
두 번째 리트리버의 비용
이어지는 설정은 밀집 전용 컬렉션에서 시작해 BM25를 두 번째 리트리버로 추가해요. 즉 포인트당 희소 벡터 하나, 두 번째 인덱스, 그리고 모든 쿼리에서 검색 한 번을 추가한다는 뜻이에요. 한 번에 하나의 요청을 서빙하는 컨테이너 하나에서, 그 추가 검색은 중앙값 쿼리 지연 시간을 0.60~1.47 ms 올렸어요. 여러분 자신의 동시성과 샤드 레이아웃 아래에서 비용을 측정해 보세요.
기존 밀집 전용 컬렉션에 희소 벡터를 추가하려면 새 컬렉션과 전체 재인덱스가 필요해요. 벡터 구성은 컬렉션 생성 시점에 고정되기 때문이죠.
새 컬렉션은 두 벡터 타입을 모두 선언해요. 희소 벡터에는 IDF 수정자가 필요해요. 흔한 용어보다 드문 용어에 더 큰 가중치를 주죠. 그게 없으면 흔한 단어가 부품 번호만큼의 비중을 가질 수 있어요.
from qdrant_client import QdrantClient, models
client = QdrantClient(
url="https://YOUR-CLUSTER.cloud.qdrant.io",
api_key="<your-api-key>",
)
client.create_collection(
collection_name="products",
vectors_config={
# size matches your dense model's output dimensions.
"dense": models.VectorParams(size=384, distance=models.Distance.COSINE)
},
sparse_vectors_config={
"bm25": models.SparseVectorParams(modifier=models.Modifier.IDF)
},
)
하이브리드 검색 문서에서 지원되는 모든 언어의 텍스트 인덱싱과 BM25 희소 벡터 생성 방법을 다룹니다.
from your_embedding_models import dense_embed, sparse_embed
query_text = "Samsung Galaxy S24 Ultra 512GB"
results = client.query_points(
collection_name="products",
prefetch=[
models.Prefetch(
# The same query, embedded for the dense retriever.
query=dense_embed(query_text),
using="dense",
limit=100,
),
models.Prefetch(
# The same query, embedded for the sparse retriever.
query=sparse_embed(query_text),
using="bm25",
limit=100,
),
],
query=models.FusionQuery(fusion=models.Fusion.RRF),
# Results returned to the caller.
limit=10,
)
using은 각 prefetch에 대해 네임드 벡터를 선택하고, FusionQuery는 두 목록을 합쳐요. 두 prefetch limit 모두 100에서 시작하죠.
도움이 되는지 측정하기
다섯 개의 공개 데이터셋에서 기본 RRF가 더 강한 개별 리트리버를 네 곳에서 이겼어요.

DBPedia-entity가 예외다: 퓨전이 밀집 검색보다 낮게 점수 낸다.
같은 라벨링된 쿼리를 밀집 검색, 희소 검색, 퓨전으로 각각 실행하세요. 모델과 후보 limit은 그대로 두고, 각 실행을 nDCG@10으로 점수 매겨요. 이 메트릭은 상위에 가까운 관련 문서에 더 많은 점수를 줍니다.
먼저 퓨전이 두 리트리버를 모두 이기는지 확인하세요. 랭킹이 갈리는 쿼리들을 검토한 뒤, 승패가 여러분 워크로드의 중요한 쿼리 유형 주변에 몰려 있는지 살펴보세요.
한 리트리버가 퓨전이 너무 낮게 랭크한 관련 결과를 찾는다면, 퓨전 방법이나 가중치를 튜닝하세요. How to Tune Hybrid Search가 그 설정들을 다뤄요. 두 리트리버가 모두 결과를 놓친다면 퓨전이 승격시킬 후보가 없어요.
승리한 설정을 보류된 쿼리에서 재확인하세요. 라벨링된 집합 구축에서 쿼리 선택과 보류 평가를 다룹니다.
자연어 전용 워크로드의 많은 부분은 밀집 검색으로 이미 커버될 수 있지만, 쿼리 형태만으로는 하이브리드 검색이 도움이 될지 알 수 없어요.
관련성 이득이 측정된 인덱싱·지연 비용을 정당화할 때만 희소 리트리버를 유지하세요.
다음에 무엇을 테스트할까
- 단계를 더 추가하기. 멀티스테이지 쿼리는 싼 표현으로 검색하고 비싼 표현으로 재스코어링해요. Cross-encoder 리랭킹은 쿼리와 청크를 모델에 함께 넣죠. When Is a Reranker Worth It?이 그 접근을 튜닝된 첫 단계와 비교해요.
- 가지고 있는 것을 튜닝하기. 퓨전 방법, RRF 상수, 리트리버별 가중치는 모두 단계 추가 없이 관련성을 움직일 수 있어요. How to Tune Hybrid Search가 같은 다섯 데이터셋 전체에서 각각을 측정해요.
더 알아보기 (Learn more)
- How to Tune Hybrid Search in Qdrant — 퓨전 튜닝 가이드
- Candidate Depth — prefetch limit 테스트
- Before Tuning a Qdrant Collection — 라벨링된 집합 구축
- When Is a Reranker Worth It?