Skip to content
Published on

Vector DB 완전 비교 2025: Pinecone, Weaviate, Chroma, pgvector 중 무엇을 선택하는가

Authors

들어가며

RAG 시스템을 구축하다 보면 필연적으로 벡터 데이터베이스를 선택해야 하는 순간이 온다. Pinecone, Weaviate, Chroma, pgvector, Qdrant... 선택지가 너무 많다.

각 도구는 다른 상황을 위해 최적화되어 있다. 프로토타입을 빠르게 만들어야 하는 상황과 수억 개의 벡터를 처리해야 하는 프로덕션 환경은 완전히 다른 선택이 필요하다. 이 글에서 실용적 기준으로 각 DB를 비교하고 결정 프레임워크를 제시한다.


벡터 DB가 왜 필요한가?

일반 데이터베이스와의 차이를 이해하면 벡터 DB의 가치가 명확해진다.

-- 일반 DB: 정확한 매칭
SELECT * FROM products WHERE id = 123;
SELECT * FROM documents WHERE title = '인공지능 가이드';

-- 벡터 DB: 의미적 유사도 검색
-- "AI에 대해 알려줘" 쿼리 → 가장 관련 있는 문서 5개 반환
SELECT id, content, 1 - (embedding <=> '[0.1, 0.2, ...]') AS similarity
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]'
LIMIT 5;
-- "=" 연산이 아닌 "<=> (코사인 거리)" 연산

벡터 DB는 수백만 개의 고차원 벡터에서 "가장 가까운 이웃"을 밀리초 내에 찾아주도록 특화되어 있다.


핵심 알고리즘: HNSW vs IVF

벡터 DB를 이해하려면 핵심 인덱싱 알고리즘을 알아야 한다.

HNSW (Hierarchical Navigable Small World):
─────────────────────────────────────────
구조: 계층적 그래프 (마치 지도의 줌 레벨처럼)
빌드 시간: O(n log n)
쿼리 시간: O(log n) 근사
메모리: 높음 (전체 그래프를 RAM에 유지)
정확도: 높음 (조정 가능)

장점: 빠른 쿼리, 높은 정확도, 동적 삽입 지원
단점: 메모리 사용량 큼, 빌드 시간 길음
적합: 수백만~수억 건, 높은 recall 요구

IVF (Inverted File Index):
──────────────────────────
구조: k-means 클러스터링 후 각 클러스터를 별도 인덱스로
빌드 시간: 빠름 (클러스터 수만큼)
쿼리 시간: nprobe 파라미터에 의존
메모리: HNSW보다 낮음
정확도: nprobe 증가 시 향상

장점: 메모리 효율적, 수십억 건 처리 가능
단점: 쿼리 시 nprobe 튜닝 필요, 정확도-속도 트레이드오프
적합: 수십억 건 이상의 초대규모 데이터셋

대부분의 RAG 시스템(수백만 건 이하)에서는 HNSW가 더 나은 선택이다.


각 DB 상세 비교

Pinecone: 운영 부담 없는 관리형 SaaS

from pinecone import Pinecone, ServerlessSpec

# 초기화
pc = Pinecone(api_key="your-api-key")

# 인덱스 생성 (한 번만)
index = pc.create_index(
    name="my-rag-index",
    dimension=1536,          # 사용하는 임베딩 모델 차원과 일치
    metric="cosine",         # cosine, euclidean, dotproduct 중 선택
    spec=ServerlessSpec(
        cloud="aws",
        region="us-east-1"
    )
)

# 벡터 업서트 (삽입/업데이트)
index = pc.Index("my-rag-index")
index.upsert(vectors=[
    {
        "id": "doc1",
        "values": embedding_vector,
        "metadata": {
            "text": "원본 텍스트 내용",
            "source": "document.pdf",
            "page": 1,
            "created_at": "2025-01-01"
        }
    },
    {
        "id": "doc2",
        "values": embedding_vector2,
        "metadata": {"text": "두 번째 문서 내용"}
    }
])

# 유사도 검색
results = index.query(
    vector=query_embedding,
    top_k=5,
    include_metadata=True,
    filter={"source": {"$eq": "document.pdf"}}  # 메타데이터 필터링
)

for match in results.matches:
    print(f"ID: {match.id}, Score: {match.score:.3f}")
    print(f"Text: {match.metadata['text'][:100]}")

# 네임스페이스로 멀티테넌시 구현
index.upsert(
    vectors=[...],
    namespace="customer-123"  # 고객별 격리
)

장점:

  • 제로 운영: 인프라 관리, 스케일링, 백업 자동
  • 초당 수만 건의 쿼리 자동 처리
  • 99.99% SLA

단점:

  • 비용이 비쌈: 1억 벡터 기준 월 $70+ (Serverless), 전용 Pod는 수백~수천 달러
  • 데이터가 Pinecone 인프라에 저장됨 (벤더 락인)
  • 복잡한 필터링 쿼리에서 성능 저하 가능

적합한 상황: 빠른 프로덕션 출시, DevOps 리소스 없음, 비용보다 속도가 중요


Weaviate: 하이브리드 검색의 강자

import weaviate
from weaviate.classes.config import Configure, Property, DataType, VectorDistances

# 로컬 연결 (Docker로 실행) 또는 Weaviate Cloud Services
client = weaviate.connect_to_local()
# 또는: client = weaviate.connect_to_wcs(cluster_url="your-url", auth_credentials=...)

# 스키마 정의 (컬렉션 생성)
client.collections.create(
    "Document",
    vectorizer_config=Configure.Vectorizer.text2vec_openai(),  # 자동 임베딩
    vector_index_config=Configure.VectorIndex.hnsw(
        distance_metric=VectorDistances.COSINE
    ),
    properties=[
        Property(name="content", data_type=DataType.TEXT),
        Property(name="source", data_type=DataType.TEXT),
        Property(name="page", data_type=DataType.INT)
    ]
)

# 문서 삽입 (vectorizer 설정 시 임베딩 자동 생성)
collection = client.collections.get("Document")
collection.data.insert({
    "content": "인공지능이 세상을 바꾸고 있다",
    "source": "article.pdf",
    "page": 1
})

# 시맨틱 검색
results = collection.query.near_text(
    query="AI 기술 동향",
    limit=5,
    return_metadata=["distance", "score"]
)

# 하이브리드 검색 (벡터 + BM25 키워드)
hybrid_results = collection.query.hybrid(
    query="AI 기술 동향",
    alpha=0.5,   # 0: 순수 BM25, 1: 순수 벡터, 0.5: 균형
    limit=5
)

# GraphQL로 복잡한 쿼리
import weaviate.classes.query as wq
complex_results = collection.query.near_text(
    query="AI 기술 동향",
    filters=wq.Filter.by_property("page").greater_than(0),
    limit=10
)

장점:

  • 하이브리드 검색(벡터 + 키워드) 내장
  • 멀티모달 지원 (텍스트, 이미지, 오디오)
  • 자동 벡터화 (vectorizer 설정 시 임베딩 코드 불필요)
  • 오픈소스 + 클라우드 모두 지원

단점:

  • 설정이 복잡하고 리소스 소모가 큼
  • Docker 기본 설치 시 2GB+ RAM 필요
  • 러닝 커브가 높음

적합한 상황: 하이브리드 검색 필요, 자체 인프라 운영 가능, 복잡한 스키마


Chroma: 가장 쉬운 시작점

import chromadb
from chromadb.utils.embedding_functions import OpenAIEmbeddingFunction

# 인메모리 (개발/테스트용)
client = chromadb.Client()

# 영구 저장 (로컬 파일)
client = chromadb.PersistentClient(path="./chroma_db")

# 임베딩 함수 설정 (자동 임베딩)
embedding_fn = OpenAIEmbeddingFunction(
    api_key="your-openai-key",
    model_name="text-embedding-3-small"
)

collection = client.create_collection(
    name="my_documents",
    embedding_function=embedding_fn,
    metadata={"hnsw:space": "cosine"}
)

# 문서 추가 (텍스트 제공 시 자동 임베딩)
collection.add(
    documents=[
        "RAG는 검색 증강 생성 기술입니다",
        "임베딩은 텍스트를 벡터로 변환합니다",
        "벡터 DB는 유사도 검색에 최적화되어 있습니다"
    ],
    ids=["doc1", "doc2", "doc3"],
    metadatas=[
        {"source": "guide.pdf", "page": 1},
        {"source": "guide.pdf", "page": 2},
        {"source": "guide.pdf", "page": 3}
    ]
)

# 쿼리 (텍스트만 제공하면 됨)
results = collection.query(
    query_texts=["텍스트 검색 기술에 대해 알려주세요"],
    n_results=3,
    where={"source": "guide.pdf"}  # 메타데이터 필터
)

print(results["documents"])
print(results["distances"])

장점:

  • 가장 쉬운 API, 코드 5줄로 시작 가능
  • Python 네이티브, Jupyter에서 바로 실행
  • 오픈소스, 무료

단점:

  • 대규모 프로덕션에 적합하지 않음 (수백만 건 이상에서 성능 저하)
  • 분산 처리 미지원
  • 엔터프라이즈 기능 부재

적합한 상황: 프로토타입, 데모, 개발 환경, 소규모 데이터셋


pgvector: 이미 PostgreSQL을 쓰고 있다면

-- PostgreSQL 확장 설치
CREATE EXTENSION vector;

-- 벡터 컬럼이 있는 테이블 생성
CREATE TABLE documents (
    id BIGSERIAL PRIMARY KEY,
    content TEXT NOT NULL,
    source VARCHAR(255),
    page INTEGER,
    embedding vector(1536),          -- 임베딩 컬럼
    created_at TIMESTAMP DEFAULT NOW()
);

-- HNSW 인덱스 생성 (쿼리 성능 핵심)
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- m: 연결 수 (높을수록 정확, 메모리 증가)
-- ef_construction: 빌드 품질 (높을수록 좋음, 빌드 느림)

-- 코사인 유사도 검색
SELECT
    id,
    content,
    source,
    1 - (embedding <=> $1) AS similarity
FROM documents
ORDER BY embedding <=> $1  -- <=> 코사인 거리
LIMIT 5;

-- 메타데이터 필터링과 조합
SELECT id, content, similarity
FROM (
    SELECT
        id,
        content,
        1 - (embedding <=> $1) AS similarity
    FROM documents
    WHERE source = 'guide.pdf'   -- 일반 PostgreSQL 필터
) sub
WHERE similarity > 0.8           -- 유사도 임계값
ORDER BY similarity DESC
LIMIT 10;

Python에서 사용:

import psycopg2
import numpy as np
from openai import OpenAI

client_openai = OpenAI()

def semantic_search(query: str, conn, top_k: int = 5) -> list:
    """pgvector를 사용한 시맨틱 검색"""

    # 쿼리 임베딩 생성
    query_embedding = client_openai.embeddings.create(
        input=query,
        model="text-embedding-3-small"
    ).data[0].embedding

    # 벡터를 pgvector 형식으로 변환
    embedding_str = str(query_embedding)

    with conn.cursor() as cur:
        cur.execute("""
            SELECT id, content, source,
                   1 - (embedding <=> %s::vector) AS similarity
            FROM documents
            ORDER BY embedding <=> %s::vector
            LIMIT %s
        """, (embedding_str, embedding_str, top_k))

        results = cur.fetchall()
        return [
            {"id": r[0], "content": r[1], "source": r[2], "similarity": r[3]}
            for r in results
        ]

# 사용 예시
conn = psycopg2.connect("postgresql://user:pass@localhost/mydb")
results = semantic_search("인공지능 기술 동향", conn)
for r in results:
    print(f"[{r['similarity']:.3f}] {r['content'][:80]}")

장점:

  • 기존 PostgreSQL 인프라 재사용
  • ACID 트랜잭션 지원 (벡터와 일반 데이터 원자적 처리)
  • 복잡한 SQL 쿼리와 자연스러운 조합
  • 운영 비용 절감

단점:

  • 수천만 건 이상에서 전용 벡터 DB 대비 성능 떨어짐
  • 인덱스가 메모리에 상주해야 최적 성능
  • 분산 처리 제한

적합한 상황: 이미 PostgreSQL 사용 중, 데이터가 RDB와 결합 필요, 비용 절감 우선


결정 매트릭스

상황최고 선택이유
프로토타입/PoCChroma가장 쉽게 시작
PostgreSQL 팀pgvector인프라 재활용, 낮은 운영 비용
빠른 프로덕션 론칭Pinecone제로 운영, 즉시 스케일링
자체 인프라 + 대규모Weaviate 또는 Qdrant오픈소스, 완전한 제어
하이브리드 검색 필수Weaviate벡터+BM25 네이티브 지원
수십억 벡터 이상Qdrant 또는 Milvus초대규모 최적화
비용 최소화pgvector별도 DB 비용 없음

성능 비교 (동일 조건 벤치마크)

테스트 조건: 100만 개 1536차원 벡터, cosine similarity, top-5 검색

초당 쿼리 처리량 (QPS):
─────────────────────────────────────────
Pinecone Serverless:  ~2,000 QPS (자동 스케일링)
Weaviate (HNSW):      ~1,500 QPS (단일 인스턴스)
pgvector (HNSW):      ~800 QPS (단일 인스턴스, RAM에 인덱스 적재 시)
Chroma:               ~200 QPS (단일 스레드 한계)

지연시간 (p99, 100만 벡터):
─────────────────────────────────────────
Pinecone:   ~50ms
Weaviate:   ~30ms (로컬)
pgvector:   ~20ms (인덱스 완전 메모리 적재 시)
Chroma:     ~100ms+

주의: 이 수치는 환경에 따라 크게 달라진다. 실제 데이터로 벤치마크를 직접 실행할 것을 강력히 권장한다.


마이그레이션 전략

벡터 DB를 나중에 교체해야 할 경우를 대비한 추상화 레이어:

from abc import ABC, abstractmethod
import numpy as np

class VectorStoreBase(ABC):
    """벡터 DB 추상화 레이어"""

    @abstractmethod
    def upsert(self, ids: list, embeddings: list, metadatas: list) -> None:
        pass

    @abstractmethod
    def search(self, query_embedding: list, top_k: int = 5) -> list:
        pass

    @abstractmethod
    def delete(self, ids: list) -> None:
        pass


class ChromaVectorStore(VectorStoreBase):
    def __init__(self, collection_name: str):
        import chromadb
        self.client = chromadb.PersistentClient(path="./chroma_db")
        self.collection = self.client.get_or_create_collection(collection_name)

    def upsert(self, ids, embeddings, metadatas):
        self.collection.upsert(ids=ids, embeddings=embeddings, metadatas=metadatas)

    def search(self, query_embedding, top_k=5):
        results = self.collection.query(query_embeddings=[query_embedding], n_results=top_k)
        return [{"id": id_, "score": 1-dist}
                for id_, dist in zip(results["ids"][0], results["distances"][0])]

    def delete(self, ids):
        self.collection.delete(ids=ids)


class PineconeVectorStore(VectorStoreBase):
    def __init__(self, index_name: str):
        from pinecone import Pinecone
        pc = Pinecone(api_key="your-key")
        self.index = pc.Index(index_name)

    def upsert(self, ids, embeddings, metadatas):
        vectors = [{"id": id_, "values": emb, "metadata": meta}
                   for id_, emb, meta in zip(ids, embeddings, metadatas)]
        self.index.upsert(vectors=vectors)

    def search(self, query_embedding, top_k=5):
        results = self.index.query(vector=query_embedding, top_k=top_k)
        return [{"id": m.id, "score": m.score} for m in results.matches]

    def delete(self, ids):
        self.index.delete(ids=ids)


# 환경에 따라 구현체 교체 가능
def get_vector_store(provider: str) -> VectorStoreBase:
    if provider == "chroma":
        return ChromaVectorStore("my_collection")
    elif provider == "pinecone":
        return PineconeVectorStore("my-index")
    else:
        raise ValueError(f"Unknown provider: {provider}")

이 패턴을 사용하면 초반에 Chroma로 시작해 나중에 Pinecone이나 Weaviate로 마이그레이션할 때 비즈니스 로직 변경이 최소화된다.


결론

벡터 DB 선택에 정답은 없다. 상황에 따라 다르다.

빠르게 시작해야 한다면: Chroma로 프로토타입 → 요구사항 명확해지면 적합한 프로덕션 DB로 이전.

이미 PostgreSQL이 있다면: pgvector를 먼저 시도해라. 수백만 건 이하에서는 충분하고, 운영 복잡도가 가장 낮다.

팀에 운영 리소스가 없다면: Pinecone Serverless로 시작해라. 비용이 나중에 문제가 되면 그때 이전을 검토한다.

대규모 자체 인프라가 있다면: Weaviate나 Qdrant를 검토해라. 하이브리드 검색이 필요하다면 Weaviate가 단연 최고다.