27장. 벡터 DB 입문
이 장의 목표 RAG 규모가 커졌을 때 어떤 벡터 DB를 어떻게 골라야 하는지 큰 그림이 잡힙니다.
Chroma·Qdrant·LanceDB 셋만 알면 대부분의 사내 RAG는 만들 수 있습니다.
27.1 왜 벡터 DB가 필요한가
26장 코드는 100개 문서까지는 잘 동작합니다. 하지만 1만 개·10만 개로 가면?
- 매번 모든 벡터를 다 비교 → 느려짐
- 메모리에 다 못 올림
- 새 문서 추가, 오래된 문서 삭제 어려움
- 메타데이터 검색 안 됨
벡터 DB는 이걸 다 해줍니다.
27.2 벡터 DB의 4가지 일
- 저장 — 청크 + 벡터 + 메타데이터
- 검색 — 가장 가까운 N개 빠르게 찾기 (ANN 알고리즘)
- 필터 — “이 부서 문서만”, “최근 6개월” 등
- 갱신 — 문서 추가·삭제·재인덱싱
27.3 ANN — Approximate Nearest Neighbor
벡터 검색의 핵심 알고리즘 가족.
정확한 검색: 모든 벡터와 거리 계산 (느림, 정확)
ANN 검색: 약간의 근사를 허용해 100~1000배 빠름
대표 알고리즘:
- HNSW (가장 흔함)
- IVF + PQ
- DiskANN
깊이 알 필요는 없습니다. 어떤 DB든 기본값으로 잘 동작합니다.
27.4 맥에서 쉽게 쓰는 벡터 DB 3가지
Chroma — 가장 입문 친화적
- 파이썬 한 줄로 시작
- SQLite 기반
- 작은 ~ 중간 규모 (수십만 청크까지)
$ pip install chromadb
import chromadb
client = chromadb.PersistentClient(path="./db")
col = client.get_or_create_collection("docs")
col.add(
documents=["문서 1 내용", "문서 2 내용"],
metadatas=[{"src": "wiki"}, {"src": "회의록"}],
ids=["d1", "d2"],
)
result = col.query(query_texts=["관련 질문"], n_results=2)
임베딩 모델을 안 지정하면 Chroma 기본 모델을 씀. 한국어 잘하려면 별도로 bge-m3 같은 걸 직접 임베딩 후 전달.
Qdrant — 사내 서버용 표준
- Rust 기반, 매우 빠름
- REST·gRPC API
- 도커로 즉시 실행
- 메타데이터 필터링 강력
- 클라우드 서비스도 제공
$ docker run -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance
q = QdrantClient(host="localhost", port=6333)
q.recreate_collection(
collection_name="docs",
vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)
LanceDB — 임베디드 + 빠름
- 파일 기반, 서버 필요 없음
- Apache Arrow 기반
- 빠른 쿼리·작은 디스크 풋프린트
- 인덱싱 자동
$ pip install lancedb
import lancedb
db = lancedb.connect("./lance_db")
tbl = db.create_table("docs", data=[
{"vector": [0.1, 0.2, ...], "text": "...", "src": "wiki"},
])
results = tbl.search([0.1, 0.2, ...]).limit(5).to_list()
27.5 세 DB 비교
| 항목 | Chroma | Qdrant | LanceDB |
|---|---|---|---|
| 진입 난이도 | 매우 쉬움 | 보통 | 쉬움 |
| 규모 | ~ 50만 | 수억 | 수백만 |
| 서버 필요 | 옵션 | ✅ | ❌ |
| 메타 필터 | 보통 | 강력 | 좋음 |
| 추천 사용처 | 프로토타입 | 사내 서비스 | 임베디드 |
선택 가이드:
- 처음 / 한 사람 / 노트북 → Chroma or LanceDB
- 사내 서비스 / 다중 사용자 → Qdrant
27.6 청킹 전략 다시 보기 (실무 버전)
고정 길이 청킹
chunks = [text[i:i+500] for i in range(0, len(text), 400)]
500자 청크, 100자 겹침.
간단하지만 문장 중간에서 잘릴 수 있음.
문장·문단 기반
import re
sentences = re.split(r'(?<=[.!?])\s+', text)
chunks = []
buf = ""
for s in sentences:
if len(buf) + len(s) < 500:
buf += " " + s
else:
chunks.append(buf)
buf = s
자연스러움 ↑
구조 기반 (Markdown / 코드)
- 헤더 단위로 자르기
- 코드는 함수 단위로
Recursive Splitter (LangChain 등)
여러 구분자(\n\n, \n, ., )를
순차로 시도하며 자릅니다.
가장 권장.
27.7 메타데이터 — RAG의 진짜 무기
벡터만 쓰지 말고 메타데이터 를 같이 저장하세요.
col.add(
documents=["..."],
metadatas=[{
"source": "wiki",
"title": "휴가 규정",
"department": "HR",
"last_updated": "2026-03-15",
"language": "ko",
}],
ids=["d1"],
)
검색 때 필터:
result = col.query(
query_texts=["휴가"],
where={"department": "HR", "language": "ko"},
n_results=3,
)
사내 검색이 잘 안 될 때 90%는 메타 필터 부재가 원인입니다.
27.8 Reranker — 한 단계 정밀화
벡터 검색은 빠르지만 종종 부정확. Reranker 모델이 1차 결과를 다시 정렬합니다.
[1차 벡터 검색] → 후보 20개
↓
[Reranker] → 상위 3~5개로 압축
↓
[LLM에 전달]
대표 모델:
BAAI/bge-reranker-v2-m3(다국어)jinaai/jina-reranker-v2-base-multilingual
# 예: sentence-transformers
from sentence_transformers import CrossEncoder
re = CrossEncoder("BAAI/bge-reranker-v2-m3")
pairs = [(query, c) for c in candidates]
scores = re.predict(pairs)
벡터 검색만 쓰는 RAG보다 체감 정확도가 크게 올라갑니다.
27.9 Hybrid Search — 벡터 + 키워드
벡터 검색이 약한 케이스:
- 사람·코드 이름 (고유명사)
- 짧은 키워드
- 숫자·코드
이때는 BM25 같은 키워드 검색과 병합.
방법:
1. 벡터 검색 → top 20
2. 키워드 검색 → top 20
3. 점수를 정규화해서 합치기 (RRF 등)
4. Reranker로 5개 압축
5. LLM에 전달
이 조합이 현업 RAG의 표준.
27.10 인덱싱 시점 vs 검색 시점
- 인덱싱: 문서를 청크화·임베딩해서 DB에 저장 → 느려도 OK (백그라운드)
- 검색: 사용자 질문을 임베딩·검색·LLM 호출 → 가능한 한 빠르게
분리해서 설계하면 운영이 쉬워집니다.
27.11 RAG 운영 체크리스트
사내 RAG 도입 시 확인할 것.
- ✅ 임베딩 모델은 한국어/다국어 강한 걸로 (bge-m3)
- ✅ 청크 300~800자
- ✅ 메타데이터 (source/department/date) 항상
- ✅ Reranker 적용
- ✅ “근거에 없으면 모른다” 시스템 프롬프트
- ✅ 출처 표시 (chunk_id 또는 title)
- ✅ 문서 갱신 파이프라인 (주기적 재인덱싱)
- ✅ 평가 셋 — 정답 알려진 질문 30~50개로 회귀 테스트
이 장에서 기억할 한 가지
RAG의 품질은 LLM이 아니라 검색에서 결정됩니다.
- 청킹 + 임베딩 + 메타 + Reranker 이 4박자가 RAG의 진짜 엔진입니다.
손으로 해볼 것
1. Chroma로 첫 RAG 만들기
$ pip install chromadb
26.9 절 코드를 Chroma로 옮겨 보세요. 같은 질문·같은 결과가 나와야 합니다.
2. 메타데이터 필터 실험
문서마다 category: "HR" / "ENG" / "FIN" 을 붙이고
질문에 카테고리 필터를 걸어서 검색해보세요.
3. Reranker 적용 (선택)
$ pip install sentence-transformers
27.8 코드를 그대로 적용해서 Reranker 적용 전후의 응답 정확도를 비교하세요.
다음 장에서는 Function Calling / Tool Use — 모델이 함수와 도구를 호출하게 만드는 패턴을 봅니다.
Agent의 전 단계입니다.