dense 벡터만 쓰는 Semantic Search, sparse 벡터와 RRF는 언제 섞어야 할까?
검색엔진이 쓰는 dense 벡터와 sparse 벡터가 각각 무엇을 잘 찾고 무엇을 놓치는지, 두 검색 결과를 순위로 합치는 RRF가 어떻게 계산되는지 설명합니다. 의류 상품 1만 개를 작은 임베딩 모델로 검색한 실측에서 모델번호 검색어의 상위 10개 안에 정답을 찾은 비율은 dense 0.8, 하이브리드 1.0이었습니다.

검색엔진이 쓰는 dense 벡터와 sparse 벡터가 각각 무엇을 잘 찾고 무엇을 놓치는지, 두 검색 결과를 순위로 합치는 RRF가 어떻게 계산되는지 설명합니다. 의류 상품 1만 개를 작은 임베딩 모델로 검색한 실측에서 모델번호 검색어의 상위 10개 안에 정답을 찾은 비율은 dense 0.8, 하이브리드 1.0이었습니다.

이 사이트 글 88편을 청크 4,369개로 나누고, 청크마다 Claude Code `claude -p`(Opus 5.5)로 맥락 문장을 만들어 붙인 뒤 bge-m3, e5, BM25, 하이브리드 검색의 실패율을 질문 100개로 쟀습니다. 제목을 이미 붙인 청크 대비 주지표(실패율@5)는 질문 80개로 구분할 수 없었고, 생성 비용(API 단가 환산 약 94달러)도 함께 확인합니다.
이 사이트 글 76편을 벡터 검색한 상위 후보를 bge-m3의 토큰별 멀티벡터(ColBERT 방식) 점수와 로컬 LLM 채점으로 다시 점수 매겨 정답 문단이 몇 위로 올라오는지 질문 60개로 비교합니다. 1차 후보가 정하는 상한과 멀티벡터 저장 공간, LLM 채점 시간도 함께 잽니다.
이 사이트에 발행된 글 72편에서 뽑은 질문을 로컬 LLM에 묻고, 함께 넣는 문단 수(0, 1, 3, 5, 10개)와 정답 문단의 위치(앞, 가운데, 끝)만 바꿔 가며 답을 글 속 숫자로 자동 채점합니다. 틀린 답이 어느 문단에서 왔는지도 함께 확인합니다.
청크 크기 글의 질문 30개로 같은 검색 설정을 다시 재 보면, 문서가 조금 바뀌기만 해도 질문 두 개 차이가 뒤집힙니다. recall@k, MRR 같은 점수 차이가 우연인지 짝 비교와 부트스트랩 신뢰구간으로 확인하고, 가르는 데 질문이 몇 개쯤 필요한지 봅니다.
이 사이트의 글 일부를 고치고 지운 뒤 다시 색인해 보면서, 청크 id를 어떻게 정하느냐에 따라 옛 청크가 검색에 남는 모습을 Qdrant에서 확인합니다. 내용 해시로 바뀐 청크만 다시 임베딩하는 방법과, 전체를 다시 임베딩할 때와 걸린 시간을 비교합니다.
같은 문장 벡터 3만 5천여 개를 PostgreSQL 17의 pgvector와 Qdrant에 넣고 색인 시간과 크기, 정확한 검색 대비 recall@10, 지연을 나란히 잽니다. 벡터 검색에 WHERE와 JOIN을 붙이면 LIMIT보다 적게 나오는 이유와 반복 스캔으로 고치는 방법도 확인합니다.
이 사이트의 글을 문장 단위로 나눈 벡터 4만여 개를 Qdrant에 넣고, HNSW의 m, ef_construct, ef를 바꿔 가며 정확한 검색 결과와 얼마나 같은지(recall@10)와 지연 시간, 그래프 파일 크기, 색인 시간을 잽니다. 기본 설정에서도 가장 가까운 결과를 놓치는 경우를 확인하고, 어느 값을 먼저 만져야 하는지 실측으로 살펴봅니다.
이 사이트의 글을 문장 2만 7,700개로 나눠 Qdrant에 넣고, 필터가 남기는 비율을 50%에서 0.06%까지 줄여 가며 근사 검색의 recall@10을 정확한 검색과 비교합니다. payload 인덱스의 필터용 추가 연결이 있을 때와 없을 때의 차이도 확인합니다.
질문을 임베딩해 이전 질문과 충분히 가까우면 저장해 둔 답을 돌려주는 시맨틱 캐시에서, 같은 뜻 질문과 말만 비슷한 다른 뜻 질문으로 기준값별 적중과 잘못된 적중을 잽니다. Redis의 RedisVL 기본값을 그대로 쓰면 무슨 일이 생기는지도 확인합니다.
다국어 임베딩 모델 세 개(multilingual-e5-small, multilingual-e5-large, bge-m3)로 이 사이트의 글을 색인하고 같은 질문 30개로 검색 품질, 임베딩 시간, 메모리를 비교합니다. 큰 모델이 더 맞힌 질문 수와 그 대가로 늘어난 질문 임베딩 시간을 함께 확인합니다.

이 사이트의 글 63편과 질문 50개로 BM25(띄어쓰기, 형태소, 식별자 보존), bge-m3 희소 벡터, 밀집 벡터, 이를 RRF로 섞은 하이브리드 검색을 개념 질문과 식별자 질문으로 나눠 recall@5와 MRR로 비교합니다. 한국어 토큰 나누기가 BM25를 얼마나 바꾸는지 출력으로 확인합니다.

다국어 소형 임베딩 모델로 한국어 문장을 벡터로 바꾸고, 내적과 코사인, 유클리드 거리가 언제 같은 순위를 내고 언제 갈라지는지 정규화 전후로 비교합니다. Qdrant의 거리 설정이 저장할 때 벡터에 무엇을 하는지도 문단 3,200개와 질문 20개로 확인합니다.

이 사이트에 발행된 글 53편을 문서로 삼아 청크 크기만 200, 400, 800, 1600자로 바꿔 가며 같은 질문 30개의 검색 결과를 비교합니다. 다국어 소형 임베딩 모델과 Qdrant로 recall@5, 문단 적중, MRR을 직접 계산하고, 512토큰을 넘는 청크의 뒷부분이 어떻게 버려지는지 확인합니다.
