pgvector & Qdrant 완벽 가이드 — RAG를 위한 벡터 DB, 임베딩 검색 실전
이 글의 핵심
벡터 데이터베이스는 텍스트·이미지·오디오를 고차원 벡터(embedding)로 변환해 의미 기반 검색을 제공하는 DB입니다. pgvector는 PostgreSQL 확장으로 기존 RDB에 벡터 검색을 추가하고, Qdrant는 Rust 기반 전용 벡터 DB로 HNSW·payload filtering·멀티 테넌시를 네이티브 지원합니다. 2024-2026년 LLM·RAG 확산으로 두 도구 모두 프로덕션 표준이 되었습니다.
pgvector 시작
설치 (PostgreSQL)
# PostgreSQL 16+
sudo apt-get install postgresql-16-pgvector
# macOS (Homebrew)
brew install pgvector
# Docker
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=password pgvector/pgvector:pg16
확장 활성화
CREATE EXTENSION IF NOT EXISTS vector;
테이블 생성
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT,
embedding vector(1536) -- OpenAI text-embedding-3-small 차원
);
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);
vector(1536): 1536차원 벡터hnsw: HNSW 인덱스 (빠른 검색)vector_cosine_ops: cosine similarity
데이터 삽입
import openai
import psycopg2
client = openai.OpenAI()
def get_embedding(text: str) -> list[float]:
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
conn = psycopg2.connect("postgresql://user:pass@localhost/db")
cur = conn.cursor()
docs = [
"FastAPI는 Python 웹 프레임워크입니다.",
"pgvector는 PostgreSQL 벡터 확장입니다.",
"Qdrant는 Rust 기반 벡터 DB입니다.",
]
for doc in docs:
embedding = get_embedding(doc)
cur.execute(
"INSERT INTO documents (content, embedding) VALUES (%s, %s)",
(doc, embedding)
)
conn.commit()
유사도 검색
query = "Python 웹 프레임워크"
query_embedding = get_embedding(query)
cur.execute(
"""
SELECT content, 1 - (embedding <=> %s::vector) AS similarity
FROM documents
ORDER BY embedding <=> %s::vector
LIMIT 5
""",
(query_embedding, query_embedding)
)
for content, similarity in cur.fetchall():
print(f"{similarity:.3f}: {content}")
<=>: cosine distance (1 - cosine similarity)ORDER BY embedding <=> query: 가장 가까운 벡터 순
Qdrant 시작
설치
# Docker
docker run -d -p 6333:6333 -p 6334:6334 qdrant/qdrant
# 또는 바이너리 다운로드
wget https://github.com/qdrant/qdrant/releases/download/v1.10.0/qdrant-x86_64-unknown-linux-gnu.tar.gz
tar -xzf qdrant-x86_64-unknown-linux-gnu.tar.gz
./qdrant
Python 클라이언트
pip install qdrant-client
Collection 생성
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
client = QdrantClient("localhost", port=6333)
client.create_collection(
collection_name="documents",
vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
)
데이터 삽입
import openai
from qdrant_client.models import PointStruct
openai_client = openai.OpenAI()
def get_embedding(text: str) -> list[float]:
response = openai_client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
docs = [
{"id": 1, "text": "FastAPI는 Python 웹 프레임워크입니다.", "category": "python"},
{"id": 2, "text": "pgvector는 PostgreSQL 벡터 확장입니다.", "category": "db"},
{"id": 3, "text": "Qdrant는 Rust 기반 벡터 DB입니다.", "category": "db"},
]
points = [
PointStruct(
id=doc["id"],
vector=get_embedding(doc["text"]),
payload={"text": doc["text"], "category": doc["category"]}
)
for doc in docs
]
client.upsert(collection_name="documents", points=points)
검색
query = "Python 웹 프레임워크"
query_embedding = get_embedding(query)
results = client.search(
collection_name="documents",
query_vector=query_embedding,
limit=5,
)
for result in results:
print(f"{result.score:.3f}: {result.payload['text']}")
필터링
from qdrant_client.models import Filter, FieldCondition, MatchValue
results = client.search(
collection_name="documents",
query_vector=query_embedding,
query_filter=Filter(
must=[
FieldCondition(key="category", match=MatchValue(value="python"))
]
),
limit=5,
)
“python” 카테고리만 검색.
RAG 파이프라인 (LangChain + pgvector)
pip install langchain langchain-openai langchain-postgres psycopg2-binary
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_postgres import PGVector
from langchain.chains import RetrievalQA
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 1. 문서 분할
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
docs = text_splitter.create_documents([
"FastAPI는 Python의 고성능 웹 프레임워크입니다...",
"pgvector는 PostgreSQL에 벡터 검색을 추가합니다...",
])
# 2. 임베딩 + 저장
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
CONNECTION_STRING = "postgresql://user:pass@localhost/db"
vectorstore = PGVector.from_documents(
documents=docs,
embedding=embeddings,
collection_name="documents",
connection=CONNECTION_STRING,
)
# 3. RAG 체인
llm = ChatOpenAI(model="gpt-4o-mini")
qa = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
)
# 4. 질문
answer = qa.invoke("FastAPI의 장점은?")
print(answer["result"])
RAG 파이프라인 (Qdrant)
pip install langchain langchain-qdrant
from langchain_qdrant import Qdrant
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.chains import RetrievalQA
from qdrant_client import QdrantClient
client = QdrantClient("localhost", port=6333)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Qdrant(
client=client,
collection_name="documents",
embeddings=embeddings,
)
llm = ChatOpenAI(model="gpt-4o-mini")
qa = RetrievalQA.from_chain_type(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
)
answer = qa.invoke("Qdrant의 특징은?")
print(answer["result"])
Hybrid Search (키워드 + 벡터)
Qdrant
from qdrant_client.models import ScoredPoint, SearchRequest, Fusion
# 벡터 검색 + 키워드 검색 조합
results = client.query_points(
collection_name="documents",
prefetch=[
# 벡터 검색
SearchRequest(vector=query_embedding, limit=10),
],
query=Fusion.RRF, # Reciprocal Rank Fusion
limit=5,
)
멀티 벡터 (dense + sparse)
from qdrant_client.models import VectorParams, SparseVectorParams
client.create_collection(
collection_name="hybrid",
vectors_config={
"dense": VectorParams(size=1536, distance=Distance.COSINE),
},
sparse_vectors_config={
"sparse": SparseVectorParams(),
},
)
# 삽입 시 dense + sparse 벡터 모두 제공
client.upsert(
collection_name="hybrid",
points=[
PointStruct(
id=1,
vector={
"dense": dense_embedding,
"sparse": {"indices": [0, 5, 10], "values": [0.8, 0.6, 0.3]},
},
payload={"text": "..."},
)
],
)
배치 처리
pgvector
from psycopg2.extras import execute_values
embeddings = [get_embedding(doc) for doc in docs]
data = [(doc, emb) for doc, emb in zip(docs, embeddings)]
execute_values(
cur,
"INSERT INTO documents (content, embedding) VALUES %s",
data,
)
Qdrant
client.upsert(
collection_name="documents",
points=[
PointStruct(id=i, vector=emb, payload={"text": doc})
for i, (doc, emb) in enumerate(zip(docs, embeddings))
],
)
성능 최적화
pgvector
-- HNSW 파라미터 튜닝
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 검색 시
SET hnsw.ef_search = 100;
m: 그래프 연결 수 (높을수록 정확하지만 느림)ef_construction: 인덱스 구축 품질ef_search: 검색 품질 (높을수록 정확하지만 느림)
Qdrant
from qdrant_client.models import HnswConfigDiff, OptimizersConfigDiff
client.update_collection(
collection_name="documents",
hnsw_config=HnswConfigDiff(m=16, ef_construct=100),
optimizers_config=OptimizersConfigDiff(
indexing_threshold=20000, # 메모리 threshold
),
)
Docker Compose (Qdrant + PostgreSQL)
version: '3.8'
services:
postgres:
image: pgvector/pgvector:pg16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: vectordb
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
qdrant:
image: qdrant/qdrant
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_storage:/qdrant/storage
volumes:
pgdata:
qdrant_storage:
트러블슈팅
pgvector: 인덱스가 느림
- 벡터 수가 적으면(<10k) 인덱스 안 씀 → seq scan
ef_construction높여서 재생성
Qdrant: OOM
indexing_threshold낮추기on_disk옵션으로 디스크 사용
임베딩 차원 불일치
- 모델 변경 시 DB 스키마도 변경 필요
- OpenAI
text-embedding-3-large는 3072차원
검색 결과 부정확
ef_search·m높이기- 더 나은 임베딩 모델 사용
- Hybrid search (키워드 + 벡터) 조합
체크리스트
- pgvector 또는 Qdrant 설치
- 임베딩 모델 선택 (OpenAI·Cohere·HuggingFace)
- HNSW 인덱스 생성
- 배치 임베딩 파이프라인
- RAG 체인 구현 (LangChain)
- 필터링·메타데이터 활용
- 성능 튜닝 (ef_search 등)
- 프로덕션 배포 (Docker·Kubernetes)
마무리
pgvector와 Qdrant는 RAG·의미 검색·추천 시스템의 핵심 인프라입니다. pgvector는 PostgreSQL의 안정성·SQL 호환성으로 중소규모 프로젝트에 최적이고, Qdrant는 대규모·멀티 테넌시·복잡한 필터링이 필요할 때 강력합니다. 2026년 현재 LLM 프로덕션 배포의 90%가 벡터 DB를 사용하고, OpenAI·Anthropic·Cohere API와의 조합이 표준 패턴입니다. RAG를 시작한다면 pgvector로 빠르게 프로토타입을 만들고, 규모가 커지면 Qdrant로 전환하는 전략을 권장합니다.
관련 글
- OpenAI API 완벽 가이드
- LangChain 완벽 가이드
- RAG 완벽 가이드
- PostgreSQL 완벽 가이드