Vector DB 하이브리드 검색
LHG 2026-06-04
처음 보는 사람도 이해되게 먼저 풀어둔다
키워드 검색과 의미 검색은 서로 다른 사각지대를 가진다
한쪽만 쓰면 어느 한 종류의 질문에서 반드시 틀린다
Supabase의 벡터 검색은 실제로 pgvector라는 Postgres 확장이 처리한다
데이터가 없어도 인덱스를 미리 만들 수 있다는 게 운영상 큰 장점이다
ef_search
create index on documents using hnsw (embedding vector_cosine_ops) with (m = 16, ef_construction = 64); begin; set local hnsw.ef_search = 100; select id, content from documents order by embedding <=> $1 limit 10; commit;
빌드는 빠르지만 데이터를 어느 정도 채운 뒤에만 만들 수 있다
<->
<=>
<#>
maintenance_work_mem
max_parallel_maintenance_workers
별도 검색 엔진 없이도 Postgres만으로 꽤 정교한 키워드 검색이 된다
create table documents ( id bigint primary key generated always as identity, content text, fts tsvector generated always as (to_tsvector('english', content)) stored, embedding extensions.vector(512) ); create index on documents using gin (fts);
본문이 바뀔 때마다 fts 컬럼이 자동으로 갱신된다.
fts
두 검색을 각자 굴렸다면 이제 결과를 하나로 합쳐야 한다
스케일이 다른 두 점수를 그대로 더하면 안 되지만 순위는 바로 더할 수 있다
create or replace function hybrid_search( query_text text, query_embedding extensions.vector(512), match_count int, full_text_weight float default 1.0, semantic_weight float default 1.0, rrf_k int default 50 ) returns setof documents language sql as $$ with full_text as ( select id, row_number() over ( order by ts_rank_cd(fts, websearch_to_tsquery(query_text)) desc) as rank_ix from documents where fts @@ websearch_to_tsquery(query_text) limit least(match_count, 30) * 2 ), semantic as ( select id, row_number() over (order by embedding <#> query_embedding) as rank_ix from documents limit least(match_count, 30) * 2 ) select documents.* from full_text full outer join semantic on full_text.id = semantic.id join documents on documents.id = coalesce(full_text.id, semantic.id) order by coalesce(1.0/(rrf_k+full_text.rank_ix),0)*full_text_weight + coalesce(1.0/(rrf_k+semantic.rank_ix),0)*semantic_weight desc limit least(match_count, 30); $$;
FULL OUTER JOIN
match_count
coalesce
websearch_to_tsquery
데이터를 RAM에 올려놓고 ANN 알고리즘을 돌리는 라이브러리와 DB다
내부적으로 HNSW와 SQLite를 쓰는 사용자 친화적 데이터베이스다
where_document
Supabase처럼 SQL 함수 한 번으로 끝나지 않는다. 코드를 직접 짜야 한다
하드웨어와 차원이 다르면 숫자는 의미가 없다. 감을 잡는 참고용이다
7가지 질문을 차례로 던지면 거의 답이 나온다
거리 측정법 불일치와 인덱스 없이 시작하는 패턴이 가장 흔하다