
‘리랭커를 넣을까 말까’ — RAG 파이프라인에 검색 단계를 붙이다 보면 누구나 한 번은 검색창에 쳐보는 질문입니다. 답부터 드리면, 같은 테스트셋으로 정확도 지표(nDCG@10, Recall@k)가 얼마나 오르는지와 P95 지연시간이 서비스 허용치를 넘는지를 동시에 재본 다음 결정해야 합니다. 둘 중 하나만 보고 판단하면 품질은 좋아졌는데 응답이 느려져 이탈률이 오르거나, 반대로 속도만 지키다 답변 품질이 그대로인 채 리랭커를 뺀 채로 끝나는 경우가 흔합니다.
리랭커가 검색 품질을 얼마나 올리는지부터 재는 법
리랭커는 1차 검색(벡터 검색이나 BM25)이 뽑아온 후보 문서를 크로스 인코더로 다시 채점해 순서를 바꾸는 단계입니다. 대표 모델로는 BAAI가 공개한 bge-reranker-large 모델 카드가 있고, sentence-transformers 라이브러리의 CrossEncoder 클래스로 바로 불러와 쓸 수 있습니다.
품질을 재려면 정답 문서가 표시된 평가셋이 먼저 있어야 합니다. 질의마다 “정답으로 인정할 청크 ID”를 미리 라벨링해두고, 리랭커 적용 전후로 nDCG@10과 Recall@10을 각각 계산해 차이를 비교합니다. 평가셋 없이 “체감상 나아진 것 같다”는 판단은 다음 모델 교체나 청킹 방식 변경 때 원인을 추적할 수 없게 만듭니다.
지연시간 증가분, 어디서 재야 정확할까요?
리랭커 지연시간은 전체 응답 시간이 아니라 “재정렬 구간만” 따로 떼어 재야 합니다. 1차 검색, 리랭킹, LLM 생성이 순차로 이어지는 파이프라인이라면 각 구간에 time.perf_counter() 같은 타이머를 따로 걸어 병목이 어디인지 구분해야 합니다.
평균값(P50)만 보고 판단하면 안 됩니다. 사용자 체감은 꼬리 지연시간에서 갈리기 때문에 P95, 가능하면 P99까지 측정하고, 후보 개수(top-k)와 배치 크기를 바꿔가며 어떤 조건에서 얼마나 늘어나는지 곡선으로 남겨두는 편이 나중에 튜닝할 때 훨씬 유용합니다.

아래는 재정렬 구간 지연시간과 개수(top-k)의 관계를 확인하는 코드 예시입니다.
import time
import numpy as np
from sentence_transformers import CrossEncoder
# 공식 모델 카드: https://huggingface.co/BAAI/bge-reranker-large
reranker = CrossEncoder("BAAI/bge-reranker-large", max_length=512)
def rerank(query, candidates, top_k=10):
pairs = [[query, c["text"]] for c in candidates]
start = time.perf_counter()
scores = reranker.predict(pairs)
elapsed_ms = (time.perf_counter() - start) * 1000
order = np.argsort(scores)[::-1][:top_k]
reranked = [candidates[i] for i in order]
return reranked, elapsed_ms
# top-k(재정렬 대상 후보 수)를 바꿔가며 지연시간을 기록합니다
for k in [10, 20, 50]:
_, latency_ms = rerank(query, candidates_pool[:k], top_k=10)
print(f"top_k={k:>3} rerank_latency={latency_ms:.1f} ms")
같은 GPU 한 장에서 후보 개수를 10 → 50으로 늘리면 재정렬 시간이 거의 선형으로 늘어나는 걸 확인할 수 있습니다. CPU 환경에서는 배치 크기와 코어 수 영향이 커서 같은 후보 개수라도 몇 배 더 걸릴 수 있으므로, 반드시 실제 서비스 인프라에서 직접 측정해야 합니다.
품질과 지연시간을 한 표에 놓고 비교해 보세요
아래 표는 측정 방법을 보여주기 위한 예시입니다. 데이터셋, 모델, 하드웨어에 따라 실제 값은 크게 달라지므로 숫자 자체를 그대로 인용하지 말고, 같은 형식으로 자신의 환경에서 직접 채워 넣는 용도로 참고해 주세요.
| 항목 | 리랭커 미적용 | 리랭커 적용 (bge-reranker-large, top-50→top-10) |
|---|---|---|
| nDCG@10 | 0.62 | 0.71 |
| Recall@10 | 0.58 | 0.66 |
| P50 지연시간 | 180ms | 260ms |
| P95 지연시간 | 340ms | 520ms |
이 표에서 봐야 할 건 절대 수치가 아니라 “품질 향상분 대비 지연시간 증가분이 서비스가 감당할 수 있는 범위인가”입니다. 예를 들어 챗봇 SLA가 P95 500ms라면 위 예시의 520ms는 기준을 살짝 넘기는 셈이라, top-k를 줄이거나 더 가벼운 모델(cross-encoder/ms-marco-MiniLM 계열)로 바꾸는 조정이 필요해집니다.
리랭커가 오히려 손해인 경우

1차 검색의 Recall@50이 이미 낮다면 리랭커를 붙여도 효과가 크지 않습니다. 애초에 후보군에 정답 문서가 안 들어와 있으면 재정렬은 있는 후보 안에서 순서만 바꿀 뿐이라, 이 경우엔 임베딩 모델 교체나 청킹 전략 재검토가 먼저입니다.
실시간 음성 응대처럼 응답 지연에 극도로 민감한 서비스도 마찬가지입니다. 크로스 인코더 특성상 후보 하나하나를 질의와 함께 다시 인코딩해야 해서 임베딩 유사도 검색보다 원천적으로 느리고, 이 구조 자체가 정보 검색 분야의 위키백과 ‘정보 검색’ 문서에서 설명하는 2단계 검색(초기 검색 후 재순위화) 방식의 본질적인 트레이드오프입니다. Cohere Rerank처럼 API로 제공되는 리랭커를 쓰는 경우엔 호출당 과금과 네트워크 왕복 시간까지 더해지므로 트래픽이 많은 서비스에서는 비용도 함께 계산해야 합니다.
Top-K를 줄이면 정말 지연시간이 줄어드나요?
줄어드는 게 맞지만, 무조건 줄이는 게 답은 아닙니다. 재정렬 대상을 50개에서 20개로 줄이면 크로스 인코더가 채점할 문서 수가 줄어드는 만큼 지연시간도 비례해서 낮아집니다.
다만 top-k를 줄이는 만큼 1차 검색 단계에서 정답 문서가 그 안에 들어와 있어야 한다는 전제가 필요합니다. Recall@20이 Recall@50보다 눈에 띄게 낮다면, top-k를 줄인 대가로 애초에 재정렬할 정답 후보가 빠져버려 품질이 떨어질 수 있습니다. 그래서 top-k를 정할 땐 지연시간 곡선과 함께 Recall@k 곡선도 같이 그려서 두 곡선이 만나는 지점을 찾는 편이 안전합니다.
이 기준으로 리랭커 도입 여부를 정해 보세요
정리하면 판단 순서는 이렇습니다. 먼저 라벨링된 평가셋으로 리랭커 적용 전후 nDCG@10과 Recall@k 차이를 재고, 동시에 재정렬 구간만 따로 P50·P95 지연시간을 측정합니다. 그다음 서비스가 정해둔 응답 시간 기준과 비교해, 품질 향상분이 그 대가를 치를 만한지 판단하면 됩니다.
바로 실행할 수 있는 다음 단계는 전체 트래픽에 적용하기 전에 일부 요청에만 리랭커를 켜는 섀도 테스트(shadow traffic)로 먼저 돌려보는 것입니다. 실제 사용자 지표(클릭률, 재질문 비율)까지 확인한 뒤 전면 적용 여부를 결정하면, 나중에 되돌리는 비용을 줄일 수 있습니다.
