> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sifi.life/llms.txt
> Use this file to discover all available pages before exploring further.

# 하이브리드 검색 & RRF — "의미 + 키워드, 둘 다 잡기"

> 이 문서는 **[RAG](/blog/guides/rag/overview)** 섹션의 일부입니다.

> Dense 검색은 의미를 이해하지만 정확한 키워드를 놓치고, BM25는 키워드를 정확히 찾지만 동의어를 인식하지 못합니다. 하이브리드 검색은 두 방식의 결과를 결합하여 서로의 약점을 보완합니다. 이 페이지에서는 왜 단순 결합이 어려운지, RRF가 이를 어떻게 해결하는지를 구체적으로 다룹니다.

***

## 왜 둘 다 필요한가: 구체적 실패 사례

### Dense만 사용할 때의 실패

```
질문: "제품번호 ABC-123의 스펙 시트"
문서: "ABC-123 제품 사양서 — 무게: 2.3kg, 전압: 220V..."

Dense 검색 결과:
1위: "제품 사양 관리 가이드" (cosine: 0.89)     ← "스펙 시트"의 의미는 이해
2위: "제품 카탈로그 개요" (cosine: 0.87)         ← 관련은 있지만 ABC-123이 아님
3위: "ABC-123 제품 사양서" (cosine: 0.85)       ← 정작 원하는 문서가 3위
```

Dense 검색은 "스펙 시트"와 "사양서"의 의미적 유사성은 잘 잡지만, **"ABC-123"이라는 고유 식별자의 정확한 매칭** 에는 약합니다.

### Sparse(BM25)만 사용할 때의 실패

```
질문: "고장이 자주 나는 부품을 교체하는 절차"
문서: "결함률이 높은 컴포넌트의 교환 프로세스"

BM25 검색 결과:
1위: "부품 고장 이력 조회" (BM25: 12.3)         ← "고장", "부품" 키워드 매칭
2위: "자주 묻는 질문 (FAQ)" (BM25: 8.7)          ← "자주" 키워드 매칭
→ 정작 원하는 문서는 검색되지 않음!             ← "결함률", "컴포넌트", "교환"은 매칭 실패
```

BM25는 **"고장"="결함**", "**부품"="컴포넌트**", "**교체"="교환"** 같은 동의어 관계를 전혀 이해하지 못합니다.

> **주의**
> **실무에서 가장 흔한 실수**: "Dense 검색이면 충분하다"고 판단하고 BM25를 생략하는 것입니다. 고유명사, 제품 코드, 오류 메시지 등 **정확한 문자열 매칭이 중요한 쿼리** 가 전체의 20-40%를 차지하는 경우가 많습니다.

***

## 점수 결합의 어려움: 왜 단순히 더할 수 없는가

두 검색 결과를 합치려면 점수를 비교해야 합니다. 하지만:

```
BM25 점수 범위:  0 ~ 25+ (이론적으로 상한 없음)
Cosine 점수 범위: 0 ~ 1.0

문서 A: BM25=15.2, Cosine=0.72
문서 B: BM25=3.1, Cosine=0.95

단순 합산: A=15.92, B=4.05 → A 승리 (BM25가 압도)
가중 합산 (0.5:0.5): A=7.96, B=1.99 → 여전히 A 승리
```

BM25 점수의 절대값이 훨씬 크기 때문에, 어떤 가중치를 사용해도 BM25가 결과를 지배하게 됩니다. 점수 정규화(Min-Max, Z-score)를 시도할 수 있지만, 쿼리마다 점수 분포가 달라 **안정적인 정규화가 어렵습니다**.

구체적으로 왜 어려운지 보면: Min-Max 정규화(`(x - min) / (max - min)`)를 적용하려면 해당 쿼리의 최솟값/최댓값을 알아야 합니다. 하지만 쿼리 "Delta Lake"는 BM25 점수가 0~~8 범위로 좁고, "the data"는 0~~25 범위로 넓습니다. 같은 BM25 점수 5.0이라도 전자에서는 상위권이고 후자에서는 하위권입니다. Z-score 정규화(`(x - μ) / σ`)도 마찬가지로, 점수 분포의 평균(μ)과 표준편차(σ)가 쿼리마다 크게 달라 **쿼리 간 비교가 불안정** 합니다. 이것이 점수 기반 결합이 실무에서 잘 동작하지 않는 근본적 이유입니다.

***

## RRF (Reciprocal Rank Fusion) 상세

### 핵심 아이디어: 점수 대신 순위(rank)를 사용

RRF는 점수의 절대값을 무시하고, 각 검색 방식에서의 **순위** 만을 사용합니다. 순위는 1, 2, 3, ... 으로 항상 같은 스케일이므로 결합이 자연스럽습니다.

### 수식

```
RRF_score(d) = Σ 1/(k + rank_i(d))

- d: 문서
- k: 상수 (기본값 60)
- rank_i(d): i번째 검색 방식에서 문서 d의 순위 (1부터 시작)
- Σ: 모든 검색 방식에 대해 합산
```

### k=60의 의미

k 값은 **상위 순위와 하위 순위의 차이를 얼마나 부드럽게 할 것인지** 조절합니다:

```
k=60일 때:
1위의 기여: 1/(60+1) = 0.01639
2위의 기여: 1/(60+2) = 0.01613  → 1위와 차이: 1.6%
5위의 기여: 1/(60+5) = 0.01538  → 1위와 차이: 6.2%
50위의 기여: 1/(60+50) = 0.00909 → 1위와 차이: 44.5%

k=1일 때:
1위의 기여: 1/(1+1) = 0.500
2위의 기여: 1/(1+2) = 0.333  → 1위와 차이: 33.4%
→ 순위 차이가 극적으로 반영됨 (너무 공격적)
```

k가 클수록 순위 간 차이가 완만해지고, 작을수록 1위에 대한 보상이 극적으로 커집니다.

**k=60은 왜 60인가?** 이 값은 2009년 Cormack 등의 논문("Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods")에서 제안되었으며, 다양한 TREC(Text REtrieval Conference) 벤치마크에서 k=10\~100 범위를 실험한 결과 60 근처에서 가장 안정적인 성능을 보였습니다. 직관적으로 설명하면, k=60은 **상위 60위까지의 순위 차이를 적당히 부드럽게 만들면서도 하위권과의 차별은 유지** 하는 균형점입니다. k가 너무 작으면(예: 1) 1위와 2위의 점수 차이가 너무 커서 한 검색 방식의 1위가 결과를 지배하고, k가 너무 크면(예: 1000) 모든 순위의 점수가 거의 같아져 순위 정보가 사실상 무시됩니다.

### 예시: 어느 문서가 최종 상위?

```
검색 결과:
- 문서 X: BM25 1위, Dense 5위
- 문서 Y: BM25 3위, Dense 2위
- 문서 Z: BM25 2위, Dense 10위

RRF 계산 (k=60):
문서 X: 1/(60+1) + 1/(60+5) = 0.01639 + 0.01538 = 0.03177
문서 Y: 1/(60+3) + 1/(60+2) = 0.01587 + 0.01613 = 0.03200 ← 최고점!
문서 Z: 1/(60+2) + 1/(60+10) = 0.01613 + 0.01429 = 0.03042

최종 순위: Y > X > Z
```

문서 Y가 승리합니다. BM25에서 1위인 문서 X보다, **양쪽에서 고르게 상위에 있는** 문서 Y가 더 높은 점수를 받습니다. 이것이 RRF의 핵심 철학입니다: **한쪽에서만 극단적으로 높은 것보다, 양쪽에서 골고루 인정받는 것이 더 낫다.**

> **참고**
> **비유**: 두 명의 심사위원이 독립적으로 순위를 매긴 후, 양쪽 모두에게 높은 평가를 받은 후보를 최종 선발합니다. 한 심사위원에게만 극찬받은 후보보다, 두 심사위원 모두에게 좋은 평가를 받은 후보가 선발됩니다.

***

## Databricks Vector Search의 하이브리드 검색

Databricks Vector Search는 `query_type="hybrid"` 설정으로 하이브리드 검색을 지원합니다:

```python theme={null}
results = vs_index.similarity_search(
    query_text="제품번호 ABC-123 스펙",
    columns=["content", "doc_id"],
    num_results=5,
    query_type="hybrid"  # Dense + BM25 + RRF 자동 적용
)
```

### 현재 제약 사항과 해결

| 제약             | 설명                              | 해결 방법                           |
| -------------- | ------------------------------- | ------------------------------- |
| BM25 가중치 조절 불가 | Dense:Sparse 비율을 직접 조절할 수 없음    | 현재는 기본 RRF에 의존                  |
| 한국어 토큰화        | 내장 BM25가 공백 기반 토큰화 사용           | Kiwi 전처리 후 토큰화된 텍스트를 별도 컬럼으로 저장 |
| 필터 + 하이브리드     | 필터 조건과 하이브리드를 동시에 적용 시 성능 이슈 가능 | 필터 범위를 좁게 설정                    |

> **참고**
> **실무 팁**: 한국어 환경에서 Databricks VS의 하이브리드 검색 BM25 품질을 높이려면, 원본 텍스트와 함께 Kiwi로 형태소 분석한 텍스트를 별도 컬럼에 저장하고, 해당 컬럼을 BM25 소스로 지정하는 방법을 고려하세요. 자세한 내용은 [한국어 RAG 최적화](../korean-nlp)를 참고하세요.

***

> **다음**: 검색 결과를 더 정밀하게 정렬하고 싶다면? → [Re-ranking](reranking)
