
벡터 DB를 pgvector로 시작한 프로젝트라면 언젠가 이런 순간을 만납니다. CREATE INDEX 한 번에 걸리는 시간이 분 단위에서 시간 단위로 넘어가고, 쿼리 응답이 예전 같지 않다고 느껴지는 시점입니다. 이 글은 pgvector가 실제로 버틸 수 있는 규모의 조건과, 전용 벡터 DB로 옮겨야 한다는 신호를 구체적인 설정값과 함께 정리합니다.
결론부터 말하면 pgvector는 행 수보다 차원 수·인덱스 종류·쓰기 패턴 세 가지 조합이 규모를 가릅니다. 같은 100만 행이라도 어떤 인덱스를 쓰고 얼마나 자주 쓰는지에 따라 버틸 수 있는 규모와 버티지 못하는 규모가 완전히 갈립니다.
pgvector 하나로 벡터 검색을 얼마나 버틸 수 있을까요?
pgvector는 PostgreSQL 확장이라서 벡터 자체의 저장 한계부터 확인해야 합니다. vector 타입은 인덱스 없이 저장만 할 경우 최대 16,000차원까지 담을 수 있지만, HNSW나 IVFFlat으로 인덱스를 걸려면 2,000차원 이하여야 합니다. OpenAI text-embedding-3-small(1536차원)이나 text-embedding-3-large(3072차원 축소 가능)처럼 흔히 쓰는 임베딩은 대부분 이 범위 안에 들어옵니다.
행 수 자체보다 더 중요한 건 인덱스 빌드가 단일 프로세스 자원에 묶여 있다는 점입니다. pgvector 0.6.0부터 HNSW 인덱스 빌드에 병렬 처리가 도입되긴 했지만, 여전히 maintenance_work_mem이 부족하면 그래프 전체가 메모리에 올라가지 못해 빌드 속도가 눈에 띄게 느려집니다. 행이 수백만 단위로 늘어나는 시점부터 인덱스 재구축 시간이 실질적인 운영 이슈로 등장하는 이유가 여기 있습니다.
HNSW와 IVFFlat 중 어떤 인덱스가 규모를 더 버티게 해줄까요?

두 인덱스는 성격이 다릅니다. IVFFlat은 클러스터 개수(lists)를 미리 정해 벡터를 나눠 담는 방식이라 빌드는 빠르지만, 데이터가 늘어나면 클러스터를 다시 계산해야 recall이 유지됩니다. HNSW는 그래프 기반이라 데이터가 늘어도 재클러스터링이 필요 없지만, 빌드 자체에 메모리와 시간이 더 듭니다.
-- HNSW 인덱스 생성 (코사인 거리 기준)
SET maintenance_work_mem = '2GB';
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 쿼리 시 탐색 범위 조정 (기본값 40)
SET hnsw.ef_search = 100;
EXPLAIN ANALYZE
SELECT id FROM items
ORDER BY embedding <=> '[0.12, 0.87, ...]'
LIMIT 10;
m은 노드당 연결 수, ef_construction은 빌드 시 탐색 폭입니다. 값을 올리면 recall은 좋아지지만 인덱스 크기와 빌드 시간이 같이 늘어납니다. IVFFlat을 쓴다면 lists는 보통 행 수의 제곱근에 가까운 값으로 잡고, 쿼리 시 ivfflat.probes로 탐색할 클러스터 수를 늘려 recall을 맞춥니다.
| 항목 | IVFFlat | HNSW |
|---|---|---|
| 빌드 속도 | 빠름 | 상대적으로 느림 |
| 데이터 증가 대응 | 재인덱싱 필요 | 그래프에 점진적 반영 |
| 메모리 사용 | 상대적으로 적음 | 그래프 구조상 더 많음 |
| 필터 조합 recall | 낮아지기 쉬움 | 0.7.0 이후 개선 |
전용 벡터 DB로 옮겨야 한다는 신호는 구체적으로 무엇일까요?
아래 신호 중 두 가지 이상이 동시에 나타난다면 pgvector 단독 운영보다 Milvus, Qdrant, Weaviate 같은 전용 벡터 DB나 관리형 서비스 도입을 검토할 타이밍입니다.
- 인덱스 재구축(REINDEX)이 서비스 시간대와 겹쳐 쓰기 지연이 사용자에게 체감될 때
- 멀티테넌트 구조라 컬렉션(네임스페이스) 단위로 격리·샤딩이 필요한데, PostgreSQL 단일 인스턴스로는 수평 확장이 번거로울 때
- 임베딩 모델을 교체할 때마다 전체 재임베딩과 인덱스 전체 재빌드를 피할 수 없어 다운타임이 길어질 때
- 초당 쿼리량이 늘어 CPU 기반 검색으로는 한계라서 GPU 가속 인덱스가 필요할 때
ef_search나probes를 아무리 올려도 원하는 recall이 안 나와 튜닝 여지가 바닥났을 때

반대로 이런 신호가 아직 없다면 굳이 옮길 이유가 없습니다. 전용 벡터 DB로 이전하면 트랜잭션 일관성이 깨지고(원본 데이터는 PostgreSQL, 벡터는 별도 저장소), 두 시스템 간 동기화 파이프라인을 새로 관리해야 하는 비용이 따라옵니다. 이 트레이드오프를 감수할 만큼 규모가 커졌는지가 핵심 판단 기준입니다.
필터를 걸면 검색 recall이 떨어지는데, pgvector에서 해결되나요?
WHERE category = 'A' ORDER BY embedding <=> :query LIMIT 10처럼 조건 필터와 벡터 검색을 같이 쓰면, 인덱스가 상위 후보를 먼저 뽑은 뒤 필터를 적용하다 보니 조건에 맞는 결과가 부족해지는 문제가 있었습니다. pgvector 공식 저장소의 릴리스 노트를 보면 0.7.0부터 이 문제를 완화하기 위한 반복 인덱스 스캔(iterative index scan) 기능이 추가됐습니다. 조건에 맞는 결과가 부족하면 탐색 범위를 자동으로 넓혀가는 방식입니다.
다만 완전히 해결된 건 아닙니다. 필터 조건의 선택도가 매우 낮은 경우(예: 전체의 1% 미만만 해당하는 조건)에는 반복 스캔 횟수가 늘면서 지연 시간이 커질 수 있습니다. 이럴 때는 필터 컬럼을 파티션 키로 미리 분리해 테이블 자체를 나누는 방식이 pgvector 안에서 시도해볼 수 있는 현실적인 대안입니다.
지금 pgvector를 계속 써도 될지 무엇부터 점검하면 될까요?
가장 먼저 확인할 건 현재 인덱스 빌드 시간과 서비스 트래픽 패턴입니다. EXPLAIN (ANALYZE, BUFFERS)로 실제 쿼리 계획을 찍어보고, 인덱스 스캔이 정상적으로 타는지, ef_search나 probes 값을 조정했을 때 응답 시간과 recall이 어떻게 변하는지부터 측정해야 합니다. 이 수치 없이 “느린 것 같다”는 감으로 이전을 결정하면 불필요한 마이그레이션 비용만 늘어납니다.
메모리 절약이 필요하다면 0.7.0에서 추가된 halfvec 타입으로 정밀도를 절반으로 낮춰 인덱스 크기를 줄이는 방법도 있습니다. 이 경우 recall이 소폭 떨어질 수 있으니 실제 데이터셋으로 A/B 비교를 해보는 편이 안전합니다. PostgreSQL 공식 문서의 CREATE INDEX 옵션과 함께 maintenance_work_mem, work_mem 설정을 먼저 튜닝해보고, 그래도 위에서 짚은 신호들이 반복된다면 그때 전용 벡터 DB 도입 검토표를 만드는 순서를 권합니다.
