pgvector & Qdrant 완벽 가이드 — RAG를 위한 벡터 DB, 임베딩 검색 실전

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 완벽 가이드