LabHub

벡터 데이터베이스 — pgvector 와 Qdrant · ANN 인덱스와 재현율 · 이론

필터가 결과를 줄이는 함정, 그리고 하이브리드

LabHub 에서 이어서 보기

한 줄 요약

인덱스 검색에 WHERE 를 붙이면 결과가 줄어든다. 인덱스는 조건을 모른 채 가까운 후보 ef_search 개만 내놓기 때문이다. 고치는 법은 후보를 늘리거나, 조건별 부분 인덱스를 두거나, 낱말 검색과 합치는 것이다.

왜 문제가 되나

"영어 문서 중에서 가장 비슷한 10건" 을 HNSW 로 찾는다. 인덱스는 언어를 모른 채 가장 가까운 40개(기본 ef_search)를 돌려주고, PostgreSQL 이 그 40개에 lang = 'en' 을 건다. 한국어 질의라면 가까운 40개가 거의 다 한국어라 영어는 두어 건 남고, LIMIT 10 인데 3건이 온다. 오류는 없다. 그냥 적게 온다.

이 코스 환경에서 잰 값이다.

필터 lang='en' · ef_search  40 → 평균 5.8건 반환 · recall 0.535필터 lang='en' · ef_search 400 → 평균 9.5건 반환 · recall 0.920lang='en' 부분 인덱스 · ef 100 → 평균 10 건 반환 · recall 0.990

세 가지 처방

후보를 늘린다(over-fetch). ef_search 를 올리면 조건에 맞는 것이 후보 안에 더 많이 남는다. 가장 쉽지만 조건이 까다로울수록(전체의 1%) 한없이 올려야 하고, pgvector 0.6 은 상한이 1000 이다. 서브쿼리로 LIMIT 200 을 먼저 뽑는 것도 같은 벽에 걸린다 — 인덱스 스캔은 ef_search 개 이상을 내놓지 않는다.

부분 인덱스. 자주 쓰는 조건이 몇 개로 정해져 있으면 CREATE INDEX ... WHERE lang = 'en' 으로 조건마다 인덱스를 둔다. 플래너가 조건에 맞는 인덱스를 고르고, 그 인덱스 안에는 조건에 맞는 벡터만 있으니 후보가 낭비되지 않는다. 조건이 수백 가지면 쓸 수 없다.

하이브리드. 낱말이 정확히 들어 있어야 하는 검색("pgbouncer" 가 언급된 것)은 전문 검색이 더 잘한다. 두 결과를 합치는 표준이 RRF(Reciprocal Rank Fusion) 다 — 각 목록에서의 순위 r 에 1/(60 + r) 을 매겨 더한다. 점수 단위가 다른 두 검색(코사인 거리와 ts_rank)을 순위로만 합치므로 정규화가 필요 없다.

실무에서 진짜 중요한 것

필터가 있는 질의는 따로 재현율을 잰다. 전체 재현율 0.98 이 필터 질의에서는 0.5 일 수 있고, 그 차이는 사용자가 "결과가 이상하다" 고 말하기 전에는 아무 지표에도 안 잡힌다. LIMIT 10 에 10건이 안 오면 그것을 정상으로 두지 말고 경보로 삼는다.

다음 실습에서 할 것

IVFFlat 과 HNSW 를 만들어 손잡이를 돌리며 재현율을 재고, 필터 함정을 재현한 뒤 부분 인덱스로 고치고, tsvector 와 RRF 로 합친다. 마지막에 목표 재현율의 ef_search 를 데이터베이스 기본값으로 못박는다.