3단계
pgvector + HNSW
25 분
pgvector + HNSW
PostgreSQL 이 벡터 검색까지 처리. 전용 벡터 DB 없이 "충분히 좋음".
1. 설치
docker run -d -e POSTGRES_PASSWORD=x -p 5432:5432 pgvector/pgvector:pg16
기존 PG:
CREATE EXTENSION IF NOT EXISTS vector;
2. 테이블
CREATE TABLE document_chunks (
id BIGSERIAL PRIMARY KEY,
document_id BIGINT NOT NULL,
chunk_index INT NOT NULL,
content TEXT NOT NULL,
embedding vector(768),
created_at TIMESTAMPTZ DEFAULT now()
);
vector(N) 차원은 임베딩 모델과 일치. 변경 시 재빌드.
3. 인덱스 선택 — HNSW vs IVFFlat
| 인덱스 | 빌드 | 쿼리 | 정확도 | 용도 |
|---|---|---|---|---|
| HNSW | 느림 | 빠름 | 매우 높음 | 읽기 중심 RAG |
| IVFFlat | 빠름 | 중간 | 튜닝 필요 | 대량 적재 + 주기 재빌드 |
4. HNSW 생성
CREATE INDEX ON document_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
m— 그래프 이웃 수 (기본 16). 높이면 정확 · 메모리 ↑ef_construction— 빌드 시 탐색 폭 (64). 높이면 느리지만 정확 ↑
전체 적재 후 한 번에 생성이 빠름.
5. 검색
SELECT id, content, 1 - (embedding <=> $1::vector) AS similarity
FROM document_chunks
ORDER BY embedding <=> $1::vector
LIMIT 10;
<=>— 코사인 거리 (0 = 동일)<->— L2<#>— 음수 내적
반드시 ORDER BY <거리> ASC + LIMIT k 여야 인덱스 사용.
6. 런타임 튜닝
SET hnsw.ef_search = 100; -- 기본 40
높이면 정확 · 느림. 세션 단위.
7. 배치 적재
async function indexChunks(chunks: { id: number; embedding: number[] }[]) {
const values = chunks.map((_, i) =>
`($${i*2+1}, $${i*2+2}::vector)`
).join(", ");
const params = chunks.flatMap(c => [c.id, JSON.stringify(c.embedding)]);
await pool.query(
`UPDATE document_chunks SET embedding = v.e
FROM (VALUES ${values}) AS v(id, e)
WHERE document_chunks.id = v.id::bigint`,
params
);
}
50 ~ 100 개 batch. API rate limit 주의.
8. 하이브리드 검색 — pg_trgm + vector
SELECT *, similarity(content, $1) AS text_score,
1 - (embedding <=> $2::vector) AS vec_score
FROM document_chunks
ORDER BY (similarity(content, $1) * 0.3 + (1 - (embedding <=> $2::vector)) * 0.7) DESC
LIMIT 10;
텍스트 정확 일치 + 의미 유사도 혼합. 정확도 보정 효과.
9. 백업
pg_dump 가 벡터를 텍스트 덤프. 복원 시 인덱스 재빌드 필요 (시간 소요). 대용량은 pg_restore --jobs 4 병렬.
10. 자주 걸리는 자리
CREATE EXTENSION vector누락- similarity DESC 로 정렬 — HNSW 인덱스 미사용. 거리 ASC 로
- 차원 불일치 — 모델 바꿨는데 컬럼 그대로
EXPLAIN에서Seq Scan— 인덱스 작동 안 함. 쿼리 패턴 점검
11. 한계 — 언제 전용 벡터 DB?
| 규모 | 권장 |
|---|---|
| ~10M 벡터 | pgvector 여유 |
| 10M ~ 100M | pgvector 튜닝 (파티셔닝 · 샤딩) |
| 100M+ | Qdrant · Milvus · Vespa |
대부분의 프로젝트는 10M 미만에서 끝.
하고픈 말
pgvector 로 시작하고 병목 측정 후 전환이 올바른 순서. "처음부터 Qdrant" 는 대개 과잉.
Next
- 04-redis-five-roles