pgvector는 언제 성능 한계에 부딪히는가 — pgvector vs VectorChord 벤치마크 비교
RAG 파이프라인을 처음 만들 때 pgvector부터 붙이는 건 거의 반사적인 선택입니다. PostgreSQL에 이미 올라가 있으니까요. CREATE EXTENSION vector 한 줄이면 되고, LangChain 문서에도 나와 있고. 근데 데이터가 수십만 건을 넘어 수천만 건에 가까워지면서 쿼리 레이턴시가 슬금슬금 올라가기 시작했습니다. HNSW 인덱스가 RAM을 얼마나 잡아먹는지 처음 확인했을 때 꽤 당황했던 기억이 납니다.
그 시점에 VectorChord를 발견했습니다. 공식 벤치마크를 처음 봤을 때 솔직히 "이게 진짜야?" 싶었어요. 인덱스 빌드 16배, 삽입 14배, QPS 최대 5배. 실제로 테스트해보니 숫자가 허투루 나온 게 아니더라고요. 이 글에서는 두 익스텐션이 어떤 원리로 동작하는지, 100만 건 기준으로 어떤 인덱스를 선택할지, 그리고 VectorChord로 넘어갈 정확한 기준점을 실제 코드와 함께 풀어봅니다.
pgvector를 당장 버려야 한다는 이야기가 아닙니다. 오히려 100만 건 이하 규모에서는 pgvector가 훨씬 편하고 안전한 선택이에요. 이 글은 이미 pgvector를 쓰고 있거나 RAG 파이프라인을 구축 중인 개발자를 대상으로 합니다. 경계를 어디로 잡아야 하는지, VectorChord가 그 경계를 어떻게 허무는지 알고 나면 다음 아키텍처 결정이 훨씬 명확해집니다.
핵심 개념
pgvector의 두 가지 인덱스 전략
pgvector는 설치 자체는 아주 간단합니다. PostgreSQL 14 이상이면 바로 붙일 수 있어요.
-- 익스텐션 설치
CREATE EXTENSION IF NOT EXISTS vector;
-- 벡터 컬럼 추가 (1536차원 OpenAI 임베딩 기준)
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT,
embedding vector(1536)
);
-- IVFFlat 인덱스 (데이터가 어느 정도 쌓인 후 생성 권장)
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- HNSW 인덱스 (빈 테이블에도 바로 생성 가능)
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64);두 인덱스는 트레이드오프가 꽤 다릅니다.
| 인덱스 | 빌드 속도 | 메모리 사용 | Recall | 특징 |
|---|---|---|---|---|
| IVFFlat | 빠름 | 낮음 | 중간 | 데이터를 클러스터로 나눠 인접 클러스터만 탐색 |
| HNSW | 느림 | 높음 (RAM 상주) | 높음 | 다층 그래프로 빠른 탐색, 점진적 추가 가능 |
HNSW의 함정: HNSW 인덱스는 전체를 메모리에 올려두는 구조입니다. 메모리는 대략
벡터 수 × 차원 × 4bytes × 그래프 오버헤드(1.2~1.5배)로 추산할 수 있는데, 500만 건 1536차원 기준으로만 계산해도 최소 36GB 이상이 나옵니다. 실제로maintenance_work_mem을 아무리 올려도 빌드 시간이 3시간을 넘어가는 걸 봤을 때, 이 인덱스 전략에 대해 다시 생각하게 됐습니다.
이미 pgvector를 쓰고 있다면: IVFFlat과 HNSW가 이미 익숙하다면 바로 VectorChord 섹션으로 건너뛰어도 됩니다.
VectorChord가 문제를 다르게 푸는 방법: RaBitQ
VectorChord의 핵심 인덱스 타입은 vchordrq로, IVF + RaBitQ 조합입니다. 여기서 RaBitQ(Randomized Binary Quantization) 가 게임 체인저 역할을 합니다.
RaBitQ는 고차원 벡터를 콤팩트한 비트 표현으로 압축하는 양자화 기법입니다. 32비트 float 대신 비트 패킹된 코드를 저장하고, 유사도 계산 시 POPCOUNT 비트 연산으로 내적을 근사해 CPU 효율을 대폭 높이면서도 높은 recall을 유지합니다. NTU에서 이론적 오차 범위를 보장하는 형태로 발표된 알고리즘입니다.
쿼리 흐름을 비교하면 차이가 더 명확합니다.
| 단계 | pgvector HNSW | VectorChord (IVF+RaBitQ) |
|---|---|---|
| 저장 방식 | 32bit float 전체 | 압축된 비트 코드 |
| 탐색 방식 | 그래프 순회 | IVF 클러스터 라우팅 |
| 유사도 계산 | float 연산 | POPCOUNT 비트 연산 |
| 메모리 요구 | RAM 상주 필수 | 디스크 친화적 |
| 인덱스 빌드 | 느림 | pgvector 대비 16배 빠름 |
-- VectorChord 설치 후 인덱스 생성
CREATE EXTENSION IF NOT EXISTS vchord CASCADE;
-- vchordrq 인덱스 (IVF + RaBitQ)
CREATE INDEX ON documents USING vchordrq (embedding vector_cosine_ops)
WITH (options = $$
residual_quantization = true
[build.internal]
lists = 1000
$$);참고로, VectorChord 0.5부터는 vchordrq와 별도로 DiskANN 인덱스 타입도 실험적으로 제공합니다. DiskANN은 디스크에서 직접 그래프 탐색을 수행하는 방식으로, 일부 데이터셋에서 IVF+RaBitQ보다 높은 QPS를 보이지만 아직 프로덕션 사용은 권장되지 않습니다. 장단점 표에서 "DiskANN 실험적 단계"가 나오는 건 이 타입을 말하는 겁니다.
실전 적용
예시 1: 100만 건 이하 RAG 시스템 — pgvector HNSW
이 규모에서는 pgvector HNSW가 가장 편한 선택입니다. Supabase, Neon, AWS RDS, GCP Cloud SQL 어디서든 바로 붙일 수 있고, LangChain이나 LlamaIndex와의 연동도 문서가 넘쳐납니다.
import asyncpg
from pgvector.asyncpg import register_vector
from openai import AsyncOpenAI
client = AsyncOpenAI()
async def semantic_search(query: str, conn, top_k: int = 5):
# asyncpg에서 pgvector 타입을 쓰려면 반드시 이 등록 과정이 필요합니다
# 빠뜨리면 타입 변환 오류가 납니다
await register_vector(conn)
response = await client.embeddings.create(
input=query,
model="text-embedding-3-small"
)
query_embedding = response.data[0].embedding
rows = await conn.fetch(
"""
SELECT id, content, 1 - (embedding <=> $1::vector) AS similarity
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT $2
""",
query_embedding,
top_k
)
return rows| 코드 포인트 | 설명 |
|---|---|
register_vector(conn) |
asyncpg에서 pgvector 타입 등록. 생략하면 오류가 납니다 |
<=> 연산자 |
코사인 거리 (1 - similarity) |
<-> 연산자 |
L2 유클리드 거리 |
<#> 연산자 |
내적(negative inner product) |
$1::vector |
asyncpg 파라미터를 vector 타입으로 명시적 캐스팅 |
HNSW 검색 정확도는 ef_search 파라미터로 조정할 수 있습니다. 값이 높을수록 recall은 올라가지만 속도는 느려집니다.
-- 세션별 ef_search 조정 (기본값 40)
SET hnsw.ef_search = 100;
-- 메타데이터 필터 결합 예시
SELECT id, content, embedding <=> $1::vector AS distance
FROM documents
WHERE category = 'tech' AND created_at > '2024-01-01'
ORDER BY embedding <=> $1::vector
LIMIT 10;실수 주의:
WHERE category = 'tech'같은 필터가 붙으면 인덱스가 제대로 타지 않는 경우가 꽤 있습니다. 실제로 처음 붙여보고EXPLAIN ANALYZE를 확인했을 때 예상과 다른 플랜이 나와서 당황했습니다. 메타데이터 필터를 결합할 때는 반드시 실행 계획을 확인해보시는 것을 권장합니다.
예시 2: 5000만 건 이상 고부하 환경 — VectorChord vchordrq
이 규모가 되면 HNSW의 메모리 문제가 본격적으로 드러납니다. 768차원 벡터 1억 건을 HNSW로 관리하려면 인덱스만 수백 GB가 필요하고, 그걸 RAM에 올려야 하니 인스턴스 비용이 기하급수적으로 뜁니다. 이 차이가 실제 인스턴스 비용으로 환산되면 체감이 확 달라지는 수준입니다. VectorChord는 이 상황을 디스크 친화적 설계로 돌파합니다.
-- VectorChord 인덱스 생성 (대규모 설정 예시)
CREATE INDEX ON documents USING vchordrq (embedding vector_cosine_ops)
WITH (options = $$
residual_quantization = true
[build.internal]
lists = 4096
spherical_centroids = false
$$);
-- 검색 시 탐색 범위 조정
SET vchordrq.probes = 100; -- 탐색할 클러스터 수 (recall vs QPS 트레이드오프)
-- pgvector와 동일한 SQL 인터페이스 사용 가능
SELECT id, content, 1 - (embedding <=> $1::vector) AS similarity
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT 10;| 파라미터 | 역할 | 권장값 |
|---|---|---|
lists |
IVF 클러스터 수 | sqrt(전체 벡터 수) 근처 |
probes |
쿼리 시 탐색할 클러스터 수 | recall 0.95 목표 시 lists * 0.05 |
residual_quantization |
잔차 양자화 활성화 | 대부분의 경우 true 권장 |
VectorChord의 또 다른 장점은 하이브리드 검색입니다. VectorChord-BM25를 함께 설치하면 Elasticsearch 없이도 키워드+벡터 복합 검색을 PostgreSQL 안에서 처리할 수 있습니다.
-- VectorChord-BM25 설치
CREATE EXTENSION IF NOT EXISTS vchord_bm25 CASCADE;
-- BM25 인덱스 생성
CREATE INDEX ON documents USING bm25 (content bm25_ops);
-- 하이브리드 검색: BM25 + 벡터 유사도 결합 (함수 시그니처는 공식 문서 확인 권장)
SELECT
id,
content,
bm25_score(content, 'RAG pipeline') * 0.3 +
(1 - (embedding <=> $1::vector)) * 0.7 AS hybrid_score
FROM documents
ORDER BY hybrid_score DESC
LIMIT 10;장단점 분석
장점
| 항목 | pgvector | VectorChord |
|---|---|---|
| 관리형 서비스 | AWS RDS, GCP, Azure, Supabase, Neon 완전 지원 | 자체 설치 필요, 관리형 선택지 제한적 |
| WAL/복제 안정성 | PITR, 물리 복제 완전 지원 | WAL(Write-Ahead Log) 기반 인덱스 미지원 |
| 인덱스 빌드 속도 | 보통 | pgvector 대비 16배 빠름 |
| 삽입 성능 | 보통 | pgvector 대비 14배 빠름 |
| 대규모 QPS | 1000만+ 벡터에서 급락 | 1억 768차원 벡터, 0.95 precision, 131 QPS 유지 |
| 메모리 효율 | HNSW는 RAM 상주 필수 | 디스크 친화적 설계, 메모리 절감 |
| 비용 효율 | 규모 증가 시 메모리 비용 급증 | Pinecone 대비 동일 비용에 6배 더 많은 벡터 저장 |
| 커뮤니티 성숙도 | 높음, 문서 풍부 | 상대적으로 적음 (빠르게 성장 중) |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| pgvector: 대규모 성능 저하 | 1000만 건 이상에서 QPS 급락 | VectorChord 마이그레이션 또는 pgvectorscale 검토 |
| pgvector: HNSW 메모리 | 데이터 증가 시 RAM 비용 급증 | 초기부터 규모 예측 후 IVFFlat 또는 VectorChord 고려 |
| VectorChord: 복구 제약 | WAL(Write-Ahead Log) 기반 인덱스 미지원, PITR 후 인덱스 재빌드 필요 | 인덱스 재빌드 시간을 복구 계획에 반영 |
| VectorChord: 메모리 최소 요구 | 인덱스 빌드 시 4GB 이하 환경 구동 불가 | 빌드 전용 인스턴스 사용 또는 외부 빌드 후 임포트 |
| VectorChord: DiskANN 상태 | 아직 실험적(experimental) 단계 | 프로덕션에서는 vchordrq(IVF+RaBitQ) 사용 권장 |
pgvectorscale: Timescale이 pgvector 위에 올린 StreamingDiskANN 익스텐션으로, HNSW의 메모리 문제를 해결하는 중간 선택지입니다. 50M 코히어 임베딩 기준 pgvector 대비 p95 레이턴시를 28배 낮춘다고 알려져 있으며, 관리형 서비스 호환성을 유지하면서 성능을 높이고 싶은 경우 먼저 검토해볼 수 있습니다.
실무에서 가장 흔한 실수
-
HNSW를 기본값으로 선택하고 데이터 규모 예측을 하지 않는 것 — "일단 HNSW로 가고 나중에 보자"는 생각은 수천만 건 규모에서 메모리 폭탄으로 돌아옵니다. 1년 후 데이터 규모를 먼저 추산하고 인덱스 전략을 잡는 것을 강하게 권장합니다.
-
메타데이터 필터와 벡터 검색을 함께 쓸 때 실행 계획을 확인하지 않는 것 —
WHERE category = 'tech'같은 필터가 붙으면 인덱스가 제대로 타지 않는 경우가 꽤 있습니다.EXPLAIN ANALYZE로 실행 계획을 확인해보시는 것을 권장합니다. -
VectorChord로 이전하면서 WAL 복제 제약을 간과하는 것 — 미션 크리티컬한 환경에서 PITR 요구사항이 있다면, VectorChord 인덱스는 복구 후 재빌드가 필요하다는 점을 운영 계획에 반드시 반영해야 합니다.
마치며
데이터 규모와 운영 요구사항이 인덱스 선택의 핵심이고, pgvector와 VectorChord는 서로 다른 문제를 잘 푸는 도구입니다. 저는 지금도 소규모 서비스에서는 pgvector HNSW를 기본으로 두고, 데이터 증가 예측이 보이는 시점에 VectorChord를 테스트 환경에 올려보는 방식을 택하고 있습니다. "성능이 문제가 되면 그때 바꾸자"는 접근보다, 미리 기준점을 잡아두는 편이 훨씬 덜 고통스럽더라고요.
지금 바로 시작해볼 수 있는 3단계:
-
가장 먼저 할 일은 현재 벡터 수와 1년 후 예상 규모를 적어두는 것입니다 — 100만 건 이하라면 pgvector HNSW, 1000만 건을 넘길 것 같다면 VectorChord를 테스트 환경에 올려볼 것을 권장합니다. Docker로 빠르게 띄울 수 있습니다:
docker run --name vectorchord -e POSTGRES_PASSWORD=yourpwd -p 5432:5432 tensorchord/vchord-pg17-v0.4.3(최신 버전은 GitHub Releases에서 확인하시는 것이 좋습니다) -
벤치마크는 직접 돌려보시는 게 제일 확실합니다 — VectorChord 공식 저장소의 벤치마크 스크립트를 활용하거나, 실제 서비스 데이터 샘플 10만~100만 건으로 두 인덱스의 빌드 시간과 쿼리 레이턴시를
EXPLAIN ANALYZE로 비교해보시면 숫자가 내 상황에서 어떻게 달라지는지 확실히 감이 옵니다. -
하이브리드 검색이 필요하다면 VectorChord-BM25 조합이 가장 현실적인 선택입니다 — 별도 Elasticsearch 클러스터를 유지하는 비용과 복잡도를 줄이고 싶다면,
vchord_bm25를 함께 설치해 PostgreSQL 단일 스택으로 키워드+벡터 검색을 통합하는 방향을 검토해보시는 것을 권장합니다.
참고 자료
- pgvector vs VectorChord 벤치마크 | VectorChord 공식 문서
- pgvector 비교 FAQ | VectorChord 공식 문서
- 벤치마크 FAQ | VectorChord 공식 문서
- PostgreSQL 벡터 검색 비교 분석 (메모리 vs 디스크) | VectorChord 블로그
- VectorChord 1.0 — 인덱싱 100배 빠르게 | VectorChord 블로그
- PostgreSQL에서 10,000 QPS 달성 | VectorChord 블로그
- $1당 40만 벡터 저장 | VectorChord 블로그
- RaBitQ 기반 DiskANN 인덱스 (VectorChord 0.5) | VectorChord 블로그
- VectorChord-BM25 소개 | VectorChord 블로그
- GitHub — tensorchord/VectorChord
- GitHub — pgvector/pgvector
- pgvector IVFFlat & HNSW 심층 분석 | AWS 블로그
- pgvector의 트레이드오프 | Qdrant 블로그
- Postgres as Vector DB 독립 벤치마크 | Sean's Blog
- HNSW vs IVFFlat vs IVF_RaBitQ 비교 | kodesage