Pinecone vs Weaviate vs Qdrant vs Chroma: 2026년 2월 기준 “성능/운영/비용”으로 고르는 벡터DB 선택 가이드
들어가며
RAG가 “데모는 쉬운데 운영이 어렵다”로 귀결되는 가장 큰 이유는 retrieval 계층이 병목이 되기 때문입니다. LLM 호출 비용/지연만 보다가, 막상 트래픽이 붙으면 벡터 검색 latency(P95), 필터링 비용, 인덱싱(ingest) 처리량, 메모리 압박이 전체 시스템 SLO를 결정합니다.
그래서 벡터DB 선택은 “기능 체크리스트”가 아니라, 워크로드(쓰기/읽기 비율, 필터 비중, 멀티테넌시, 하이브리드 필요 여부)를 먼저 고정하고 그에 맞춰야 합니다.
2026년 2월 관점에서 많이 쓰는 4종(Pinecone/Weaviate/Qdrant/Chroma)을 선택 가이드 + 성능 관점 포인트로 정리합니다.
(참고로, 벤치마크 수치는 환경에 따라 크게 흔들립니다. 아래에서는 ‘왜 그런 결과가 나오는가’에 초점을 둡니다.)
🔧 핵심 개념
1) 벡터 검색 성능을 좌우하는 4요소
- Index 타입(Flat vs HNSW)
- Flat: 정확하지만 O(N). 작은 데이터/테넌트에는 유리.
- HNSW: ANN 그래프 기반. 대규모에서 latency/throughput에 유리.
Weaviate는 flat→HNSW로 자동 전환하는 dynamic index를 제공(기본 threshold 10,000)합니다. (weaviate.io)
-
필터링 전략(“ANN 후 필터” vs “필터-aware ANN”)
실무 RAG는 거의 항상tenant_id,doc_type,created_at같은 metadata filter가 붙습니다.
Qdrant는 payload index를 별도로 만들고, 어떤 필드를 인덱싱할지/디스크에 둘지까지 튜닝할 수 있어 “필터가 많은 서비스”에서 강점이 나옵니다. (qdrant.tech) - Quantization/Compression(메모리 vs recall vs 속도 트레이드오프)
- Qdrant: scalar/binary/product quantization을 공식 지원하고, 특히 scalar는
float32 -> int8로 4x 메모리 절감 + SIMD 비교로 속도 이점이 가능합니다. (qdrant.tech) - Weaviate: PQ/SQ/RQ/BQ 등 다양한 compression 조합을 제공하며, dynamic index와 함께 “작을 땐 가볍게, 커지면 빠르게”를 설계할 수 있습니다. (docs.weaviate.io)
- (주의) Weaviate의 “default quantization”은 문서/설정 상태에 따라 오해가 생길 수 있어, 실제 배포에서는 환경변수/컬렉션 설정을 명시적으로 확인하는 습관이 필요합니다. (forum.weaviate.io)
- Qdrant: scalar/binary/product quantization을 공식 지원하고, 특히 scalar는
- 멀티테넌시 모델
- Pinecone: namespace 단위로 격리하고, “요청이 특정 namespace만 타게” 설계하여 noisy neighbor를 줄이고, 비용도 namespace 크기에 연동된다는 메시지를 강하게 가져갑니다. (docs.pinecone.io)
- Weaviate: multi-tenant + dynamic index 조합이 설계상 자연스럽습니다(작은 테넌트는 flat 유지). (weaviate.io)
2) “하이브리드 검색”이 필요한가?
- Weaviate: vector + BM25를 fusion으로 결합하는 hybrid search를 지원(가중치/전략 조정). (docs.weaviate.io)
- Pinecone: dense+sparse를 하나의 hybrid 인덱스/엔드포인트로 다루고, alpha로 keyword vs semantic 비중을 조절합니다. (pinecone.io)
결론: “RAG인데도 키워드 정확도가 중요(상품명/에러코드/약어)”하면 hybrid는 옵션이 아니라 거의 필수입니다.
💻 실전 코드
아래는 동일한 데이터(텍스트+metadata)로 4개 DB에 최소 동작 파이프라인을 맞춘 예시입니다.
(실무에서는 embedding 모델/차원/배치 크기/필터 패턴을 먼저 확정하고, 그다음 DB별 튜닝을 시작하세요.)
# Python 3.11+
# pip install qdrant-client chromadb weaviate-client pinecone
# (Pinecone는 API Key 필요, Weaviate는 서버/클라우드 엔드포인트 필요)
from typing import List, Dict, Any
import os
TEXTS = [
"Pinecone serverless namespaces are useful for multitenancy.",
"Weaviate hybrid search fuses vector and BM25 results.",
"Qdrant payload index helps fast metadata filtering.",
"Chroma PersistentClient stores data under persist_directory.",
]
METAS = [
{"tenant_id": "t1", "source": "doc", "tag": "multitenancy"},
{"tenant_id": "t1", "source": "doc", "tag": "hybrid"},
{"tenant_id": "t2", "source": "doc", "tag": "filter"},
{"tenant_id": "t2", "source": "doc", "tag": "local"},
]
# 예제용 더미 embedding (실무에서는 OpenAI/Voyage/Cohere/bge 등으로 교체)
def embed(texts: List[str], dim: int = 8) -> List[List[float]]:
# 절대 운영에 쓰지 말 것: 단순 해시 기반
out = []
for t in texts:
v = [0.0] * dim
for i, ch in enumerate(t.encode("utf-8")):
v[i % dim] += (ch % 13) / 13.0
out.append(v)
return out
vectors = embed(TEXTS, dim=8)
def demo_qdrant():
# Qdrant: 필터가 중요하면 payload index를 "초기에" 생성 권장
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
collection = "demo"
client.recreate_collection(
collection_name=collection,
vectors_config=models.VectorParams(size=8, distance=models.Distance.COSINE),
)
# payload index: tenant_id로 필터할 거면 인덱싱
client.create_payload_index(
collection_name=collection,
field_name="tenant_id",
field_schema=models.KeywordIndexParams(),
)
points = [
models.PointStruct(id=i, vector=vectors[i], payload=METAS[i] | {"text": TEXTS[i]})
for i in range(len(TEXTS))
]
client.upsert(collection, points=points)
res = client.search(
collection_name=collection,
query_vector=embed(["hybrid search"], dim=8)[0],
query_filter=models.Filter(
must=[models.FieldCondition(key="tenant_id", match=models.MatchValue(value="t1"))]
),
limit=3,
)
return [r.payload for r in res]
def demo_chroma():
# Chroma: 로컬/프로토타이핑에 매우 편함. PersistentClient는 디렉토리에 sqlite+세그먼트 저장.
import chromadb
client = chromadb.PersistentClient(path="./chroma_data") # persist_directory
col = client.get_or_create_collection("demo")
ids = [str(i) for i in range(len(TEXTS))]
col.upsert(ids=ids, documents=TEXTS, embeddings=vectors, metadatas=METAS)
# where 필터 + 쿼리 embedding
q = embed(["payload index"], dim=8)[0]
res = col.query(query_embeddings=[q], n_results=3, where={"tenant_id": "t2"})
return res["metadatas"][0]
def demo_weaviate():
# Weaviate: hybrid(BM25+vector), dynamic index, quantization 등 옵션이 풍부
# 여기서는 "vector search + filter" 예시만 최소 형태로.
import weaviate
from weaviate.classes.config import Configure, Property, DataType
client = weaviate.connect_to_local() # 로컬 weaviate 실행 중이라고 가정
try:
client.collections.delete("Demo")
except Exception:
pass
demo = client.collections.create(
"Demo",
vectorizer_config=Configure.Vectorizer.none(),
properties=[
Property(name="text", data_type=DataType.TEXT),
Property(name="tenant_id", data_type=DataType.TEXT),
Property(name="tag", data_type=DataType.TEXT),
],
# 필요시 index/quantization/hybrid(BM25) 등을 컬렉션 단위로 설정
)
with demo.batch.dynamic() as batch:
for i in range(len(TEXTS)):
batch.add_object(
properties={"text": TEXTS[i], "tenant_id": METAS[i]["tenant_id"], "tag": METAS[i]["tag"]},
vector=vectors[i],
)
q = embed(["multitenancy"], dim=8)[0]
res = demo.query.near_vector(
near_vector=q,
limit=3,
filters=weaviate.classes.query.Filter.by_property("tenant_id").equal("t1"),
)
return [o.properties for o in res.objects]
def demo_pinecone():
# Pinecone: serverless index, namespace로 테넌트 분리하는 패턴이 강력
from pinecone import Pinecone, ServerlessSpec
pc = Pinecone(api_key=os.environ["PINECONE_API_KEY"])
index_name = "demo-vecdb-2026"
if index_name not in pc.list_indexes().names():
pc.create_index(
name=index_name,
dimension=8,
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1"),
)
index = pc.Index(index_name)
# tenant_id를 namespace로 사용 (멀티테넌시)
upserts_t1 = []
upserts_t2 = []
for i in range(len(TEXTS)):
item = (str(i), vectors[i], METAS[i] | {"text": TEXTS[i]})
(upserts_t1 if METAS[i]["tenant_id"] == "t1" else upserts_t2).append(item)
if upserts_t1:
index.upsert(vectors=upserts_t1, namespace="t1")
if upserts_t2:
index.upsert(vectors=upserts_t2, namespace="t2")
q = embed(["hybrid"], dim=8)[0]
res = index.query(vector=q, top_k=3, namespace="t1", include_metadata=True)
return [m["metadata"] for m in res["matches"]]
if __name__ == "__main__":
# 실행 환경에 따라 원하는 것만 테스트
print("Qdrant:", demo_qdrant())
print("Chroma:", demo_chroma())
# print("Weaviate:", demo_weaviate())
# print("Pinecone:", demo_pinecone())
⚡ 실전 팁
1) “성능 비교”를 할 때, 먼저 질문을 바꿔라
단순 TopK latency 비교는 의미가 약합니다. 아래 3가지를 꼭 넣어야 실전과 비슷해집니다.
- Filtered query 비중(예: 70%가
tenant_id+doc_type) - ingest 패턴(배치 upsert vs 스트리밍, update/delete 빈도)
- P95/P99 + 동시성(단일 요청 ms는 예쁘게 나오기 쉽습니다)
Qdrant는 payload index를 “나중에 만들면” 업데이트가 막힐 수 있으니, 컬렉션 생성 직후 인덱스 설계 확정이 중요합니다. (qdrant.tech)
Pinecone은 쿼리/업서트에 rate/용량 제한이 걸리면 429가 나므로, 백오프/재시도 + 배치가 필수입니다. (pinecone-poc-guide.mintlify.app)
2) 멀티테넌시: “namespace 분리”가 정답인 경우가 많다
Pinecone은 namespace 단위로 데이터가 분리되고, 읽기/쓰기 요청이 특정 namespace로만 라우팅된다는 점을 멀티테넌시 핵심 가치로 설명합니다. 이 방식은
- noisy neighbor 완화
- 테넌트 offboarding 단순화
- 비용(읽기 단위)이 “전체 스캔”보다 예측 가능 같은 장점이 있습니다. (docs.pinecone.io)
반대로 “테넌트 수가 수만이고, 각 테넌트가 매우 작다”면 Weaviate의 dynamic index(작을 때 flat, 커지면 HNSW) 전략이 운영적으로 깔끔합니다. (weaviate.io)
3) Quantization은 “메모리 절약”이 아니라 “캐시 적중률” 게임
대규모에서 성능이 무너지는 가장 흔한 이유는 CPU가 아니라 메모리/NUMA/캐시 미스입니다.
- Qdrant scalar quantization은 메모리를 4배 줄여 캐시 효율을 올리고, int8 SIMD로 비교가 빨라질 수 있습니다. (qdrant.tech)
- Weaviate도 RQ/PQ/SQ/BQ 등 압축 옵션이 있고, index 타입에 따라 가능한 조합이 다릅니다. (docs.weaviate.io)
실무 팁: “recall이 0.98에서 0.97로 떨어져도 latency가 2배 좋아진다”면, RAG 전체 품질은 오히려 좋아지는 경우가 흔합니다(리랭커/LLM이 후단에서 보정).
4) Chroma는 “로컬/제품화 전 단계”에서 빛난다
Chroma의 강점은 운영 복잡도가 아니라 DX와 로컬 영속성입니다. PersistentClient는 지정한 디렉토리에 sqlite 파일과 컬렉션 세그먼트를 저장합니다. (cookbook.chromadb.dev)
대신 10M급/고동시성/분산까지 밀어붙일 계획이면, 초반부터 Qdrant/Weaviate/Pinecone 같은 “서버/클러스터 전제” 제품으로 가는 편이 시행착오가 줄어듭니다.
🚀 마무리
정리하면, 4개 중 “누가 제일 빠르냐”보다 아래처럼 고르는 게 실패 확률이 낮습니다.
- Pinecone: 완전 관리형 + serverless 운영 모델, 멀티테넌시를 namespace로 깔끔하게 가져가고 싶을 때. (아키텍처/namespace 모델이 강한 메시지) (docs.pinecone.io)
- Weaviate: hybrid(BM25+vector) + 다양한 인덱스 전략(dynamic 포함) + 압축 옵션으로 “검색 기능 자체를 제품화”할 때. (weaviate.io)
- Qdrant: metadata filtering이 핵심이거나, payload index/온디스크 옵션/quantization 등으로 “성능 튜닝 여지”를 확보하고 싶을 때. (qdrant.tech)
- Chroma: 로컬 RAG, 프로토타이핑, 단일 노드에서 빠른 반복이 최우선일 때(영속성 구조가 단순). (cookbook.chromadb.dev)
다음 학습 추천:
1) 내 워크로드로 Filtered P95 벤치마크 스크립트를 먼저 만들고(동시성 포함),
2) Qdrant payload index 설계 / Weaviate dynamic+quantization / Pinecone namespace 전략을 각각 “한 가지 가설”로 비교해보면, 마케팅 문구가 아니라 수치로 결론이 납니다.
원하시면, (1) 데이터 크기/차원(768? 1536?), (2) 필터 패턴, (3) QPS/쓰기 비율을 알려주시면 그 조건에 맞춘 벤치마크 시나리오와 튜닝 체크리스트까지 더 구체적으로 잡아드릴게요.