pgvectorscale의 StreamingDiskANN과 벡터 압축으로 PostgreSQL 하나에서 수천만 벡터 RAG를 Pinecone 대비 28배 빠르게
TL;DR: pgvectorscale은 PostgreSQL에 StreamingDiskANN 인덱스와 통계적 이진 양자화(SBQ)를 추가해, 전용 벡터 DB 없이 수천만 개의 벡터를 99% recall 수준으로 처리할 수 있게 합니다. Timescale 자체 벤치마크(50M 벡터, 768차원 기준) 결과로는 Pinecone 대비 p95 레이턴시 28배 감소, 비용 75% 절감이었습니다.
전용 벡터 데이터베이스를 따로 운영하고 있다면, 저와 같은 고민을 해본 적 있을 겁니다. Pinecone 청구서를 보면서 "이걸 꼭 써야 하나?", PostgreSQL과 Pinecone 사이에서 데이터 동기화를 맞추다 "그냥 하나로 합칠 수 없을까?" 하는 생각이요. 솔직히 저도 처음엔 전용 벡터 DB가 필수라고 생각했습니다. 아, 처음 접하시는 분을 위해 한 줄만 짚고 넘어가면 — 임베딩은 텍스트나 이미지를 수백수천 개의 숫자 배열로 변환한 것이고, 벡터 검색은 그 숫자들 사이의 유사도를 빠르게 찾는 기술입니다. 근데 20242025년을 지나면서 업계 분위기가 눈에 띄게 바뀌고 있어요. Confident AI는 비용과 운영 복잡도 문제로 Pinecone을 걷어내고 pgvector로 갈아탔고, NVIDIA, Reddit, TripAdvisor 같은 곳들도 PostgreSQL 기반 벡터 검색을 프로덕션에 올리고 있습니다.
이 흐름의 중심에 pgvectorscale이 있습니다. Timescale이 만든 이 PostgreSQL 확장은 StreamingDiskANN 인덱스와 통계적 이진 양자화(SBQ)라는 두 기술을 결합해, 단일 PostgreSQL 서버에서 수천만 개의 벡터를 높은 정확도로 처리합니다. 이 글에서는 이 두 기술이 어떻게 동작하는지, 실제로 어떻게 설정하는지, 그리고 어떤 상황에서 진짜 효과가 있는지를 같은 팀 동료에게 이야기하듯 풀어볼게요.
핵심 개념
StreamingDiskANN — 메모리 걱정 없이 수억 벡터를
pgvector의 HNSW 인덱스는 훌륭하지만 치명적인 약점이 있습니다. 전체 인덱스를 RAM에 올려야 한다는 거예요. 1536차원 벡터 기준으로 50M 개를 올리면 약 64GB RAM이 필요합니다. 서버 스펙을 올릴수록 비용도 같이 뜁니다.
StreamingDiskANN은 Microsoft Research의 DiskANN 알고리즘을 Timescale이 PostgreSQL에 맞게 재구현한 그래프 기반 ANN(Approximate Nearest Neighbor, 근사 최근접 이웃) 인덱스입니다. 아이디어 자체는 단순하고 영리합니다.
핵심 아이디어: 그래프 탐색 시 자주 방문하는 "hot path" 노드만 메모리에 유지하고, 나머지 데이터는 NVMe SSD에서 스트리밍 방식으로 읽어옵니다. 결과적으로 RAM 한계를 훨씬 넘는 규모의 데이터를 처리할 수 있습니다.
그래프 기반 ANN이 어떻게 동작하는지 간단히 보면, 각 벡터가 그래프의 노드가 되고 유사한 벡터들끼리 엣지로 연결됩니다. 쿼리가 들어오면 시작 노드에서 점점 쿼리에 가까운 방향으로 이동하며 후보를 좁혀나갑니다. DiskANN은 이 탐색 경로 중 핵심 부분만 캐싱하기 때문에 메모리 효율이 뛰어납니다.
| 인덱스 | 50M 벡터 RAM 요구량 (1536차원 기준) | 최대 차원 | 특징 |
|---|---|---|---|
| HNSW (pgvector) | ~64GB | 2,000차원 | 전체 인덱스 메모리 상주 |
| StreamingDiskANN | 16,000차원 | Hot path만 메모리, 나머지 SSD |
16,000차원 지원도 눈여겨볼 만합니다. 최근 LLM들이 고차원 임베딩을 생성하는 경우가 늘고 있는데, HNSW의 2,000차원 한계는 이런 상황에서 걸림돌이 됩니다.
통계적 이진 양자화(SBQ) — 압축하되 정확도는 지키기
StreamingDiskANN이 메모리 문제를 해결한다면, SBQ는 인덱스 크기와 탐색 속도 문제를 해결합니다. 두 기술은 함께 작동하는데, SBQ가 StreamingDiskANN의 압축 레이어 역할을 담당합니다.
벡터 압축 기법 중에 이진 양자화(Binary Quantization, BQ)라는 게 있습니다. 각 차원 값을 0 또는 1로 변환해 저장 공간을 대폭 줄이는 방식인데, 문제는 정보 손실이 너무 크다는 겁니다. 저도 실무에서 BQ를 테스트해봤다가 recall이 너무 낮아 포기한 경험이 있어요.
Timescale 연구팀이 고안한 통계적 이진 양자화(SBQ) 는 이 문제를 해결합니다. 일반 BQ는 0을 기준으로 양자화 경계를 고정하지만, SBQ는 각 차원 값의 통계적 분포를 분석해 차원별 평균값을 경계값(threshold)으로 사용합니다. 차원마다 다른 최적화된 경계를 찾기 때문에, 같은 압축률에서 일반 BQ보다 훨씬 높은 정확도를 낼 수 있습니다.
SBQ 동작 구조: 인덱스 탐색 1단계에서는 압축된 이진 벡터로 빠르게 후보군을 추립니다. 2단계에서는 추려진 후보를 원본 전체 정밀도 벡터로 재순위화(rerank)해 최종 결과를 만듭니다. 속도와 정확도를 동시에 잡는 2-pass 구조입니다.
결과적으로 실제 벤치마크에서 96~99% recall을 달성하면서도 인덱스 크기는 대폭 줄어듭니다.
용어 보충 — recall: 전체 실제 유사 벡터 중 검색 결과에 포함된 비율입니다. 99% recall은 진짜 유사한 벡터 100개 중 99개를 찾아낸다는 의미입니다. ANN 검색에서 recall과 속도는 트레이드오프 관계입니다.
실전 적용
예시 1: 기본 StreamingDiskANN 인덱스 구축
설치는 pgvector가 먼저 깔려 있어야 합니다. pgvectorscale은 pgvector 위에서 동작하는 레이어입니다. 먼저 작업할 테이블 스키마부터 잡아봅니다.
-- 기본 테이블 구조 (1536차원 OpenAI 임베딩 기준)
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
content TEXT NOT NULL,
embedding VECTOR(1536) NOT NULL
);
-- pgvector와 pgvectorscale 확장 활성화
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS vectorscale CASCADE;
-- 가장 심플한 형태의 StreamingDiskANN 인덱스
CREATE INDEX ON documents USING diskann (embedding vector_cosine_ops);실제 프로덕션 환경이라면 파라미터를 직접 튜닝하는 게 좋습니다. SBQ 압축까지 활성화하는 전체 설정은 이렇습니다.
-- 인덱스 빌드 전 PostgreSQL 인덱스 빌드용 메모리를 충분히 확보하는 것을 권장합니다
SET maintenance_work_mem = '4GB';
-- SBQ + 메모리 최적화 레이아웃 적용한 상세 인덱스
CREATE INDEX documents_embedding_diskann
ON documents USING diskann (embedding vector_cosine_ops)
WITH (
storage_layout = 'memory_optimized', -- SBQ 압축 활성화 핵심 옵션
num_neighbors = 50, -- 그래프 노드당 최대 이웃 수
search_list_size = 100, -- 빌드 시 후보 탐색 목록 크기
max_alpha = 1.2 -- 그래프 가지치기 계수
);| 파라미터 | 역할 | 기본값 | 조정 팁 |
|---|---|---|---|
storage_layout |
memory_optimized 설정 시 SBQ 활성화 |
— | 대용량이면 반드시 설정 |
num_neighbors |
그래프 연결 밀도 | 50 | 높을수록 정확하지만 인덱스 크기 증가 |
search_list_size |
빌드 시 탐색 범위 | 100 | 높을수록 품질 좋지만 빌드 시간 증가 |
max_alpha |
가지치기 공격성 | 1.2 | 1.0~1.5 사이에서 조정 |
maintenance_work_mem은 PostgreSQL이 인덱스 빌드 같은 유지 보수 작업에 할당하는 메모리 한도입니다. 저도 처음에 이 설정을 무시하고 빌드했다가 예상보다 훨씬 오래 걸려서 당황했던 적이 있어요. 10M 벡터 이상이라면 미리 넉넉하게 올려두는 걸 권장합니다.
예시 2: 쿼리 파라미터 튜닝으로 정확도/성능 균형 맞추기
인덱스를 만들었다면, 쿼리 단계에서도 조정할 수 있는 레버가 있습니다. 서비스 SLA에 따라 실시간으로 바꿀 수 있다는 게 편리한 점입니다.
-- 세션 단위로 검색 정확도 조정
-- 프로덕션 전체에 적용하려면 아래처럼 데이터베이스 레벨로 설정할 수 있습니다
-- ALTER DATABASE mydb SET diskann.query_search_list_size = 100;
SET diskann.query_search_list_size = 100;
-- 값이 높을수록 정확하지만 레이턴시 증가 (기본값 100)
-- recall이 부족하면 150~200까지 올려볼 수 있습니다
SET diskann.query_rescore = 50;
-- SBQ 재순위화 단계의 후보 수 (기본값 50)
-- 0으로 설정하면 rerank를 건너뛰어 속도는 빠르지만 정확도가 떨어집니다
-- 코사인 유사도 기반 ANN 검색
-- $1: VECTOR 타입 (쿼리 임베딩)
SELECT
id,
content,
embedding <=> $1 AS distance
FROM documents
ORDER BY embedding <=> $1
LIMIT 10;query_rescore가 SBQ의 2단계 rerank 후보 수를 제어합니다. 실무에서는 기본값 50이 대부분의 케이스에서 잘 동작했습니다. 세션 단위 SET 대신 서비스 전체에 적용하고 싶다면 ALTER DATABASE 또는 ALTER ROLE로 데이터베이스 레벨에서 지정하는 방법도 있습니다.
예시 3: RAG 파이프라인에서의 하이브리드 검색
벡터 검색만으로는 부족한 경우가 있습니다. 키워드가 중요한 쿼리에서는 전문 검색(Full-Text Search)과 조합하면 훨씬 좋은 결과가 나옵니다. PostgreSQL이라면 이걸 DB 안에서 바로 해결할 수 있습니다.
-- pgvectorscale(벡터 검색) + PostgreSQL FTS(전문 검색) 하이브리드
-- $1: VECTOR 타입 (쿼리 임베딩)
-- $2: TEXT 타입 (전문 검색용 키워드, 예: 'postgresql & vector')
SELECT
id,
content,
ts_rank(to_tsvector('simple', content), query) AS text_score,
embedding <=> $1 AS vector_score
FROM
documents,
to_tsquery('simple', $2) query
WHERE
to_tsvector('simple', content) @@ query -- FTS로 후보를 먼저 축소
ORDER BY
vector_score -- 최종 정렬은 벡터 유사도 기준
LIMIT 20;여기서 text_score는 SELECT에 올라와 있지만 정렬에는 쓰이지 않습니다. 이 쿼리의 의도는 FTS를 전처리 필터로만 활용하고, 최종 랭킹은 벡터 유사도에 맡기는 방식입니다. 만약 두 점수를 결합해 더 정교한 랭킹이 필요하다면 Reciprocal Rank Fusion(RRF)이나 가중합 방식을 고려해볼 수 있습니다.
전용 벡터 DB에서는 이런 쿼리를 애플리케이션 레이어에서 두 DB를 오가며 구현해야 합니다. PostgreSQL에서는 SQL 한 쿼리로 끝납니다. 실무에서 자주 맞닥뜨리는 상황인데, 이 단순함이 생각보다 큰 차이를 만들어냅니다.
LangChain이나 LlamaIndex를 쓰고 있다면, TimescaleVectorStore 통합을 통해 pgvectorscale을 벡터 스토어로 직접 연결할 수 있습니다. 별도 코드 없이 프레임워크가 알아서 처리해줍니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 메모리 효율 | HNSW 대비 RAM 요구량이 크게 낮아, 50M 벡터를 RAM 초과 없이 처리 가능 |
| 처리 성능 | 99% recall 기준 Pinecone 대비 p95 레이턴시 28배 감소, Qdrant 대비 QPS 11.4배 우세 (Timescale 자체 벤치마크, 768차원 기준) |
| 비용 절감 | 전용 벡터 DB + OLTP DB 이중 운영 불필요, AWS EC2 기준 약 75% 비용 절감 (Timescale 자체 벤치마크 기준) |
| 운영 단순화 | 기존 PostgreSQL 백업·복제·ACID 트랜잭션 인프라 그대로 재활용 |
| 고차원 지원 | HNSW의 2,000차원 한계 없이 16,000차원 임베딩까지 지원 |
| SBQ 정확도 | 표준 BQ 대비 높은 정확도, 일반 벤치마크에서 96~99% recall 달성 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 인덱스 빌드 시간 | 10M 벡터 기준 중간급 인스턴스에서 약 40분 소요 | 빌드 전 maintenance_work_mem을 4GB 이상으로 확보 |
| 소규모에서는 HNSW가 유리 | 수백만 건 이하 + RAM 충분한 환경에서는 HNSW가 더 빠를 수 있음 | 데이터 규모에 따라 인덱스 선택 |
| Rust 빌드 환경 필요 | pgvectorscale이 Rust로 작성되어 컴파일 환경이 필요 | Docker 이미지나 패키지 매니저 활용 권장 |
| pgvector 의존성 | pgvectorscale 설치 전 pgvector 선행 설치 필수 | CREATE EXTENSION vectorscale CASCADE로 자동 처리 가능 |
| Rerank 비용 | query_rescore 값이 클수록 레이턴시 증가 |
SLA에 맞게 조정, 일반적으로 기본값 50이 적절 |
| 업데이트 집약 워크로드 | 대량 업데이트 시 그래프 인덱스 재구성 비용 발생 | 배치 업데이트 후 REINDEX 고려 |
실무에서 가장 흔한 실수
-
소규모 데이터에 DiskANN을 쓰는 것. 수백만 건 이하이고 서버 RAM이 충분하다면 pgvector의 HNSW가 더 빠릅니다. DiskANN은 RAM을 초과하는 대용량에서 진가를 발휘하는 기술입니다.
-
인덱스 빌드 전
maintenance_work_mem설정을 확인하지 않는 것. 이 값이 너무 작으면 빌드 시간이 크게 늘어납니다. 빌드 전SET maintenance_work_mem = '4GB'정도로 올려두면 체감 차이가 납니다. -
query_rescore를 0으로 내리고 recall 저하를 모르고 지나치는 것. 속도 최적화 과정에서 rerank를 꺼버리면 정확도가 눈에 띄게 떨어집니다. 변경 전후로 반드시 recall을 측정해보는 것을 권장합니다.
마치며
pgvectorscale의 StreamingDiskANN + SBQ 조합은 "전용 벡터 DB가 필요하다"는 전제 자체를 재검토하게 만드는 기술입니다. 수천만 벡터 규모에서 99% recall을 유지하면서 비용을 대폭 줄이고, PostgreSQL의 모든 운영 이점을 그대로 누릴 수 있다면 검토할 이유는 충분합니다.
지금 바로 시작해볼 수 있는 3단계:
-
Docker로 환경을 띄워봅니다. Timescale 공식 Docker 이미지에 pgvector와 pgvectorscale이 포함되어 있습니다.
docker pull timescale/timescaledb-ha:pg17으로 바로 시작할 수 있고, 로컬에서 빌드 환경 걱정 없이 인덱스 동작을 확인해볼 수 있습니다. 태그별 포함 확장 목록은 pgvectorscale 공식 GitHub에서 최신 설치 안내를 확인하는 게 안전합니다. -
기존 pgvector 테이블에 인덱스를 교체해봅니다.
CREATE INDEX ... USING diskann으로 새 인덱스를 만들고,EXPLAIN ANALYZE로 기존 HNSW 대비 쿼리 플랜 차이를 비교해볼 수 있습니다. 데이터 양이 적으면 쿼리 플래너가 인덱스 대신 시퀀셜 스캔을 선택할 수 있는데, 이럴 때는SET enable_seqscan = off를 임시로 설정하면 인덱스 동작을 확인하는 데 도움이 됩니다. 기존 데이터 마이그레이션은 필요 없습니다. -
diskann.query_search_list_size와diskann.query_rescore를 조정하며 recall-latency 트레이드오프를 직접 측정해봅니다. 서비스 SLA에 맞는 파라미터 조합을 찾는 과정이 생각보다 직관적입니다. pgvectorscale GitHub의 README에 파라미터별 설명이 잘 정리되어 있으니 함께 펼쳐두면 좋습니다.
참고 자료
- pgvectorscale GitHub | timescale/pgvectorscale
- PostgreSQL and Pgvector: Now Faster Than Pinecone, 75% Cheaper, and 100% Open Source | Timescale Medium
- pgvectorscale: An Extension for Improved Vector Search in Postgres | dbvis.com
- Advanced Vector Workloads with pgvectorscale and Hybrid Search | DigitalOcean 공식 문서
- Enable and use DiskANN — Azure Database for PostgreSQL | Microsoft Learn
- PostgreSQL Vector Search: VectorChord vs. pgvector vs. pgvectorscale | VectorChord Blog
- pgvectorscale — Accelerating AI development | UnfoldAI