블로그 RAG 검색 정확도를 측정했더니, 고칠 건 정확도가 아니었습니다
제 블로그에는 하이브리드 검색이 붙어 있습니다. 벡터 검색과 키워드 검색을 섞은, 요즘 흔히 RAG라고 부르는 그 구조입니다. 붙여둔 지 반년이 지났는데, 정작 "이게 정확하긴 한가"를 한 번도 제대로 확인한 적이 없다는 생각이 들었습니다.
그래서 추측으로 답하지 않기로 하고 실제로 측정해봤습니다. 결론부터 적으면 정확도 자체는 나쁘지 않았습니다. 다만 측정을 하고 나서야 보인 것이 둘 있었는데, 애석하게도 둘 다 정확도 이야기가 아니었습니다.
어떻게 설계돼 있었나
먼저 지금 구조를 짧게 정리하면 이렇습니다. 검색어가 들어오면 두 갈래로 흩어집니다.
검색어
├─ 벡터 검색 : gemini-embedding-001로 임베딩(3072차원)
│ → MongoDB Atlas $vectorSearch
└─ 키워드 검색 : 제목·요약·태그를 부분 문자열로 채점
↓
RRF로 두 결과를 합산 (k=60, 벡터 0.7 / 키워드 0.3)
↓
상위 10개 반환
RRF(Reciprocal Rank Fusion)는 두 검색 결과의 점수가 아니라 순위를 섞는 방식입니다. 각 리스트에서 몇 등이었는지만 보고 1/(k+순위)로 환산해 더하기 때문에, 척도가 다른 벡터 유사도와 키워드 점수를 억지로 같은 자로 재지 않아도 됩니다. 이 선택 자체는 지금도 옳다고 생각합니다.
글은 발행될 때 제목 + 본문을 한 덩어리로 임베딩해 벡터 하나로 저장합니다. 이 "한 덩어리"가 나중에 문제가 됩니다만, 그 이야기는 다음 편에 하겠습니다. 아무튼 216편의 글이 전부 이 벡터를 갖고 있었습니다.
정확도를 어떻게 쟀나
정확도를 잰다는 건 결국 "사람이 이 검색어를 쳤을 때, 위에 뜬 글이 실제로 그 사람이 열어볼 만한 글인가"를 따지는 일입니다. 정답표가 따로 있는 게 아니니, 실제 검색 로그에서 의도가 분명한 검색어를 추린 뒤 각 검색어의 상위 결과를 하나씩 직접 판정했습니다.
먼저 솔직히 적어둘 것이 있습니다. 표본이 작습니다. 반년간 쌓인 검색이 애초에 많지 않았고, 그중 타이핑하다 만 파편이나 키보드 테스트(asd 같은 것)를 걷어내면 의도가 분명한 검색어는 열몇 개 남짓이었습니다. 그러니 아래 숫자는 정밀한 지표라기보다 경향으로 읽는 게 맞습니다.
덧붙여 판정한 사람도 저 하나입니다. 관련 있다 없다를 가르는 기준이 제 머릿속에만 있었으니, 같은 검색어 세트를 다른 사람이 재면 숫자는 달라질 수 있습니다.
정확도는 나쁘지 않았습니다
의도가 분명한 검색어 16개 기준으로, 맨 위에 뜬 글이 관련 있는 비율(Precision@1)은 88%였습니다. 완성형 검색어만 추리면 91%까지 올라갔습니다. 표본이 작다는 단서를 붙이더라도, 적어도 "위에 뜬 글이 엉뚱하다"는 상황은 거의 없었다는 뜻입니다.
특히 벡터 검색이 제 몫을 하는 게 눈에 보였습니다.
| 검색어 | 결과 | 키워드 검색만으로는? |
|---|---|---|
엠시피 |
MCP 관련 글을 정확히 매칭 | 한 글자도 안 겹쳐 매칭 불가 |
두존 (오타) |
'두쫀쿠' 글이 1위 | 철자가 달라 매칭 불가 |
프로메테우스 |
Prometheus 글이 1위 | 표기가 달라 매칭 불가 |
음차와 오타는 키워드 검색이 절대 잡지 못합니다. 이건 애초에 벡터 검색을 붙인 이유 그 자체였고, 실제로 작동하고 있었습니다. 완전히 빗나간 검색어는 딱 하나, 미세먼지였는데 — 이건 랭킹이 틀린 게 아니라 그 주제로 쓴 글이 아예 없어서였습니다. 검색이 못 찾은 게 아니라 찾을 것이 없었던 것입니다. 이 구분은 중요합니다. 전자는 검색을 고쳐야 하는 문제지만, 후자는 글을 더 써야 하는 문제이기 때문입니다.
정작 놀란 건, 검색하는 사람이 없었습니다
정확도는 그렇게 확인이 됐는데, 로그를 들여다보다 더 크게 걸린 것은 다른 쪽이었습니다. 검색이 반년 동안 총 38번 일어났습니다. 그중 71%가 기능을 붙인 첫 이틀에 몰려 있었고 — 다름 아닌 제가 만들면서 테스트하던 흔적입니다 — 최근 석 달간의 실제 검색은 단 한 건이었습니다.
돌이켜 생각해보면 이건 뼈아픈 지적입니다. 저는 반년 내내 "검색 정확도"를 신경 쓸 대상이라고 막연히 여기고 있었는데, 정작 그 검색창까지 도달하는 방문자가 거의 없었습니다. 고쳐야 할 게 정확도가 아니었다는 첫 번째 이유가 이것입니다. 검색 품질을 아무리 끌어올려도, 검색하는 사람이 없으면 체감되는 변화는 0입니다.
그래서 임베딩의 진짜 쓰임새를 다시 봤습니다. 검색창은 거의 안 쓰이지만, 같은 임베딩으로 계산되는 글 하단의 '관련 글'은 모든 글 페이지에 노출됩니다. 임베딩이 실제로 독자와 만나는 자리는 검색 결과가 아니라 이쪽이었습니다. 측정 대상을 처음부터 잘못 잡고 있었던 셈입니다.
그리고 결함 네 개가 보였습니다
측정의 진짜 소득은 정확도 숫자가 아니라, 재는 과정에서 드러난 파이프라인의 숨은 결함이었습니다. 정확도가 88%나 나온 건 이 결함들이 짧은 검색어에서는 티가 안 났기 때문이지, 결함이 없어서가 아니었습니다. 네 개였습니다.
- RRF 점수가 사실상 두 값에만 고정돼 있었습니다. 검색 결과의 최고 점수가 늘
0.0148아니면0.0164였는데, 알고 보니 순위에 없는 문서에까지 점수를 얹는 비표준 계산이 섞여 있었습니다. 품질을 가늠하는 신호로 쓸 수 없는 숫자였던 것입니다. - 벡터 검색에 언어·숨김 필터가 빠져 있었습니다. 발행 필터만 있고 언어나 숨김 여부는 거르지 않아, 숨긴 글이 검색에 새어 나올 수 있는 구멍이 있었습니다.
- 발행한 글을 고쳐도, 임베딩은 옛날 내용 그대로였습니다. 임베딩은 최초 발행 때 딱 한 번만 만들어지고, 이후 본문을 아무리 고쳐도 갱신되지 않았습니다. 오래 다듬은 글일수록 검색이 낡은 내용을 보고 있었습니다.
- 그리고 가장 컸던 것 — 1만 자를 임베딩하는 줄 알았는데, 실제로는 2천 토큰에서 잘리고 있었습니다. 코드는 분명히 1만 자에서 자르도록 돼 있었는데, 정작 API는 그보다 훨씬 앞에서 조용히 나머지를 버리고 있었습니다. 글의 3분의 1 이상이 뒷부분을 검색에서 잃고 있었던 셈입니다.
마지막 결함은 처음엔 믿기지 않아서, 같은 글을 길이를 늘려가며 여러 번 임베딩해 벡터를 직접 비교해봤습니다. 그 결과가 꽤 결정적이었는데 — 그 이야기는 다음 편에서 따로 하겠습니다.
측정하지 않았다면 저는 아직도 "검색 잘 되네"라고 여기고 있었을 겁니다. 정확도 88%라는 숫자는 그 자체로는 안심스러운 값이지만, 그 숫자를 만드는 과정을 뜯어보지 않았다면 결함 네 개도, 검색 트래픽이 없다는 사실도 영영 몰랐을 것입니다. 결국 중요한 건 좋은 숫자가 아니라 그 숫자가 어디서 나왔는지를 들여다보는 일이라는 생각이 들었습니다. 다음 편에서는 네 결함 중 가장 흥미로웠던 임베딩 잘림 문제를, 그 벡터 비교 실험과 함께 자세히 다뤄보겠습니다.
관련 글
개인 블로그에 AI 검색 달기 (1) - 왜 하이브리드 검색인가
블로그 검색 기능을 개선하면서 키워드 검색의 한계를 느꼈습니다. 벡터 검색과 키워드 검색을 결합한 하이브리드 검색을 선택한 이유와 아키텍처 설계 과정을 공유합니다.
개인 블로그에 AI 검색 달기 (2) - MongoDB Atlas Vector Search 구현
MongoDB Atlas Vector Search 인덱스 설정부터 NestJS에서 하이브리드 검색을 구현하는 과정. $vectorSearch의 null 필터 제한사항과 RRF 알고리즘, 유사도 임계값 튜닝까지.
RAG는 꼭 필요한 것인가
임베딩과 벡터 검색이 매력적이긴 하지만, 키워드 검색만으로도 충분한 경우가 많지 않을까. 검색·관련 글 영역에 임베딩을 직접 쓰고 있는 입장에서, RAG가 정말 필요한 자리를 다시 생각해 본 짧은 회고.