모니터링 결과 검색(Retrieval)의 62%가 실패하고 있었다. 버그는 한 컬럼 내의 두 가지 서로 다른 척도(Scale)였다.
요약
하이브리드 검색 시스템에서 RRF 점수와 재순위화(Reranker) 점수가 동일한 컬럼에 저장되어 발생한 모니터링 오류 사례를 다룹니다. 서로 다른 척도를 가진 점수를 구분 없이 집계할 때 발생하는 가짜 장애(False Incident)의 위험성을 경고합니다.
핵심 포인트
- RRF 점수는 유사도가 아닌 순위 정보를 나타내므로 기존 임계값 적용 시 주의가 필요함
- 서로 다른 점수 체계(Scoring regimes)는 반드시 별도의 라벨이나 컬럼으로 구분하여 저장해야 함
- 척도에 영향을 받지 않는 가장 확실한 실패 신호는 검색 결과의 공백(Emptiness)임
- 알람 시스템 구축 시 검증된 정상 쿼리를 통해 임계값의 적절성을 반드시 확인해야 함
공포
개인 메모에 대한 하이브리드 검색(Hybrid retrieval) — 벡터 유사도(Vector similarity) + BM25, Reciprocal Rank Fusion (RRF)으로 융합, 일부 티어에서는 선택적인 교차 인코더(Cross-encoder) 재순위화(Rerank)를 사용합니다. 모든 검색은 품질 모니터링을 위해 top_score를 기록합니다. 기록된 10,706건의 검색을 분석하면서, 저는 당연한 임계값(Threshold)인 top_score < 0.3 = 약한 검색(Weak retrieval)을 적용했습니다. 결과는 다음과 같았습니다: 62%의 "실패", "평균 점수 0.017로 100% 실패"하는 수십 명의 사용자, 그리고 공포스러운 전월 대비 "성능 저하(Degradation)". "100% 실패"한 사용자 중 한 명은 천 건의 검색을 수행한 유료 고객이었습니다. 저는 이미 장애 대응(Incident mode) 모드에 절반쯤 진입해 있었습니다.
단서
정확한 엔티티 이름(Entity name)을 검색하는 것 — 즉, 반드시 성공해야 하는 검색 — 의 top_score가 0.0426으로 기록되었습니다. 그리고 "실패" 중인 사용자들은 모두 평균 0.016–0.021을 기록하고 있었습니다. 그때 깨달았습니다: RRF 점수는 표준 k=60일 때 1/(k + rank)입니다. 최상위 순위(Top rank) = 1/60 ≈ 0.0167입니다. 저의 "재앙적인" 사용자들은 실패하고 있었던 것이 아닙니다 — 그들의 최상위 결과는 거의 매번 1순위(Rank-1)였습니다. 평균 0.017은 완벽한 RRF 검색이 보여주는 모습입니다.
실제로 일어난 일
재순위화기(Reranker)를 거치는 요청은 코사인 스타일의 점수(0–1 척도, 0.3 이상 = 좋음)를 기록합니다. 가공되지 않은 RRF 경로를 따르는 요청은 융합 점수(0.016–0.05 척도, 0.017 = 매우 우수)를 기록합니다. 이 두 가지가 척도 태그(Scale tag) 없이 동일한 top_score 컬럼에 저장되었습니다. 해당 컬럼에 대한 모든 집계 — 평균, z-점수(z-scores), 저의 실패 임계값, 심지어 상태 모니터링 크론(Cron) 작업까지 — 모두 사과와 궤도 속도를 평균 내고 있었던 셈입니다. "전월 대비 성능 저하"는 단지 더 많은 트래픽이 하이브리드로 이동함에 따라 RRF 경로의 비중이 커진 것뿐이었습니다.
척도 교정(Scale-correction) 후 살아남은 데이터: 실제 실패(결과 없음)는 9–13%였으며, 이는 주로 에이전트가 말 그대로 비어 있는 스토어를 쿼리하고 있던 두 계정 때문이었습니다 — 이는 실제 문제였지만, "검색 기능이 고장 났다"는 것과는 완전히 다른 문제였습니다.
일반화할 수 있는 교훈
- 결합된 순위 점수(Fused ranking score)는 유사도(Similarity)가 아닙니다. RRF의 출력값은 순위 정보(Rank information)를 나타내는 것이지, 신뢰도(Confidence)를 나타내는 것이 아닙니다. 점수를 결합하는 순간, 그 점수의 절대값은 대시보드가 해석하는 의미와는 더 이상 일치하지 않게 됩니다.
- 서로 다른 점수 체계(Scoring regimes)에서 나온 점수들을 하나의 라벨 없는 컬럼에 절대 함께 저장하지 마세요.
score_kind(또는 쓰기 시점에 계산된 척도 인지 가능한 품질 라벨)를 기록하십시오. 분석 시점에 추측하는 방식이야말로 새벽 3시에 잘못된 장애(False incidents) 알람을 받게 되는 원인입니다. - 유일하게 척도에 영향을 받지 않는 실패 신호는 공백(Emptiness)입니다. 결과가 0개라는 것은 모든 경로에서 동일한 의미를 갖습니다. 의구심이 든다면 임계값(Thresholds)이 아니라 0의 개수를 세십시오.
- 알람을 믿기 전에 검증된 정상 쿼리(Known-good query)를 통해 알람을 검증하십시오. "0.04점을 기록한" 단 하나의 완전 일치(Exact-match) 검색 결과가 저 자신에게 호출(Paging)을 보내는 상황을 막아주었습니다.
모두가 물려받는 기본값인 k=60은 Cormack, Clarke & Buettcher (2009)의 논문, "Reciprocal Rank Fusion outperforms Condorcet and individual rank learning methods"에서 유래되었습니다. 이 함정은 리랭커(Reranker)와 퓨전 스코어링(Fusion scoring)을 혼합하는 모든 RAG 스택에 적용됩니다. 점수 컬럼을 검색(grep)하여 1/60 근처에 이봉 분포(Bimodal cluster)가 있는지 확인해 보십시오.
저는 AI 에이전트를 위한 메모리 레이어인 Mengram을 구축하고 있습니다. 모든 검색과 함께 기록되는 척도 인지 가능한 품질 라벨(Scale-aware quality label)이라는 해결책은 공개 커밋 히스토리에 남아 있습니다. 질문은 언제든 환영합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기