RAG 를 만들면 임베딩한 벡터를 어딘가에 저장하고 검색해야 한다. 처음엔 쓰던 검색엔진(OpenSearch)에 벡터 기능을 얹어 시작했는데, 전용 벡터 DB 로 옮길지 고민이 생기면서 후보들을 제대로 비교해 봤다. 결론부터 말하면, 규모가 크지 않으면 뭘 골라도 성능은 충분하고 선택을 가르는 건 성능이 아니라 기능과 운영이라는 것이었다. 이 글은 Open...
RAG 를 만들면 임베딩한 벡터를 어딘가에 저장하고 검색해야 한다. 처음엔 쓰던 검색엔진(OpenSearch)에 벡터 기능을 얹어 시작했는데, 전용 벡터 DB 로 옮길지 고민이 생기면서 후보들을 제대로 비교해 봤다. 결론부터 말하면, 규모가 크지 않으면 뭘 골라도 성능은 충분하고 선택을 가르는 건 성능이 아니라 기능과 운영이라는 것이었다.
이 글은 OpenSearch · Milvus · Qdrant · Vespa · pgvector 다섯 제품을 같은 축으로 비교하고, 데이터 규모·차원·하이브리드 필요 여부에 따라 무엇을 고르면 되는지 정리한 기록이다.
"성능은 충분하다"는 인덱스 알고리즘(HNSW) 얘기고, 메모리 얘기는 다르다. 아래 표에도 온디스크 인덱싱·양자화가 계속 등장하는데, 그게 왜 부가 기능이 아니라 비용을 가르는 핵심 선택지인지부터 짚고 시작한다.
메모리 사용량은 벡터 수 × 차원 × 4byte 로 정해진다(float32 기준). 1,600만 벡터에 1024차원이면 raw 약 68GB다. 그런데 이건 그래프 구조를 뺀 순수 벡터 크기일 뿐이고, 실제 필요 메모리는 이보다 크다.
RDB 감각으로 이 규모를 보면 안 된다. RDB는 row 하나가 수백 byte에 불과하고, B-Tree 인덱스는 자주 쓰는 페이지만 캐시해도 충분하다 — 콜드 데이터를 디스크에서 읽어도 순차 접근이라 지연이 크지 않다. 반면 벡터는 row 자체가 크다(1024차원 float32 하나가 이미 4KB). 게다가 HNSW 그래프 탐색은 링크를 따라 이리저리 점프하는 무작위 접근이라, B-Tree처럼 "일부만 캐시"하는 전략이 잘 안 통한다 — 낮은 지연을 유지하려면 거의 다 메모리에 있어야 한다. 그래서 "레코드 수천만 건"이라는 같은 숫자가 RDB에서는 가벼운 사이징이지만 벡터 DB에서는 128GB, 256GB급 인스턴스나 다중 노드 샤딩을 요구하는 무거운 사이징이 된다. Pinecone·Milvus·Weaviate 같은 서비스의 수천만~수억 벡터 배포 가이드가 대용량 메모리 노드 여러 대로 샤딩하는 걸 기본값으로 두는 이유도 여기에 있다.
OpenSearch 공식 HNSW 메모리 공식은 1.1 × (4 × 차원 + 8 × M) × 벡터수다(M은 기본 16). 위 조건을 대입하면 약 72GB — raw 보다 이미 커진다. 여기에 circuit breaker(기본값: (전체 메모리 − JVM heap) × 50% 만 k-NN에 허용)와 레플리카(있으면 필요 메모리가 그대로 배수)까지 고려하면, "raw 68GB 니까 128GB 인스턴스 하나면 충분하다"는 계산은 틀리기 쉽다. 128GB 인스턴스에 JVM heap 32GB를 배정했다면 k-NN이 실제 쓸 수 있는 메모리는 (128−32) × 0.5 = 48GB뿐이라, 레플리카 없이도 이미 모자란다.
그래서 이 규모부터는 단일 장비를 키우기보다 샤딩으로 벡터를 여러 노드에 나누는 게 현실적인 선택이 된다. 차원이 크거나(예: 1536) 노드당 감당할 벡터 수를 넘기면 그때 양자화나 DiskANN(온디스크)을 검토한다. 아래 비교표의 "온디스크 인덱싱"·"양자화" 행이 바로 이 문제에 대한 제품별 답이다.
OpenSearch 공식 문서 기준(k-NN memory estimation, circuit breaker settings). 다른 제품(Milvus·Qdrant·Vespa)의 메모리 공식은 이 글에서 별도로 검증하지 않았다 — 확인 필요.
| 제품 | 성격 |
|---|---|
| OpenSearch | 범용 검색·분석 엔진에 k-NN(벡터) 플러그인을 얹은 형태 |
| Milvus | 처음부터 벡터를 위해 만든 전용 분산 DB |
| Qdrant | 전용 벡터 DB. Rust 로 작성, 가볍고 빠름 |
| Vespa | 대규모 서빙·검색 엔진. 텐서·벡터를 1급으로 내장 |
| pgvector | PostgreSQL 확장(extension). 벡터가 그냥 테이블의 한 컬럼 |
"전용이라 무조건 낫다"는 건 마케팅 수사다. 범용 엔진도 벡터 검색을 충분히 하고, billion-scale 도 가능하다. 전용 DB 의 진짜 차이는 기능의 폭(학습형 sparse, multi-vector, GPU 인덱스, DiskANN)이다.
pgvector 는 이 넷과 출발점 자체가 다르다 — 별도 DB 가 아니라 "이미 쓰던 PostgreSQL 에 벡터 컬럼 하나 추가하는" 접근이다. CREATE EXTENSION vector; 한 줄로 끝나고, 3대 클라우드(AWS Aurora/RDS, GCP Cloud SQL/AlloyDB, Azure)가 매니지드로 지원한다(pgvector Installation). 뒤에 나올 표에서 "미지원"이 많은 이유도 여기 있다 — 전용 벡터 DB 의 고급 기능 대부분을 처음부터 포기하고 단순함을 택한 제품이다.
거의 모든 제품이 HNSW(그래프 기반, 인메모리 고성능)를 기본으로 지원한다. 그래서 HNSW 만 쓸 거면 인덱스는 선택 기준이 못 된다. 차이는 그 외 선택지다.
범례: ◎ 완전 지원(공식 기능) · ○ 지원(대체 수단 또는 제약 있음) · △ 부분/실험적 지원 · ✗ 미지원
| OpenSearch | Milvus | Qdrant | Vespa | pgvector | |
|---|---|---|---|---|---|
| HNSW | ◎ | ◎ | ◎ | ◎ | ◎ |
| IVF 계열 | ◎ | ◎ | ✗(quantization·full-scan으로 대체) | ○ | ◎(IVFFlat) |
| 온디스크 인덱싱 | ○(quantization 기반 on_disk, DiskANN은 아님) | ◎(Knowhere DiskANN) | ○(mmap) | ○(확인 필요) | ✗(core) / ◎(pgvectorscale 확장의 StreamingDiskANN) |
| GPU 인덱스 | ✗ | ◎ | ✗ | ✗ | ✗ |
| 양자화 | ○ | ○ | ◎ | ○ | ○(halfvec·binary, v0.7.0~) |
Qdrant는 IVF를 지원하지 않는다 — 공식 문서가 명시하는 밀집 벡터 인덱스는 HNSW 하나뿐이다(Qdrant Indexing). OpenSearch는 Microsoft의 DiskANN 알고리즘 자체를 채택하지 않았고, binary/scalar quantization 기반 on_disk 모드(GA)로 유사한 메모리 절감 효과를 낸다(Disk-based vector search).
Qdrant의 mmap은 Milvus의 DiskANN(Vamana 그래프를 SSD 접근에 최적화해 설계)과는 접근 자체가 다르다. Qdrant는 벡터 파일과 HNSW 그래프를 OS의 가상 주소 공간에 매핑해 두고, 실제 로딩은 OS 페이지 캐시에 맡긴다 — 자주 쓰는 페이지는 캐시에 남아 인메모리와 비슷한 속도를 내지만, 캐시에 없는 페이지는 그때 디스크를 읽는다. 별도 자료구조를 새로 설계한 게 아니라 OS가 이미 하는 일(페이지 캐싱)을 벡터 저장소에 그대로 위임한 것이라 구현은 단순하지만, HNSW 그래프 탐색처럼 접근 패턴이 무작위(random access)에 가까우면 캐시 미스가 잦아져 순수 온디스크 전용 구조(DiskANN)보다 지연이 더 크게 튈 수 있다(Qdrant Storage).
pgvector 는 core 확장과 pgvectorscale(Timescale 이 별도로 만든 확장)을 구분해야 한다. HNSW·IVFFlat·halfvec·binary quantization 은 core 기능이지만, DiskANN(StreamingDiskANN)은 pgvectorscale 을 따로 설치해야 나온다(pgvectorscale StreamingDiskANN). "pgvector 를 쓴다"고 해서 DiskANN 까지 자동으로 따라오지 않는다.
| OpenSearch | Milvus | Qdrant | Vespa | pgvector | |
|---|---|---|---|---|---|
| dense ANN | ◎ | ◎ | ◎ | ◎ | ◎ |
| 하이브리드(BM25+벡터) | ◎ | ◎ | ○ | ◎ | ○(SQL로 직접 조합, ts_rank_cd는 BM25 아님) |
| 학습형 sparse(SPLADE) | ✗ | ◎ | △ | ○ | ○(sparsevec 타입만 제공, SPLADE 전용 통합은 없음) |
| multi-vector | ✗ | ◎ | ○ | ◎ | ✗ |
| 메타데이터 필터링 | ◎ | ◎ | ◎(표현력 최강) | ◎ | ◎(SQL WHERE·JOIN 그대로) |
| OpenSearch | Milvus | Qdrant | Vespa | pgvector | |
|---|---|---|---|---|---|
| 설치·운영 난이도 | 중 | 높음(컴포넌트 다수) | 낮음 | 높음 | 낮음(CREATE EXTENSION 한 줄) |
| 분산 확장 | ◎ | ◎ | ○(OSS는 수동 리밸런싱, 자동은 Cloud 전용) | ◎ | ✗(core는 단일 노드, Citus는 범용 샤딩이라 벡터 전용 통합 아님) |
| 멀티테넌시 | 인덱스 단위 | collection/partition | collection/shard | 스키마 | 확인 필요(권장 패턴 공식 문서 미명시) |
| 생태계·커뮤니티 | 큼 | 큼 | 중 | 중 | 큼(GitHub 22,000+ star, 3대 클라우드 매니지드) |
네 제품 다 "샤드 단위로 HNSW를 독립 구축하고 결과를 병합한다"는 기본 골격은 같지만, 리밸런싱 성숙도는 다르다.
shard_number도 생성 후 변경이 안 된다. 자동 리밸런싱과 무중단 Resharding(v1.13)은 Qdrant Cloud 전용이라 공식 문서가 명시한다(distributed_deployment, cluster-scaling). 그래서 "가장 가볍게 운영하고 싶다 → Qdrant"를 고를 때도, self-host라면 나중에 샤드를 늘리는 결정을 미리 해둬야 한다.비교표보다 실무에서 중요한 건 "내 조건이면 뭘 고르나"다.
비교하면서 가장 크게 배운 건, 벤더 블로그의 우위 주장 상당수가 교차 검증하면 흔들린다는 점이다.
이 글은 "뭘 고를까"에 초점을 맞춘 표면 비교다. 왜 제품마다 메모리·분산·인덱싱 방식이 이렇게 다른지 내부 구조까지 파고들고 싶다면 벡터 DB 5종, 아키텍처는 어떻게 다른가를 다음으로 읽으면 된다. Milvus 개별 아키텍처는 Milvus 아키텍처 글에, HNSW 같은 검색 알고리즘 자체는 벡터 검색 알고리즘 — kNN에서 HNSW까지에 더 정리해 두었다.
함께 읽기
외부 자료