pgvector 필터링과 하이브리드 검색
pgvector 필터링과 하이브리드 검색
프로덕션에서 벡터 검색은 단독으로 쓰기보다 "카테고리가 같은 것 중에서 가까운 것"처럼 조건을 결합해서 쓰는 경우가 많아요. pgvector는 WHERE 절과 함께 쓰는 최근접 이웃 검색을 인덱싱하는 방법을 몇 가지 제시해요.
필터 컬럼에 인덱스 만들기
카테고리 조건을 먼저 거르는 쿼리가 있다고 해 볼게요.
SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5;
좋은 시작점은 필터에 쓰는 컬럼에 일반 인덱스를 만드는 거예요. 이러면 많은 경우 빠르고 정확한 최근접 이웃 검색이 가능해져요. PostgreSQL은 B-tree(기본), hash, GiST, SP-GiST, GIN, BRIN 같은 인덱스 유형을 지원해요.
CREATE INDEX ON items (category_id);
여러 컬럼이라면 다중 컬럼 인덱스를 고려해 봐요.
CREATE INDEX ON items (location_id, category_id);
정확 인덱스는 조건이 전체 행의 낮은 비율에 걸릴 때 잘 동작해요. 반대로 많은 비율에 걸리는 조건이라면 근사 인덱스가 더 나을 수 있어요.
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops);
근사 인덱스와 필터링 순서
근사 인덱스에서는 필터링이 인덱스 스캔 이후에 적용돼요. 예를 들어 조건이 행의 10%에 걸리고 HNSW 기본 hnsw.ef_search 40이라면, 평균적으로 4개 행만 조건을 통과해요. 더 많은 결과가 필요하면 반복 인덱스 스캔(iterative index scans)을 켜면 필요할 때 자동으로 인덱스를 더 스캔해요.
SET hnsw.iterative_scan = strict_order;
부분 인덱스와 파티셔닝
필터 값이 몇 개 안 되는 경우에는 부분 인덱스(partial index)를 고려해 봐요.
CREATE INDEX ON items USING hnsw (embedding vector_l2_ops) WHERE (category_id = 123);
반대로 다양한 값으로 필터링한다면 파티셔닝이 더 어울릴 수 있어요.
CREATE TABLE items (embedding vector(3), category_id int) PARTITION BY LIST(category_id);
멀티테넌시
여러 테넌트가 근사 인덱스를 공유하면, 한 테넌트의 벡터가 다른 테넌트의 재현율(과 속도)에 영향을 줄 수 있어요. 테넌트 격리가 필요하면 리스트 파티셔닝이나 별도 테이블을 쓰는 게 좋아요.
CREATE TABLE items (customer_id int, embedding vector(3)) PARTITION BY LIST(customer_id);
하이브리드 검색
PostgreSQL의 풀텍스트 검색과 결합하면 키워드 + 벡터의 하이브리드 검색도 가능해요.
SELECT id, content FROM items, plainto_tsquery('hello search') query
WHERE textsearch @@ query ORDER BY ts_rank_cd(textsearch, query) DESC LIMIT 5;
두 결과를 합칠 때는 Reciprocal Rank Fusion(RRF)이나 cross-encoder를 쓰는 게 흔한 방법이에요.