
안녕하세요! 👋
"우리 서비스에 ChatGPT 같은 AI 챗봇을 붙이고 싶은데, 어떻게 우리 회사 데이터를 학습시키지?"
이런 고민 해보신 적 있으신가요? 바로 이 문제를 해결하는 핵심 기술이 Vector Database입니다.
오늘은 AI 시대의 필수 인프라가 된 Vector DB에 대해 실무 관점에서 깊이 파헤쳐보겠습니다. 바로 시작할게요!
Vector Database는 고차원 벡터 데이터를 효율적으로 저장하고 검색하는 데 특화된 데이터베이스입니다. 최근 LLM과 AI 애플리케이션의 폭발적인 성장과 함께 필수 인프라로 자리잡았습니다.
기존 관계형 DB는 정확한 매칭(exact match)에 최적화되어 있습니다. 하지만 현대 AI 애플리케이션은 의미적 유사성(semantic similarity)을 기반으로 데이터를 검색해야 합니다.
예시로 이해하기:
1. 임베딩(Embedding)
텍스트, 이미지, 오디오 등을 고차원 벡터(숫자 배열)로 변환하는 과정입니다.
# OpenAI 임베딩 예시
from openai import OpenAI
client = OpenAI()
text = "Vector Database는 AI 시대의 필수 인프라입니다"
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
embedding = response.data[0].embedding
# [0.023, -0.145, 0.892, ...] (1536차원 벡터)
2. 유사도 검색(Similarity Search)
벡터 간의 거리를 계산하여 가장 유사한 데이터를 찾습니다.
주요 거리 측정 방식:
3. ANN (Approximate Nearest Neighbor)
수백만 개의 벡터에서 정확한 최근접 이웃을 찾으려면 너무 느립니다. Vector DB는 근사 알고리즘(HNSW, IVF 등)을 사용해 속도와 정확도를 균형있게 유지합니다.
특징:
적합한 경우:
가격: 종량제 (무료 티어 제공)
from pinecone import Pinecone
pc = Pinecone(api_key="your-api-key")
# 인덱스 생성
index = pc.Index("my-index")
# 벡터 삽입
index.upsert(vectors=[
("id1", [0.1, 0.2, 0.3], {"text": "첫 번째 문서"}),
("id2", [0.4, 0.5, 0.6], {"text": "두 번째 문서"})
])
# 검색
results = index.query(vector=[0.1, 0.2, 0.3], top_k=5)
특징:
적합한 경우:
import weaviate
from weaviate.classes.init import Auth
client = weaviate.connect_to_weaviate_cloud(
cluster_url="your-cluster-url",
auth_credentials=Auth.api_key("your-api-key")
)
# 컬렉션 생성
articles = client.collections.create(
name="Article",
vectorizer_config=weaviate.classes.config.Configure.Vectorizer.text2vec_openai()
)
# 데이터 삽입 (자동 벡터화)
articles.data.insert({
"title": "Vector DB 가이드",
"content": "Vector Database는..."
})
# 시맨틱 검색
response = articles.query.near_text(
query="벡터 데이터베이스",
limit=5
)
특징:
적합한 경우:
import chromadb
client = chromadb.Client()
# 컬렉션 생성
collection = client.create_collection(name="my_collection")
# 데이터 추가
collection.add(
documents=["Vector DB는 AI의 필수 인프라입니다", "임베딩은 텍스트를 벡터로 변환합니다"],
ids=["id1", "id2"],
metadatas=[{"source": "blog"}, {"source": "docs"}]
)
# 검색
results = collection.query(
query_texts=["AI 인프라"],
n_results=2
)
특징:
적합한 경우:
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType
# 연결
connections.connect(host="localhost", port="19530")
# 스키마 정의
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=128),
FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=500)
]
schema = CollectionSchema(fields=fields)
collection = Collection(name="articles", schema=schema)
# 인덱스 생성
collection.create_index(
field_name="embedding",
index_params={"metric_type": "L2", "index_type": "IVF_FLAT", "params": {"nlist": 128}}
)
# 검색
results = collection.search(
data=[[0.1, 0.2, ...]],
anns_field="embedding",
param={"metric_type": "L2", "params": {"nprobe": 10}},
limit=5
)
| 기준 | Pinecone | Weaviate | Chroma | Milvus |
|---|---|---|---|---|
| 설정 난이도 | ⭐ | ⭐⭐ | ⭐ | ⭐⭐⭐⭐ |
| 확장성 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 비용 | 중간~높음 | 낮음~중간 | 무료 | 낮음 |
| 프로덕션 준비도 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 커뮤니티 | 중간 | 큼 | 큼 | 큼 |
RAG는 Vector DB의 가장 대표적인 실무 활용 사례입니다. LLM에게 관련 문맥을 제공하여 더 정확하고 최신의 답변을 생성하도록 합니다.
사용자 질문
↓
임베딩 변환
↓
Vector DB 검색 → 관련 문서 K개 추출
↓
LLM 프롬프트 생성 (질문 + 문서)
↓
LLM 답변 생성
↓
사용자에게 반환
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import TextLoader
# 1. 문서 로드 및 분할
loader = TextLoader("company_docs.txt")
documents = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
splits = text_splitter.split_documents(documents)
# 2. Vector Store 생성
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory="./chroma_db"
)
# 3. Retriever 설정
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 4}
)
# 4. RAG 체인 생성
llm = ChatOpenAI(model="gpt-4", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True
)
# 5. 질문하기
query = "우리 회사의 휴가 정책은 어떻게 되나요?"
result = qa_chain.invoke({"query": query})
print(f"답변: {result['result']}")
print(f"\n참조 문서:")
for doc in result['source_documents']:
print(f"- {doc.page_content[:100]}...")
1. 청킹(Chunking) 전략
문서를 어떻게 나누느냐가 검색 품질에 큰 영향을 미칩니다.
# 기본 전략: 고정 크기
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", ".", "!", "?", ",", " ", ""]
)
# 고급 전략: 의미론적 청킹
from langchain_experimental.text_splitter import SemanticChunker
semantic_splitter = SemanticChunker(
embeddings=OpenAIEmbeddings(),
breakpoint_threshold_type="percentile" # 의미 변화 지점에서 분할
)
2. 메타데이터 활용
# 메타데이터 추가
documents = []
for doc in raw_docs:
documents.append({
"content": doc.text,
"metadata": {
"source": doc.source,
"date": doc.date,
"category": doc.category,
"author": doc.author
}
})
# 필터링된 검색
results = vectorstore.similarity_search(
query="최신 제품 업데이트",
k=5,
filter={"category": "product", "date": {"$gte": "2024-01-01"}}
)
3. 하이브리드 검색
벡터 검색과 키워드 검색을 결합하여 정확도를 높입니다.
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
# 벡터 검색 retriever
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# 키워드 검색 retriever
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 5
# 앙상블 (50:50 가중치)
ensemble_retriever = EnsembleRetriever(
retrievers=[vector_retriever, bm25_retriever],
weights=[0.5, 0.5]
)
4. Re-ranking
검색된 문서를 재정렬하여 가장 관련성 높은 문서를 우선시합니다.
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CohereRerank
# Cohere reranker 사용
compressor = CohereRerank(model="rerank-english-v2.0")
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=vector_retriever
)
# 더 정확한 검색 결과
compressed_docs = compression_retriever.get_relevant_documents(
"Vector Database 성능 최적화"
)
시나리오: 대규모 문서(FAQ, 매뉴얼, 정책)를 기반으로 고객 질문에 답변
구현:
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
# 대화 기록 저장
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True
)
# 대화형 RAG 체인
qa = ConversationalRetrievalChain.from_llm(
llm=ChatOpenAI(temperature=0),
retriever=vectorstore.as_retriever(),
memory=memory
)
# 연속 대화
response1 = qa({"question": "환불 정책이 어떻게 되나요?"})
response2 = qa({"question": "그럼 부분 환불도 가능한가요?"}) # 이전 맥락 유지
시나리오: 이커머스 상품 검색
# 상품 데이터 임베딩
products = [
{"id": 1, "name": "무선 이어폰", "description": "노이즈 캔슬링 기능이 있는 프리미엄 이어폰"},
{"id": 2, "name": "블루투스 헤드셋", "description": "장시간 사용 가능한 편안한 헤드셋"},
]
# Vector DB에 저장
for product in products:
text = f"{product['name']} {product['description']}"
embedding = get_embedding(text)
vectorstore.add_texts(
texts=[text],
metadatas=[product]
)
# 자연어 검색
results = vectorstore.similarity_search("조용한 곳에서 음악 듣기 좋은 제품", k=5)
# "노이즈 캔슬링"이라는 키워드가 없어도 관련 상품을 찾음
시나리오: 콘텐츠 기반 추천
# 사용자가 본 영화의 임베딩 평균
user_watched = ["인셉션", "인터스텔라", "매트릭스"]
user_embeddings = [get_embedding(movie) for movie in user_watched]
avg_embedding = np.mean(user_embeddings, axis=0)
# 유사한 영화 추천
recommendations = vectorstore.similarity_search_by_vector(
embedding=avg_embedding,
k=10,
filter={"genre": "SF"} # 같은 장르 내에서만
)
시나리오: 대량의 문서에서 중복/유사 콘텐츠 찾기
def find_duplicates(documents, threshold=0.95):
duplicates = []
for i, doc in enumerate(documents):
embedding = get_embedding(doc.text)
similar = vectorstore.similarity_search_by_vector(
embedding=embedding,
k=5
)
for match in similar[1:]: # 자기 자신 제외
if match.similarity > threshold:
duplicates.append((doc.id, match.id, match.similarity))
return duplicates
1. 배치 삽입
# 나쁜 예: 개별 삽입
for doc in documents:
vectorstore.add_texts([doc]) # 느림
# 좋은 예: 배치 삽입
batch_size = 100
for i in range(0, len(documents), batch_size):
batch = documents[i:i+batch_size]
vectorstore.add_texts(batch) # 훨씬 빠름
2. 인덱스 튜닝
# Milvus 예시
index_params = {
"metric_type": "L2",
"index_type": "IVF_FLAT",
"params": {"nlist": 1024} # 클러스터 수 조정
}
# 데이터 규모에 따라 조정
# - 소규모 (< 100만): nlist = 1024
# - 중규모 (100만 ~ 1000만): nlist = 4096
# - 대규모 (> 1000만): nlist = 16384
1. 캐싱
from functools import lru_cache
@lru_cache(maxsize=1000)
def search_with_cache(query: str, k: int = 5):
return vectorstore.similarity_search(query, k=k)
2. 병렬 처리
from concurrent.futures import ThreadPoolExecutor
def batch_search(queries, k=5):
with ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(
lambda q: vectorstore.similarity_search(q, k=k),
queries
))
return results
import time
from datetime import datetime
class VectorDBMonitor:
def __init__(self):
self.metrics = []
def log_search(self, query, duration, num_results):
self.metrics.append({
"timestamp": datetime.now(),
"query": query,
"duration_ms": duration * 1000,
"num_results": num_results
})
def get_stats(self):
if not self.metrics:
return {}
durations = [m["duration_ms"] for m in self.metrics]
return {
"total_searches": len(self.metrics),
"avg_duration_ms": sum(durations) / len(durations),
"p95_duration_ms": sorted(durations)[int(len(durations) * 0.95)],
"p99_duration_ms": sorted(durations)[int(len(durations) * 0.99)]
}
# 사용
monitor = VectorDBMonitor()
start = time.time()
results = vectorstore.similarity_search(query, k=5)
duration = time.time() - start
monitor.log_search(query, duration, len(results))
# 사용자별 데이터 격리
def search_with_access_control(query, user_id, k=5):
results = vectorstore.similarity_search(
query=query,
k=k * 2, # 여유있게 가져오기
filter={"allowed_users": {"$in": [user_id]}}
)
return results[:k]
1. 임베딩 모델 선택
# 비용 효율적: OpenAI text-embedding-3-small (저렴)
embeddings_cheap = OpenAIEmbeddings(model="text-embedding-3-small")
# 고성능: OpenAI text-embedding-3-large (비쌈)
embeddings_premium = OpenAIEmbeddings(model="text-embedding-3-large")
# 오픈소스: 완전 무료
from langchain.embeddings import HuggingFaceEmbeddings
embeddings_free = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
2. 차원 축소
# OpenAI 임베딩 차원 축소 (비용 절감)
response = client.embeddings.create(
model="text-embedding-3-large",
input="텍스트",
dimensions=512 # 기본 3072에서 512로 축소
)
3. 스토리지 최적화
# 사용하지 않는 오래된 데이터 정리
cutoff_date = datetime.now() - timedelta(days=365)
vectorstore.delete(
filter={"date": {"$lt": cutoff_date.isoformat()}}
)
증상: 관련 없는 문서가 검색됨
해결책:
1. 임베딩 모델 업그레이드
2. 청킹 사이즈 조정 (너무 크거나 작지 않게)
3. 하이브리드 검색 적용
4. Re-ranking 추가
증상: 응답 시간이 1초 이상
해결책:
1. 인덱스 파라미터 튜닝
2. 캐싱 적용
3. 검색 결과 수(k) 줄이기
4. 필터 최적화
증상: Out of Memory 에러
해결책:
1. 배치 크기 줄이기
2. 차원 축소
3. 디스크 기반 인덱스 사용
4. 분산 처리 (Milvus 클러스터)
Vector Database는 이제 AI 애플리케이션의 필수 인프라입니다. 이 가이드에서 다룬 내용을 정리하면:
핵심 포인트:
질문이나 피드백은 댓글로 남겨주세요! 🚀