본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

하이브리드 검색: WHERE 필터와 벡터 순위

~22 min · pgvector, hybrid, sql

Level 0Scout
0 XP0/41 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

실제 서비스 쿼리의 모습

전체 테이블에서 가장 가까운 열 개만 찾는 경우는 드물어. 보통은 특정 테넌트와 프로젝트에 속하고, 어떤 시점 이후 갱신됐으며, 사용자가 읽을 권한이 있는 결과 가운데 가장 가까운 열 개를 원해. pgvector를 쓰면 이 조건을 SQL문 하나로 적고 구조화 필터는 Postgres에 맡길 수 있어.

인덱스의 동작을 고려한 필터

HNSW와 IVFFlat 인덱스가 근사 최근접 이웃을 먼저 찾고 WHERE 조건은 그 후보 집합에 적용될 수 있어. 조건이 후보를 매우 많이 거른다면 CTE나 부분 인덱스를 이용해 Postgres가 먼저 필터한 뒤 순위를 매기게 해. 실제 실행 방식은 EXPLAIN ANALYZE로 측정해야 해.

반복적으로 후보를 넓히는 방식

인덱스가 후보 열 개를 돌려줬는데 필터를 통과한 결과는 세 개뿐일 수 있어. 이럴 때는 가까운 후보 100개를 넉넉히 가져와 조건을 적용하고, 남은 결과에서 상위 열 개를 골라. 얼마나 넉넉히 가져올지는 실제 데이터로 조정해.

Code

구조화 필터를 적용한 하이브리드 쿼리·sql
WITH candidates AS (
    SELECT id, source, chunk_index, text, metadata,
           1 - (embedding <=> $1) AS similarity
    FROM   chunks
    WHERE  metadata->>'tenant'    = $2
      AND  metadata->>'doc_type'  = ANY($3::text[])
      AND  updated_at >= now() - interval '90 days'
    ORDER BY embedding <=> $1
    LIMIT 100                       -- 필터 위해 over-fetch
)
SELECT *
FROM   candidates
WHERE  similarity >= 0.30           -- 최소 품질 floor
ORDER  BY similarity DESC
LIMIT  10;
파이썬에서 같은 쿼리 실행하기·python
with conn.cursor() as cur:
    cur.execute(open('hybrid_query.sql').read(), (query_vec, 'cwkpippa', ['markdown', 'code']))
    rows = cur.fetchall()

for row in rows:
    print(f'{row[5]:.3f}  {row[1]}#{row[2]}  → {row[3][:80]}')

External links

Exercise

직접 가진 데이터로 벡터 순위와 구조화 필터 두 개를 결합한 하이브리드 쿼리를 작성해. EXPLAIN ANALYZE로 인덱스 사용 여부를 확인하고, LIMIT을 30, 100, 300으로 바꿔 가며 필터를 통과한 결과 수가 어디서 안정되는지 살펴봐.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.