Published on

Pinecone·Milvus 하이브리드검색 튜닝으로 MRR 올리기

Authors

서치 품질을 이야기할 때 “정확도”라는 단어는 너무 넓습니다. RAG/검색 시스템에서는 특히 MRR(Mean Reciprocal Rank) 이 체감 품질과 직결됩니다. 사용자가 원하는 문서가 1위에 오면 1.0, 2위면 0.5, 3위면 0.333...처럼 빠르게 떨어지기 때문입니다.

Pinecone과 Milvus 모두 벡터 검색을 잘하지만, 실제 서비스에서 MRR을 올리려면 대개 하이브리드(벡터 + 키워드/메타데이터) 검색재랭킹 이 필수입니다. 이 글은 “어떤 파라미터를 어떻게 만져야 MRR이 오르는지”를 중심으로, Pinecone·Milvus에서 공통으로 적용 가능한 튜닝 루프를 정리합니다.

성능(지연) 최적화가 필요하다면 프론트/백엔드 병목도 함께 보세요: Next.js LCP 4초→1초 - RSC·이미지·폰트 최적화, Spring Boot 3 가상 스레드에서 JDBC 지연 줄이기

하이브리드 검색이 MRR에 강한 이유

하이브리드 검색은 대개 다음 실패 케이스를 줄입니다.

  • 동의어/의미 매칭: 벡터가 강함(예: “해지” vs “구독 취소”).
  • 정확한 토큰/코드/약어: 키워드가 강함(예: 에러 코드, 함수명, SKU, 정책 번호).
  • 최신성/권한/카테고리: 메타데이터 필터가 강함(예: tenant_id, visibility, updated_at).

MRR을 올리는 핵심은 “정답 문서가 Top k 후보군에 들어올 확률(리콜)”을 올린 다음, “정답을 1위로 끌어올리는 재정렬(랭킹)”을 강하게 거는 것입니다.

MRR 튜닝의 기본 루프(반드시 이 순서)

  1. 오프라인 평가셋 만들기: 쿼리, 정답 문서(또는 정답 chunk), 허용되는 정답 집합.
  2. 후보군 생성 단계 튜닝: 벡터/키워드/필터/하이브리드 가중치로 Top k 후보를 뽑아 리콜을 확보.
  3. 재랭킹 단계 튜닝: Cross-Encoder 또는 LLM rerank로 Top k를 재정렬해 MRR을 끌어올림.
  4. 에러 분석: MRR이 떨어지는 쿼리 유형을 분류(약어, 숫자, 긴 질문, 다의어 등).
  5. 파라미터/인덱스/스키마 재설계: 임베딩 모델, chunk 전략, 필드 설계까지 되돌아감.

이 루프를 무시하고 “인덱스 파라미터만” 만지면, 대개 지연만 줄고 MRR은 안 오릅니다.

1) 평가셋: MRR을 제대로 측정하는 최소 조건

MRR을 올리려면 먼저 “정답이 무엇인지”가 명확해야 합니다.

  • 문서 단위 정답인지, chunk 단위 정답인지 결정
  • 동일 문서의 여러 chunk가 정답이면 “정답 집합”으로 처리
  • 쿼리 로그 기반이면 클릭/체류시간 등으로 약지도 라벨 생성

아래는 파이썬으로 MRR을 계산하는 최소 구현입니다.

from typing import List, Set

def mrr_at_k(ranked_lists: List[List[str]], gold_sets: List[Set[str]], k: int = 10) -> float:
    assert len(ranked_lists) == len(gold_sets)
    rr_sum = 0.0
    for ranked, gold in zip(ranked_lists, gold_sets):
        rr = 0.0
        for i, doc_id in enumerate(ranked[:k], start=1):
            if doc_id in gold:
                rr = 1.0 / i
                break
        rr_sum += rr
    return rr_sum / len(ranked_lists)

평가 시에는 MRR만 보지 말고, 후보군 단계에서는 Recall@k도 같이 보세요. 후보군에 정답이 없으면 rerank가 아무리 좋아도 MRR이 오르지 않습니다.

2) 후보군 생성: Pinecone·Milvus 공통 튜닝 포인트

(A) kefSearch/nprobe를 “MRR 관점”으로 잡기

ANN 검색은 근사치라서, 후보군 리콜이 조금만 흔들려도 MRR이 크게 출렁입니다.

  • Pinecone: 인덱스 타입/파드 구성에 따라 내부 탐색 파라미터가 추상화되어 있지만, 실무적으로는 Top k를 키우고, 필터/네임스페이스로 탐색 공간을 줄이는 방식이 가장 안전합니다.
  • Milvus: IVF 계열이면 nprobe, HNSW면 ef/efSearch가 후보군 리콜에 직접 영향.

권장 접근:

  • 1차 후보군 k_candidates를 50~200으로 올려 리콜을 확보
  • rerank는 Top 10~50 범위에서 수행
  • 지연이 터지면 k를 줄이기 전에 필터/파티션/스키마로 탐색 공간을 줄임

(B) 필터는 “정확도”가 아니라 “랭킹 안정성”을 만든다

MRR이 낮은 시스템은 의외로 “정답이 11위”가 아니라 “정답이 아예 후보군에 없음”이 많습니다. 권한/테넌트/언어/카테고리 필터가 느슨하면, 벡터 유사도가 높은 다른 테넌트 문서가 섞이면서 정답이 밀립니다.

  • 필수 필터: tenant_id, visibility, lang
  • 자주 효과 있는 필터: product, doc_type, updated_at(최근 문서 가중)

Milvus에서는 파티션 설계가 중요합니다. 예를 들어 tenant_id가 크고 격리가 필요하면 파티션(또는 컬렉션 분리)로 탐색 공간을 구조적으로 줄이는 게 MRR과 지연 모두에 유리합니다.

(C) 하이브리드 스코어 결합: “가중치”보다 “정규화”가 먼저

벡터 점수와 BM25(또는 sparse) 점수는 스케일이 다릅니다. 가중치만 섞으면 쿼리 길이/희귀 토큰 여부에 따라 한쪽이 과도하게 지배합니다.

실전에서 가장 흔한 개선은 다음 순서입니다.

  1. 각 스코어를 쿼리 단위로 정규화(예: min-max, z-score, rank-based)
  2. 결합 함수 선택(가중합, reciprocal rank fusion 등)
  3. 가중치 튜닝

특히 RRF(Reciprocal Rank Fusion) 는 스코어 스케일 문제를 피해가면서 MRR을 안정적으로 끌어올리는 경우가 많습니다.

def rrf_fusion(rank_a: list[str], rank_b: list[str], k: int = 60) -> list[str]:
    # k는 RRF 상수(일반적으로 10~100). 값이 클수록 상위 순위 편향이 완만해짐.
    score = {}
    for r, doc_id in enumerate(rank_a, start=1):
        score[doc_id] = score.get(doc_id, 0.0) + 1.0 / (k + r)
    for r, doc_id in enumerate(rank_b, start=1):
        score[doc_id] = score.get(doc_id, 0.0) + 1.0 / (k + r)
    return [d for d, _ in sorted(score.items(), key=lambda x: x[1], reverse=True)]

Pinecone에서 dense+sparse를 함께 쓸 때도, “alpha로 섞기”만 하기보다 RRF처럼 순위 기반 결합을 실험해보면 MRR이 더 잘 오르는 케이스가 있습니다(특히 약어/코드 쿼리).

3) Pinecone에서의 하이브리드 튜닝 체크리스트

(A) 네임스페이스와 메타데이터로 탐색 공간을 줄여라

  • 테넌트/프로덕트/환경을 네임스페이스로 분리 가능하면 분리
  • 분리가 어렵다면 메타데이터 필터를 강제

탐색 공간이 줄면 후보군 k를 키워도 지연이 덜 늘고, 결과적으로 rerank에 더 많은 후보를 공급해 MRR이 오릅니다.

(B) sparse 벡터는 “토큰 전략”이 MRR을 좌우한다

키워드 성능은 단순히 BM25 엔진의 문제가 아니라, 토큰화/정규화가 절반입니다.

  • 숫자/하이픈/스네이크 케이스 보존(예: E11000, user_id)
  • 한글은 형태소 분석기를 쓰되, 도메인 사전(상품명/약어)을 반드시 추가
  • 불용어 제거는 보수적으로(질문형 문장에서는 불용어가 의미를 가질 때가 있음)

(C) chunk 설계: “정답이 1위가 되기 쉬운 단위”로 쪼개기

MRR은 1위를 보상합니다. 즉, chunk가 너무 크면 정답 근거가 포함되어도 유사도가 분산되어 1위를 놓칠 수 있습니다.

  • 문서형 지식베이스: 200500 토큰 수준 chunk + 1020% overlap부터 시작
  • API 레퍼런스/정책: 섹션/조항 단위로 구조적 chunking
  • 표/리스트: 행 단위로 분해하거나, 요약 텍스트를 별도 필드로 추가

4) Milvus에서의 하이브리드 튜닝 체크리스트

Milvus는 인덱스/서치 파라미터를 비교적 직접 제어합니다. 그래서 “MRR이 안 오를 때 어디를 만져야 하는지”가 더 명확합니다.

(A) 인덱스 선택과 파라미터

  • HNSW: M, efConstruction, 검색 시 efSearch
  • IVF_FLAT/IVF_PQ: nlist, 검색 시 nprobe

경험칙:

  • MRR이 낮고 지연 여유가 있으면 efSearch 또는 nprobe를 먼저 올려 후보군 리콜을 확보
  • 메모리/디스크 제약이 있으면 PQ를 쓰되, rerank를 반드시 붙여 손실을 보정

(B) 스칼라 필드 + 인덱스 + 파티션

Milvus에서 필터가 느리면, 필터가 후보군 단계에 제대로 반영되지 않아 “정답이 밀리는” 현상이 생깁니다.

  • 필터 자주 쓰는 스칼라 필드는 인덱스 고려
  • tenant_id/lang처럼 강한 분리 축은 파티션으로 설계

(C) 하이브리드 구현: 두 번 검색 후 결합이 현실적인 해법

Milvus 자체의 조합 방식은 버전/구성에 따라 다르지만, 실무적으로는

  • 벡터 검색 Top k1
  • 키워드/필터 기반 검색 Top k2(외부 엔진 또는 스칼라 조건)
  • RRF 또는 정규화 가중합으로 결합
  • 최종 Top k를 rerank

이 파이프라인이 MRR을 가장 예측 가능하게 올립니다.

5) rerank가 MRR을 “진짜로” 올린다

후보군 단계는 리콜 싸움이고, MRR은 결국 1위를 만드는 싸움입니다. rerank는 그 1위를 만드는 가장 강력한 도구입니다.

(A) Cross-Encoder rerank

  • 장점: 정밀한 의미 매칭, 짧은 후보군에서 매우 강함
  • 단점: 비용/지연

Top 50 후보를 rerank하고 Top 10만 반환하는 구성이 흔합니다.

# 의사 코드
candidates = hybrid_retrieve(query, k_candidates=100)
reranked = cross_encoder_rerank(query, candidates)  # returns list sorted by relevance
return reranked[:10]

(B) LLM rerank(설명가능성까지 원하면)

LLM으로 rerank하면 근거 스니펫 추출, 이유 생성까지 같이 할 수 있지만, 스키마 검증/출력 안정성이 중요합니다. API 스키마 에러가 잦다면 OpenAI Responses API 422 스키마 검증 에러 해결 가이드처럼 “출력 계약”을 먼저 안정화하세요.

6) MRR이 안 오를 때의 전형적인 원인 7가지

  1. 정답이 후보군에 없음: k_candidates 부족, efSearch/nprobe 낮음, 필터 누락
  2. chunk가 너무 큼/작음: 의미가 분산되거나 문맥이 끊김
  3. 임베딩 모델 미스매치: 도메인 용어에 약함, 다국어 혼재
  4. 하이브리드 결합 스케일 문제: 정규화 없이 가중치만 조정
  5. 키워드 토큰화 실패: 약어/숫자/특수문자 처리 부재
  6. 메타데이터 품질 부족: updated_at, doc_type 같은 랭킹 힌트가 없음
  7. 평가셋 라벨이 부정확: 정답 문서가 여러 개인데 단일 정답으로 고정

7) 실전 파라미터 튜닝 플랜(추천 순서)

아래 순서대로 실험하면 “MRR이 오르는 방향”으로 수렴하기 쉽습니다.

  1. 후보군 k_candidates를 100으로 올리고, rerank Top 20을 붙여 MRR 상한을 확인
  2. 필터/네임스페이스/파티션으로 탐색 공간을 줄여 지연을 회수
  3. 하이브리드 결합을 RRF로 바꿔 스코어 스케일 문제를 제거
  4. 토큰화/도메인 사전으로 sparse 품질을 올려 약어/코드 쿼리 MRR을 끌어올림
  5. chunk 전략을 재설계(섹션 기반 + 오버랩)하고, 정답이 1위가 되기 쉬운 단위로 조정
  6. 마지막에 ANN 파라미터(efSearch, nprobe)로 지연-리콜 트레이드오프를 미세 조정

8) 운영 관점: MRR을 지속적으로 올리는 계측

  • 쿼리 유형별 MRR 대시보드(숫자 포함, 길이, 언어, 카테고리)
  • “0점 쿼리”(Top k에 정답 없음) 비율 추적
  • rerank 전/후 MRR 비교로 rerank 기여도 측정

또한 배포/롤백이 잦은 검색 시스템에서는 실험 플래그와 버전 관리가 중요합니다. 모델/인덱스 교체를 안전하게 운영하려면 Triton Inference Server 모델 핫스왑 배포·롤백 실전 같은 운영 패턴을 참고해 “검색 파이프라인도” 점진 롤아웃이 가능하게 설계하는 것이 좋습니다.

마무리

Pinecone·Milvus에서 하이브리드 검색을 튜닝해 MRR을 올리는 핵심은 단순합니다.

  • 후보군 단계에서 정답을 안정적으로 Top k 안에 넣고
  • 스코어 결합은 정규화 또는 RRF 로 흔들림을 줄이며
  • rerank로 정답을 1위로 끌어올리는 구조를 만든다

인덱스 파라미터는 마지막 미세 조정일 뿐이고, 실제 MRR을 바꾸는 레버는 “후보군 리콜 + 결합 안정성 + rerank”입니다. 이 3가지를 실험 루프로 굴리면, 같은 데이터/같은 모델에서도 MRR이 체감될 정도로 올라갑니다.