PostgreSQL 하나로 10억 개 벡터를 저장·검색하는 VectorChord — pgvector 대비 인덱싱이 100배 빠른 이유
RAG 파이프라인을 구성하다 보면 어느 순간 벽에 부딪힙니다. 벡터 DB는 따로 두고, 키워드 검색은 Elasticsearch로, 메타데이터 필터는 PostgreSQL로... 인프라가 세 개로 쪼개지는 순간 운영 복잡도가 폭발합니다. 슬랙 알림을 세 군데서 받고, 장애가 나면 어느 시스템인지 먼저 확인해야 하고, 비용도 각각 따로 청구됩니다. "그냥 PostgreSQL 하나로 다 할 수 없나?" 싶었던 분이라면 VectorChord가 그 답이 될 수 있습니다.
VectorChord는 PostgreSQL 확장(extension)으로, 대규모 벡터 유사도 검색을 가능하게 해주는 오픈소스 프로젝트입니다. pgvecto.rs라는 프로젝트가 전신인데, TensorChord 팀이 아키텍처를 전면 재설계해 새 이름으로 출시했습니다. pgvecto.rs가 HNSW 중심으로 설계되어 잦은 업데이트 환경에서 성능 한계를 드러냈고, IVF 기반으로 처음부터 다시 짜면서 그 문제를 해결한 것이 VectorChord입니다. 저도 처음엔 "pgvector 있는데 왜 또?"라고 생각했는데, 벤치마크 숫자를 보고 나서 생각이 바뀌었습니다. 2025년 12월 1.0 GA 버전을 선언하면서 프로덕션 레디임을 공식화했고, 현재는 1.1까지 나온 상태입니다.
VectorChord를 쓰면 pgvector HNSW 대비 인덱싱을 100배 빠르게 빌드하면서, 벡터 검색·BM25 키워드 랭킹·멀티벡터 Late Interaction을 PostgreSQL 단일 스택 안에서 모두 처리할 수 있습니다.
핵심 개념
IVF + RaBitQ: 왜 이 조합이 빠른가
pgvector의 HNSW는 그래프 기반 인덱스입니다. 검색 품질이 좋지만, 새 벡터를 삽입할 때마다 그래프를 재조정해야 합니다. 데이터가 자주 바뀌는 환경에서는 이 재조정 비용이 발목을 잡습니다.
VectorChord는 다른 전략을 씁니다. IVF(Inverted File Index) 구조로 벡터를 클러스터에 할당하고, 각 클러스터의 포스팅 리스트(Posting List)로 관리합니다. 삽입은 append 하나로 끝납니다. 전역 그래프를 건드릴 일이 없습니다. 삭제의 경우 코드 클리어로 처리되는데, 한 가지 알아두면 좋은 점이 있습니다. 삭제된 벡터는 VACUUM이 실행되기 전까지 포스팅 리스트에 데드 엔트리로 남아 있어 검색 품질에 영향을 줄 수 있습니다. 삭제가 빈번한 워크로드라면 VACUUM 주기를 별도로 고려해두는 것이 좋습니다.
여기에 RaBitQ(Randomized Bit Quantization) 가 핵심입니다. 32비트 부동소수점 벡터를 먼저 무작위로 회전시킨 뒤 이진화(binarization)해서 비트 패킹된 정수 코드로 압축합니다. 단순히 bfloat16처럼 정밀도만 낮추는 것과는 다릅니다. 무작위 회전 단계를 거치면 양자화 오류가 방향에 관계없이 고르게 분산되어 정밀도 손실을 최소화할 수 있습니다. 쿼리 시에는 복잡한 부동소수점 연산 대신 비트 연산과 정수 산술만으로 포스팅 리스트를 스캔합니다. 쿼리 하나가 CPU 캐시 안에서 거의 다 해결된다는 뜻입니다.
IVF(Inverted File Index): 검색 엔진에서 역색인이라고 부르는 그 구조입니다. 벡터 공간을 여러 클러스터로 나누고, 각 클러스터에 속한 벡터 목록(포스팅 리스트)을 관리합니다. 쿼리가 들어오면 가장 가까운 몇 개 클러스터만 탐색하므로 전체 스캔보다 훨씬 빠릅니다.
지원하는 거리 함수
| 거리 함수 | 연산자 | 용도 |
|---|---|---|
| Cosine | <=> |
방향성 유사도 (텍스트 임베딩 일반) |
| L2 | <-> |
유클리드 거리 |
| Inner Product | <#> |
점수 기반 랭킹 |
| MaxSim | @# |
멀티벡터 Late Interaction (ColBERT 등) |
MaxSim은 VectorChord만의 차별점입니다. ColBERT처럼 토큰 레벨 다중 벡터를 사용하는 모델에서 쿼리 토큰과 문서 토큰 간 최대 유사도를 집계하는 연산을 PostgreSQL 안에서 직접 처리할 수 있습니다.
VectorChord Suite: All-in-One 아키텍처
VectorChord 팀이 최근 밀고 있는 방향이 흥미롭습니다. 벡터 검색 하나로 끝내는 게 아니라 세 가지를 하나의 PostgreSQL에서 통합 운영하는 구성입니다.
| 구성 요소 | 역할 |
|---|---|
| VectorChord | 벡터 유사도 검색 (IVF + RaBitQ) |
| VectorChord-BM25 | 키워드 BM25 랭킹 인덱스 |
| pg_tokenizer | 한국어 포함 다국어 토크나이저 |
BM25: TF-IDF의 발전형으로, 단어 빈도와 문서 길이를 함께 고려한 키워드 기반 랭킹 알고리즘입니다. 전통적인 검색 엔진에서 표준으로 쓰여온 방식인데, VectorChord-BM25는 이를 PostgreSQL 네이티브로 구현해 Elasticsearch 대비 3배 빠른 BM25 랭킹을 제공합니다.
EDB(EnterpriseDB)가 자사 AI 플랫폼에 VectorChord-BM25를 공식 채택한 것도 이 방향의 신뢰성을 높여줍니다.
실전 적용
개념은 이렇고, 실제로 어떻게 쓰는지 세 가지 시나리오로 보겠습니다. 기본 벡터 검색부터 하이브리드, 멀티벡터 순으로 난이도를 올렸습니다.
예시 1: 대규모 RAG 파이프라인에서 인덱스 구성
가장 기본적인 사용 패턴입니다. vchordrq 인덱스 타입을 사용하면 IVF + RaBitQ 조합이 적용됩니다.
-- VectorChord 확장 활성화
CREATE EXTENSION IF NOT EXISTS vchord;
-- 문서 테이블 (768차원 임베딩 예시)
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT,
embedding VECTOR(768)
);
-- vchordrq 인덱스 생성
-- lists: IVF 클러스터 수. 데이터 크기에 따라 조정
CREATE INDEX ON documents USING vchordrq (embedding vector_cosine_ops)
WITH (options = $$
residual_quantization = true
[build.internal]
lists = [4096]
spherical_centroids = false
$$);
-- probes: 쿼리 시 탐색할 클러스터 수. 높을수록 정확하지만 느려짐
SET vchordrq.probes = 10;
-- 시맨틱 검색
-- $1 예시: '[0.12, -0.07, 0.34, ...]'::vector(768)
SELECT id, content, embedding <=> $1::vector AS distance
FROM documents
ORDER BY distance
LIMIT 10;| 파라미터 | 설명 |
|---|---|
residual_quantization |
잔차 양자화 활성화. 클러스터 중심과의 차이를 다시 양자화해 정밀도를 높임 |
lists |
IVF 클러스터 수. 데이터 100만 개당 약 1000~4000 권장 |
spherical_centroids |
코사인 거리 사용 시 true로 설정하면 클러스터 중심이 단위 구면에 올라가 정밀도가 올라감 |
vchordrq.probes |
쿼리 시 탐색할 클러스터 수. lists의 약 10~20분의 1을 시작점으로 실험해보시면 좋음 |
lists 값 설정 팁: 데이터 수 대비 너무 적으면 클러스터당 벡터가 많아져 스캔 비용이 올라가고, 너무 많으면 빌드 시간과 메모리가 커집니다. 1억 개라면 4096~16384 사이에서 실험해보시는 것을 권장합니다.
예시 2: 하이브리드 검색 (시맨틱 + BM25 키워드)
실무에서 자주 맞닥뜨리는 상황인데, 순수 벡터 검색만으로는 정확한 고유명사나 코드 식별자 검색이 약합니다. "OpenAI" 같은 브랜드명이나 useEffect 같은 정확한 식별자는 의미 유사도보다 키워드 일치가 훨씬 신뢰할 만합니다. BM25와 RRF(Reciprocal Rank Fusion)를 결합하면 두 가지 약점을 서로 보완할 수 있습니다.
-- VectorChord-BM25 확장도 필요
CREATE EXTENSION IF NOT EXISTS vchord_bm25;
-- BM25 인덱스 컬럼 추가
ALTER TABLE documents ADD COLUMN content_bm25 bm25vector;
-- k=60은 RRF의 표준 상수값
WITH semantic AS (
SELECT id, RANK() OVER (ORDER BY embedding <=> $1::vector) AS rank
-- $1 예시: '[0.12, -0.07, 0.34, ...]'::vector(768)
FROM documents
ORDER BY embedding <=> $1::vector
LIMIT 60
),
keyword AS (
SELECT id, RANK() OVER (ORDER BY content_bm25 <&> $2::bm25vector) AS rank
-- $2 예시: to_bm25vector('PostgreSQL 벡터 검색')
FROM documents
ORDER BY content_bm25 <&> $2::bm25vector
LIMIT 60
)
SELECT
COALESCE(s.id, k.id) AS id,
-- 한쪽 검색에만 등장한 결과에 패널티를 주기 위해 rank 대신 1000 사용
1.0 / (60 + COALESCE(s.rank, 1000)) + 1.0 / (60 + COALESCE(k.rank, 1000)) AS rrf_score
FROM semantic s
FULL OUTER JOIN keyword k ON s.id = k.id
ORDER BY rrf_score DESC
LIMIT 10;RRF(Reciprocal Rank Fusion): 여러 검색 결과의 순위를 결합하는 알고리즘입니다. 각 시스템에서의 순위(rank)를
1/(k + rank)형태로 변환해 합산합니다. 점수 스케일이 달라도 순위 기반으로 공정하게 합산되는 게 장점입니다.
예시 3: ColBERT 멀티벡터 검색 (MaxSim)
솔직히 이 기능이 VectorChord를 다시 보게 만든 결정적 이유였습니다. ColBERT는 문서를 단일 임베딩이 아닌 토큰별 다중 벡터로 표현하는 리랭킹 모델입니다. 쿼리의 각 토큰과 문서의 각 토큰 사이에서 최대 유사도를 골라 합산하기 때문에, 단일 임베딩 방식보다 정밀한 의미 매칭이 가능합니다. 기존 벡터 DB에서 이걸 제대로 다루기가 쉽지 않았는데, VectorChord에서는 네이티브로 지원됩니다.
ColBERT / ColQwen2: 토큰 수준 다중 임베딩을 생성하는 모델입니다. 문서 하나를 단일 벡터가 아니라 토큰 수만큼의 벡터 배열로 표현합니다. ColQwen2는 ColBERT 방식을 이미지·PDF 등 멀티모달로 확장한 Vision-Language 모델입니다.
-- ColBERT 스타일: 문서 토큰 벡터 배열로 저장
-- VECTOR(128)[]는 128차원 벡터의 배열 타입
-- VectorChord의 vchordrq 인덱스는 이 가변 길이 배열 타입을 직접 지원하며,
-- pgvector의 단일 vector 타입과 달리 토큰별 다중 벡터를 하나의 컬럼으로 관리할 수 있습니다
CREATE TABLE colbert_documents (
id BIGSERIAL PRIMARY KEY,
content TEXT,
token_embeddings VECTOR(128)[] -- 토큰별 128차원 벡터 배열
);
-- MaxSim 인덱스 생성
CREATE INDEX ON colbert_documents
USING vchordrq (token_embeddings vector_maxsim_ops)
WITH (options = $$
[build.internal]
lists = [1024]
$$);
-- 쿼리 토큰 벡터 배열로 MaxSim 검색
-- $1 예시: ARRAY['[0.1, 0.2, ...]'::vector(128), '[0.3, 0.1, ...]'::vector(128)]
SELECT id, content
FROM colbert_documents
ORDER BY token_embeddings @# $1::vector[] DESC
LIMIT 10;WARP(MaxSim 쿼리 실행을 위한 커널 최적화 기법)가 적용된 후 810초 걸리던 평가 쿼리셋이 41초로 단축됐다는 사례가 보고됐습니다. 약 18.7배 차이입니다. PDF를 OCR 없이 이미지 그대로 검색하는 ColQwen2 기반 파이프라인에도 같은 구조를 그대로 적용할 수 있습니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 인덱싱 속도 | pgvector 대비 100배 빠른 인덱스 빌드. 1억 벡터를 20분 이내에 처리 |
| 디스크 효율 | RaBitQ 양자화로 메모리 풋프린트 최소화. $1/월 인스턴스에서 400만 벡터 서빙 가능 |
| 업데이트 친화성 | IVF 포스팅 리스트 구조로 잦은 삽입·삭제 시 HNSW보다 안정적 |
| 완전한 SQL 호환 | JOIN, WHERE 필터, 트랜잭션 모두 표준 SQL로 처리 |
| 멀티벡터 지원 | MaxSim + WARP로 ColBERT 스타일 Late Interaction 실용화 |
| All-in-One 스택 | BM25 + 벡터 검색을 단일 DB에서 처리해 운영 복잡도 감소 |
| 억 단위 스케일 | 아키텍처 변경 없이 10억 벡터까지 확장 가능 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 인덱스 빌드 메모리 | 4GB 이하 인스턴스에서 인덱스 빌드 불가 | 8GB 이상 인스턴스에서 빌드 후 이동하거나, 외부 KMeans 사전 계산 활용 |
| 외부 KMeans 필요 | 최적 성능을 위해 별도 머신에서 클러스터 중심 사전 계산 권장 | 클라우드 스팟 인스턴스를 단기 활용해 비용 최소화 |
| x86 최적화 편향 | fast-scan 커널이 x86_64 전용. ARM(aarch64)에서 성능 저하 | ARM 환경이라면 pgvector HNSW와 성능 비교 후 선택 |
| f32 전용 | 현재 float32 벡터 타입만 지원. int8 등 미지원 | 임베딩 모델이 다른 타입을 출력한다면 f32 변환 필요 |
| 빌드 의존성 | clang-17+ 필요. Ubuntu 22.04, Debian 12에서 수동 설치 | Docker 공식 이미지(tensorchord/vchord-postgres) 사용 권장 |
| 생태계 성숙도 | pgvector 대비 커뮤니티 규모, 클라우드 완전 관리형 지원 부족 | 자체 호스팅 또는 TensorChord 클라우드 검토 |
실제로 ARM 인스턴스에서 직접 돌려봤는데, 공개된 벤치마크 수치의 절반 정도가 나왔습니다. M1/M2 Mac이나 AWS Graviton 환경에서는 반드시 자체 환경에서 먼저 측정해보시는 것이 중요합니다.
HNSW vs IVF 선택 기준: 데이터셋이 수십만 건 이하이거나 메모리에 완전히 올라가는 환경이라면 pgvector HNSW가 여전히 더 단순하고 충분한 선택일 수 있습니다. VectorChord는 수백만~수십억 규모에서, 잦은 업데이트가 있는 환경에서 진가를 발휘합니다.
실무에서 가장 흔한 실수
-
lists값을 기본값으로 두는 경우: 데이터 크기에 맞지 않는 클러스터 수는 검색 품질과 속도 모두에 영향을 줍니다.lists값을 데이터 수에 비례해 조정하고,SET vchordrq.probes = N으로 쿼리 탐색 범위도 함께 튜닝해보시면 체감 차이가 납니다. -
외부 KMeans 없이 프로덕션 인덱스를 빌드하는 경우: 내부 KMeans 빌드도 동작하지만, 대규모 데이터에서는 외부에서 사전 계산한 클러스터 중심을 주입하는 방식이 품질과 속도 모두 유리합니다. 공식 문서의
centroid주입 옵션을 참고하시면 좋습니다. -
ARM 환경에서 공개 벤치마크를 그대로 기대하는 경우: 공개된 수치는 모두 x86_64 기준입니다. M1/M2 Mac이나 AWS Graviton 환경에서는 실제 측정치가 상당히 다를 수 있으니, 스펙을 결정하기 전에 자체 환경에서 검증이 필요합니다.
마치며
지금 RAG 스택을 세 개의 서비스로 운영하고 있다면, VectorChord Suite로 통합을 검토해볼 시점입니다. 벡터 DB·키워드 엔진·멀티벡터 리랭커를 별도로 관리하는 대신, 익숙한 SQL로 전부 처리하면서 운영 비용과 인프라 복잡도를 동시에 줄일 수 있습니다.
지금 바로 시작해볼 수 있는 3단계:
-
Docker로 즉시 환경 구성: 아래 명령어로 VectorChord가 포함된 PostgreSQL 컨테이너를 바로 띄울 수 있습니다. 별도 컴파일 없이 시작해볼 수 있습니다.
bashdocker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=postgres tensorchord/vchord-postgres:pg17-latest -
소규모 데이터로 인덱스 성능 비교: 기존에 pgvector를 쓰고 있다면, 10~50만 건 수준의 동일 데이터셋에
vchordrq인덱스를 병렬로 생성해EXPLAIN ANALYZE로 실행 계획과 소요 시간을 직접 비교해보시면 좋습니다. -
하이브리드 검색 파이프라인 구성: VectorChord-BM25 확장도 함께 설치하고, 위의 RRF 쿼리 예시를 현재 프로젝트에 적용해보시면 벡터만으로는 잡기 어렵던 정확도 개선을 체감할 수 있습니다.
참고 자료
- VectorChord 공식 사이트
- VectorChord GitHub
- VectorChord 공식 문서
- VectorChord 1.0: 100x Faster Indexing than pgvector
- VectorChord: Store 400k Vectors for $1 in PostgreSQL
- VectorChord 0.5: RaBitQ-empowered DiskANN Index
- VectorChord 0.3: Multi-Vector Contextual Late Interaction
- PostgreSQL Vector Search: VectorChord vs. pgvector vs. pgvectorscale
- Hybrid search with Postgres Native BM25 and VectorChord
- VectorChord-BM25: 3x Faster than Elasticsearch
- All-in-one VectorChord Suite: Building Production-Ready RAG
- Scaling Vector Search to 1 Billion on PostgreSQL
- ColBERT rerank in PostgreSQL
- PGXN vchord 배포 페이지
- EDB Postgres AI - VectorChord-BM25 공식 채택