본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 25. · 3 Views
벡터 데이터베이스 기초 완벽 가이드
RAG 시스템의 핵심인 벡터 데이터베이스를 처음부터 차근차근 배워봅니다. ChromaDB, Pinecone 등 주요 벡터 DB 비교부터 HNSW, IVF 같은 인덱싱 알고리즘까지, 실무에 바로 적용할 수 있는 실전 지식을 담았습니다.
목차
1. 벡터 DB의 필요성
어느 날 이개발 씨는 회사에서 챗봇 프로젝트를 맡게 되었습니다. "사용자 질문에 맞는 답변을 우리 회사 문서에서 찾아서 보여주면 돼요." 팀장님의 설명은 간단했지만, 막상 구현하려니 막막했습니다.
수천 개의 문서에서 어떻게 관련된 내용을 빠르게 찾아낼 수 있을까요?
벡터 데이터베이스는 텍스트, 이미지, 음성 같은 데이터를 숫자 배열(벡터)로 변환해 저장하고, 의미적으로 유사한 데이터를 빠르게 검색하는 특수한 데이터베이스입니다. 기존 관계형 DB가 정확한 매칭을 찾는다면, 벡터 DB는 의미상 비슷한 것을 찾아냅니다.
RAG(Retrieval Augmented Generation) 시스템에서 필수적인 기술입니다.
다음 코드를 살펴봅시다.
# 일반 DB vs 벡터 DB 검색 비교 예제
import chromadb
# ChromaDB 클라이언트 초기화
client = chromadb.Client()
collection = client.create_collection("company_docs")
# 문서를 벡터로 변환해서 저장 (자동 임베딩)
collection.add(
documents=["파이썬은 객체지향 언어입니다", "자바는 정적 타입 언어입니다"],
ids=["doc1", "doc2"]
)
# 의미 기반 검색 - "프로그래밍 언어"라는 정확한 단어 없어도 찾음
results = collection.query(query_texts=["프로그래밍 언어 특징"], n_results=2)
print(results) # 두 문서 모두 관련도 높게 검색됨
이개발 씨는 먼저 가장 단순한 방법을 떠올렸습니다. 키워드 검색은 어떨까요?
사용자가 "휴가 신청 방법"을 물으면 문서에서 "휴가"와 "신청"이라는 단어를 찾으면 되지 않을까요? 하지만 곧 문제를 발견했습니다.
사용자가 "연차 쓰려면 어떻게 해요?"라고 물으면 어떻게 할까요? "휴가"라는 단어가 없으니 찾을 수 없습니다.
같은 의미인데 표현만 다른 것뿐인데 말이죠. 선배 개발자 박시니어 씨가 이개발 씨의 고민을 듣고는 빙그레 웃었습니다.
"바로 그 문제를 해결하려고 벡터 데이터베이스가 나온 거예요." 벡터 데이터베이스란 무엇일까요? 쉽게 비유하자면, 벡터 DB는 마치 도서관 사서가 책의 내용을 이해하고 분류하는 것과 같습니다. 일반 데이터베이스가 책 제목으로만 찾는다면, 벡터 DB는 책의 주제와 내용을 이해해서 비슷한 책들을 함께 찾아줍니다.
기술적으로 설명하면, 텍스트를 숫자 배열로 변환하는 과정을 임베딩이라고 합니다. 예를 들어 "고양이가 좋아요"라는 문장이 [0.2, 0.5, 0.8, ...] 같은 768차원 벡터로 변환됩니다.
비슷한 의미의 문장은 비슷한 벡터 값을 갖게 됩니다. 왜 기존 데이터베이스로는 안 될까요? 이개발 씨가 처음 생각했던 MySQL이나 PostgreSQL 같은 관계형 데이터베이스는 정확한 매칭에 특화되어 있습니다.
WHERE 절에 "휴가"라고 쓰면 정확히 "휴가"라는 단어가 있는 행만 찾습니다. 동의어 사전을 만들면 되지 않을까요?
"휴가", "연차", "휴일"을 모두 등록해두는 거죠. 하지만 한국어에는 같은 의미를 표현하는 방법이 수백 가지입니다.
"쉬는 날", "off", "브레이크" 등 끝이 없습니다. 더 큰 문제는 문맥에 따라 의미가 달라진다는 점입니다.
벡터 DB의 등장 바로 이런 한계를 극복하기 위해 벡터 데이터베이스가 개발되었습니다. 벡터 DB를 사용하면 의미 기반 검색이 가능해집니다.
"휴가 신청"과 "연차 쓰는 법"이 다른 표현이지만 의미상 유사하다는 것을 자동으로 인식합니다. 또한 다국어 검색도 자연스럽게 됩니다.
영어로 된 질문에 한국어 문서를 찾아주는 것도 가능합니다. 무엇보다 확장성이 뛰어납니다.
수백만 개의 문서 중에서도 밀리초 단위로 유사한 문서를 찾아냅니다. 이것이 RAG 시스템의 핵심입니다.
코드로 직접 체험해보기 위의 예제 코드를 살펴보겠습니다. 먼저 ChromaDB 클라이언트를 초기화하고 컬렉션을 만듭니다.
컬렉션은 관계형 DB의 테이블과 비슷한 개념입니다. add 메서드로 문서를 추가하면 내부적으로 자동으로 임베딩 벡터로 변환되어 저장됩니다.
핵심은 query 메서드입니다. "프로그래밍 언어 특징"이라는 질문에 대해 문서에는 "프로그래밍 언어"라는 정확한 단어가 없지만, 의미상 관련된 두 문서를 모두 찾아냅니다.
벡터 간의 코사인 유사도를 계산해서 가장 가까운 문서를 반환하는 것입니다. 실무에서의 활용 사례 실제 기업들은 어떻게 활용할까요?
고객 지원 챗봇을 예로 들어봅시다. 고객이 "환불받고 싶어요"라고 물으면, 벡터 DB는 "환불 정책", "반품 절차", "취소 방법" 같은 관련 문서들을 모두 찾아냅니다.
정확히 "환불"이라는 단어가 없는 문서도 의미상 관련이 있으면 검색됩니다. 네이버, 카카오 같은 기업들도 사내 문서 검색 시스템에 벡터 DB를 적극 활용하고 있습니다.
직원이 질문하면 수만 개의 사내 위키, 메일, 문서 중에서 가장 관련도 높은 정보를 찾아주는 것이죠. 주의할 점 하지만 벡터 DB가 만능은 아닙니다.
초보 개발자들이 흔히 하는 실수는 모든 검색을 벡터 DB로 하려는 것입니다. 예를 들어 "2024년 1월 매출"처럼 정확한 날짜와 숫자가 중요한 검색은 일반 DB가 훨씬 효율적입니다.
벡터 검색은 의미 기반이라 "2024년"과 "2023년"을 구분하지 못할 수 있습니다. 따라서 하이브리드 검색을 사용하는 것이 좋습니다.
정확한 필터링은 관계형 DB로, 의미 기반 검색은 벡터 DB로 처리하는 것입니다. 정리 이개발 씨는 박시니어 씨의 설명을 듣고 눈이 번쩍 뜨였습니다.
"아, 그래서 요즘 ChatGPT 같은 서비스들이 우리 문서를 학습시킨다고 할 때 벡터 DB를 쓰는 거군요!" 벡터 데이터베이스는 AI 시대의 필수 기술입니다. 단순한 키워드 매칭을 넘어 의미를 이해하는 검색이 가능해집니다.
여러분도 다음 프로젝트에서 벡터 DB를 활용해 보세요.
실전 팁
💡 - 정확한 매칭이 필요하면 일반 DB, 의미 검색이 필요하면 벡터 DB를 사용하세요
- 대부분의 벡터 DB는 자동 임베딩 기능을 제공하므로 직접 벡터 변환할 필요 없습니다
- RAG 시스템 구축 시 벡터 DB는 필수입니다
2. ChromaDB Pinecone Weaviate Qdrant 비교
이개발 씨는 벡터 DB의 필요성을 깨달았지만, 이제 또 다른 고민이 생겼습니다. 구글에서 검색하니 ChromaDB, Pinecone, Weaviate, Qdrant 등 이름도 생소한 제품들이 쏟아집니다.
"도대체 뭘 선택해야 하지?" 각 제품의 장단점을 비교해보지 않으면 나중에 후회할 것 같았습니다.
ChromaDB는 로컬 개발에 최적화된 경량 벡터 DB이고, Pinecone은 관리형 클라우드 서비스로 편리하지만 비용이 높습니다. Weaviate는 오픈소스로 확장성이 뛰어나며, Qdrant는 Rust로 작성되어 성능이 우수합니다.
프로젝트 규모와 예산에 따라 적합한 벡터 DB를 선택해야 합니다.
다음 코드를 살펴봅시다.
# 각 벡터 DB 클라이언트 초기화 비교
import chromadb
# from pinecone import Pinecone
# import weaviate
# from qdrant_client import QdrantClient
# ChromaDB - 로컬에서 바로 실행 가능
chroma_client = chromadb.Client()
collection = chroma_client.create_collection("test")
collection.add(documents=["안녕하세요"], ids=["1"])
results = collection.query(query_texts=["반갑습니다"], n_results=1)
# Pinecone - API 키 필요, 클라우드 전용
# pc = Pinecone(api_key="your-api-key")
# index = pc.Index("test-index")
# Qdrant - 로컬/클라우드 모두 가능
# qdrant = QdrantClient(":memory:") # 메모리 모드
박시니어 씨가 이개발 씨의 화면을 보더니 고개를 끄덕였습니다. "벡터 DB도 종류가 많죠.
각각 특징이 달라서 상황에 맞게 선택해야 해요." ChromaDB - 입문자의 친구 ChromaDB는 마치 SQLite와 같은 존재입니다. 별도 서버 설치 없이 pip로 라이브러리만 설치하면 바로 사용할 수 있습니다.
가장 큰 장점은 간편함입니다. 위 코드처럼 몇 줄이면 벡터 검색이 작동합니다.
자동 임베딩 기능도 제공해서 OpenAI나 HuggingFace 모델을 쉽게 연동할 수 있습니다. 로컬 파일 시스템에 데이터를 저장하므로 프로토타입 개발이나 학습용으로 완벽합니다.
단점도 명확합니다. 확장성이 제한적입니다.
수백만 개 이상의 데이터를 다루거나 동시 사용자가 많으면 성능이 떨어집니다. 또한 분산 처리를 지원하지 않아 대규모 서비스에는 부적합합니다.
이개발 씨처럼 처음 벡터 DB를 배우거나 작은 프로젝트를 시작할 때 최고의 선택입니다. Pinecone - 편리함의 대가 Pinecone은 완전 관리형 클라우드 서비스입니다.
AWS나 GCP처럼 회원가입하고 API 키를 받아 바로 사용합니다. 관리 부담이 없다는 것이 최대 장점입니다.
서버 설정, 백업, 모니터링, 스케일링 등을 Pinecone이 알아서 처리합니다. 개발자는 코드만 작성하면 됩니다.
또한 수억 개의 벡터도 밀리초 단위로 검색할 수 있는 성능을 보장합니다. 하지만 비용이 만만치 않습니다.
무료 플랜은 매우 제한적이고, 실서비스 수준으로 사용하려면 월 수십만 원 이상 지불해야 합니다. 또한 클라우드 전용이라 온프레미스 환경에서는 사용할 수 없습니다.
스타트업이 빠르게 MVP를 만들거나 인프라 관리 인력이 부족한 팀에게 적합합니다. Weaviate - 오픈소스의 힘 Weaviate는 Go 언어로 작성된 오픈소스 벡터 데이터베이스입니다.
Docker로 쉽게 설치할 수 있고, Kubernetes 클러스터에도 배포 가능합니다. 확장성과 유연성이 뛰어납니다.
여러 대의 서버로 분산 처리가 가능하고, 스키마를 정의해 구조화된 데이터와 벡터를 함께 저장할 수 있습니다. GraphQL API를 제공해 복잡한 쿼리도 직관적으로 작성할 수 있습니다.
또한 멀티모달을 지원합니다. 텍스트뿐 아니라 이미지, 오디오 벡터도 함께 저장하고 검색할 수 있습니다.
예를 들어 이미지로 검색하면 유사한 이미지와 관련 텍스트를 함께 찾아줍니다. 단점은 학습 곡선이 가파르다는 것입니다.
설정 옵션이 많고 개념도 복잡해서 초보자에게는 어렵습니다. 또한 직접 인프라를 관리해야 하므로 DevOps 지식이 필요합니다.
중대형 서비스를 직접 운영하고 싶거나 멀티모달 검색이 필요한 경우 좋은 선택입니다. Qdrant - 속도의 승리자 Qdrant는 Rust 언어로 작성되어 메모리 효율과 처리 속도가 매우 우수합니다.
성능이 최대 강점입니다. 벤치마크에서 다른 벡터 DB들보다 2-3배 빠른 검색 속도를 보여줍니다.
또한 필터링과 벡터 검색을 동시에 수행할 때 특히 효율적입니다. 사용법도 비교적 간단합니다.
Python 클라이언트가 잘 만들어져 있고, Docker로 쉽게 실행할 수 있습니다. 로컬 모드와 서버 모드를 모두 지원해 개발부터 배포까지 유연하게 사용 가능합니다.
오픈소스이면서도 클라우드 서비스도 제공합니다. Pinecone처럼 관리형 서비스를 원하면 Qdrant Cloud를 쓸 수 있고, 직접 운영하려면 오픈소스 버전을 쓰면 됩니다.
단점은 상대적으로 생태계가 작다는 것입니다. Weaviate나 Pinecone에 비해 커뮤니티가 작고 레퍼런스가 적습니다.
높은 성능이 중요하거나 하이브리드 검색(필터 + 벡터)을 많이 사용하는 프로젝트에 추천합니다. 어떻게 선택할까? 박시니어 씨가 정리해줬습니다.
"학습 목적이거나 작은 프로젝트면 ChromaDB, 빠르게 서비스를 만들고 인프라 걱정 없이 가려면 Pinecone, 직접 운영하면서 확장성 확보하려면 Weaviate나 Qdrant를 쓰세요." 이개발 씨는 회사 프로젝트 요구사항을 다시 살펴봤습니다. 초기 사용자가 많지 않고 온프레미스 서버에 배포해야 한다는 조건이 있었습니다.
"일단 ChromaDB로 시작해서 빠르게 프로토타입을 만들고, 나중에 사용자가 늘어나면 Qdrant로 마이그레이션하면 되겠네요." 실무 의사결정 기준 실제로 기술 선택을 할 때는 다음을 고려하세요. 데이터 규모는 얼마나 될까요?
1만 개 이하면 ChromaDB, 100만 개 이상이면 Pinecone이나 Qdrant가 적합합니다. 예산은 충분한가요?
무료나 저비용으로 시작하려면 오픈소스를 선택하세요. 팀에 인프라 관리 역량이 있나요?
없다면 Pinecone 같은 관리형 서비스가 나을 수 있습니다. 정리 결국 정답은 없습니다.
각 벡터 DB마다 장단점이 명확하므로 프로젝트 상황에 맞춰 선택하면 됩니다. 처음에는 ChromaDB로 빠르게 학습하고, 실제 서비스는 요구사항에 따라 다른 제품으로 전환하는 것도 좋은 전략입니다.
실전 팁
💡 - 프로토타입은 ChromaDB, 실서비스는 요구사항에 맞춰 Pinecone/Weaviate/Qdrant 선택
- 벡터 DB 간 마이그레이션은 생각보다 어렵지 않으니 처음 선택에 너무 고민하지 마세요
- 대부분 무료 플랜이나 오픈소스 버전을 제공하니 직접 테스트해보고 결정하세요
3. 인덱싱 알고리즘 HNSW IVF
프로토타입이 완성되고 이개발 씨는 뿌듯했습니다. 하지만 테스트 데이터를 10만 개로 늘리자 검색 속도가 급격히 느려졌습니다.
1초가 넘게 걸리는 검색도 있었습니다. 팀장님이 걱정스러운 표정으로 말했습니다.
"실제로는 100만 개 이상의 문서를 검색해야 하는데 괜찮을까요?"
**HNSW(Hierarchical Navigable Small World)**는 그래프 기반 알고리즘으로 빠른 검색 속도를 제공하지만 메모리를 많이 사용합니다. **IVF(Inverted File Index)**는 클러스터링 기반으로 메모리 효율적이지만 정확도가 다소 낮습니다.
벡터 DB의 성능은 인덱싱 알고리즘 선택에 크게 좌우되므로 속도와 정확도, 메모리 사이의 트레이드오프를 이해해야 합니다.
다음 코드를 살펴봅시다.
import chromadb
from chromadb.config import Settings
# HNSW 인덱스 설정 (기본값)
client = chromadb.Client(Settings(
chroma_db_impl="duckdb+parquet",
anonymized_telemetry=False
))
collection = client.create_collection(
name="hnsw_index",
metadata={"hnsw:space": "cosine"} # 코사인 유사도 사용
)
# 대량 데이터 추가
documents = [f"문서 {i} 내용" for i in range(10000)]
ids = [f"doc_{i}" for i in range(10000)]
collection.add(documents=documents, ids=ids)
# 빠른 검색 - HNSW 그래프 탐색
results = collection.query(query_texts=["검색어"], n_results=10)
박시니어 씨가 이개발 씨의 성능 테스트 결과를 보더니 고개를 끄덕였습니다. "예상했던 문제네요.
벡터 검색의 핵심은 인덱싱 알고리즘입니다." 왜 인덱싱이 필요할까? 벡터 검색의 원리를 생각해봅시다. 사용자가 질문하면 질문을 벡터로 변환하고, 데이터베이스에 저장된 모든 벡터와 거리를 계산해서 가장 가까운 것을 찾습니다.
문제는 벡터가 많아질수록 계산량이 선형적으로 증가한다는 것입니다. 100만 개의 벡터가 있다면 100만 번의 거리 계산이 필요합니다.
각 벡터가 768차원이면 매번 768개의 숫자를 비교해야 하죠. 마치 정렬되지 않은 배열에서 원소를 찾는 것과 같습니다.
O(n) 시간 복잡도로는 실시간 서비스가 불가능합니다. 바로 여기서 인덱싱 알고리즘이 등장합니다.
HNSW - 고속도로 같은 그래프 HNSW는 벡터들을 계층적 그래프로 구성합니다. 비유하자면 도시 간 이동할 때 고속도로를 타고 빠르게 이동한 뒤 목적지 근처에서 지방도로로 내려가는 것과 같습니다.
최상위 레이어에는 성긴 그래프가 있어 빠르게 대략적인 영역으로 이동합니다. 그다음 레이어에서는 좀 더 촘촘한 그래프로 범위를 좁혀갑니다.
최하위 레이어에서 정확한 최근접 이웃을 찾습니다. 속도가 매우 빠릅니다.
100만 개 벡터에서도 밀리초 단위로 검색합니다. 정확도도 높아서 실제 최근접 이웃을 거의 놓치지 않습니다.
그래서 Qdrant, Weaviate 같은 고성능 벡터 DB들이 기본 알고리즘으로 채택했습니다. 단점은 메모리 사용량입니다.
그래프를 메모리에 유지해야 하므로 원본 벡터 데이터 외에 추가 메모리가 필요합니다. 벡터 개수가 많을수록 메모리 부담이 커집니다.
또한 추가 비용도 고려해야 합니다. 새로운 벡터를 추가할 때 그래프를 업데이트해야 하므로 삽입 속도가 상대적으로 느립니다.
IVF - 도서관의 분류 시스템 IVF는 완전히 다른 접근입니다. 먼저 벡터들을 클러스터로 그룹화합니다.
마치 도서관에서 책을 주제별로 분류하는 것과 같습니다. 검색할 때는 먼저 질문 벡터가 어느 클러스터에 가까운지 찾습니다.
그다음 해당 클러스터 내부의 벡터들만 검사합니다. 전체를 뒤지는 대신 일부만 보는 것이죠.
메모리 효율이 좋습니다. 클러스터 중심점만 메모리에 올리면 되고, 실제 벡터는 디스크에 저장할 수 있습니다.
또한 클러스터 개수를 조절해서 속도와 정확도를 조정할 수 있습니다. 단점은 정확도 손실입니다.
클러스터 경계 근처의 벡터는 놓칠 수 있습니다. 예를 들어 실제 최근접 벡터가 다른 클러스터에 있으면 찾지 못합니다.
이를 보완하려고 여러 클러스터를 검사하면 속도가 느려집니다. 실제 코드에서는? 위의 ChromaDB 예제를 보면 HNSW가 기본 설정입니다.
metadata에 "hnsw:space"를 지정해 거리 메트릭을 선택할 수 있습니다. "cosine"은 코사인 유사도, "l2"는 유클리드 거리를 의미합니다.
대부분의 벡터 DB는 여러 인덱스 옵션을 제공합니다. Qdrant는 HNSW 외에 디스크 기반 인덱스도 지원하고, Pinecone은 자체 개발한 인덱싱 알고리즘을 사용합니다.
언제 무엇을 선택할까? 박시니어 씨가 조언했습니다. "메모리가 충분하고 최고 속도가 필요하면 HNSW를 쓰세요.
ChromaDB나 Qdrant의 기본 설정이 바로 HNSW입니다." "반대로 수억 개 이상의 대규모 데이터에서 메모리 비용을 줄이고 싶으면 IVF나 다른 클러스터링 기반 알고리즘을 고려하세요. Faiss 라이브러리가 다양한 IVF 변형을 제공합니다." 이개발 씨는 자신의 프로젝트를 분석했습니다.
100만 개 문서, 평균 검색 응답시간 100ms 이하 목표. 서버 메모리는 32GB.
"HNSW로 충분하겠네요. ChromaDB 기본 설정 그대로 사용하면 되겠습니다." 고급 주제 - 하이브리드 인덱스 최신 벡터 DB들은 여러 인덱스를 조합합니다.
자주 검색되는 핫 데이터는 HNSW로 메모리에, 오래된 콜드 데이터는 IVF로 디스크에 저장하는 식입니다. 또한 양자화(Quantization) 기법을 사용해 메모리를 줄입니다.
768차원 float32 벡터를 int8로 변환하면 메모리가 4분의 1로 줄어듭니다. 정확도는 약간 떨어지지만 실용적으로는 충분한 경우가 많습니다.
정리 인덱싱 알고리즘은 벡터 DB의 성능을 결정하는 핵심입니다. HNSW는 빠르고 정확하지만 메모리를 많이 쓰고, IVF는 메모리 효율적이지만 정확도가 낮습니다.
대부분의 프로젝트에서는 기본 설정(HNSW)으로 충분합니다. 성능 문제가 생기면 그때 인덱스 파라미터를 튜닝하거나 다른 알고리즘을 시도해도 늦지 않습니다.
실전 팁
💡 - 초보자는 HNSW 기본 설정으로 시작하세요. 대부분 잘 작동합니다
- 메모리 부족하면 양자화를 먼저 고려하세요. 인덱스 변경보다 쉽습니다
- 성능 튜닝이 필요하면 벤치마크를 돌려보고 데이터로 의사결정하세요
4. 실습 ChromaDB 구축
이론은 충분히 배웠습니다. 이제 이개발 씨는 직접 손을 움직여볼 시간입니다.
"실제로 회사 문서를 넣고 검색이 되는지 확인해봐야겠어요." 박시니어 씨가 옆에서 지켜보며 실습을 도와주기로 했습니다. 첫 번째 목표는 간단합니다.
ChromaDB를 설치하고 샘플 데이터로 검색이 잘 되는지 확인하는 것입니다.
ChromaDB는 pip 한 줄로 설치되며, 별도 서버 없이 바로 사용할 수 있는 임베디드 벡터 데이터베이스입니다. 컬렉션을 만들고 문서를 추가하면 자동으로 임베딩 벡터로 변환되어 저장됩니다.
query 메서드로 의미 기반 검색을 수행하고, 메타데이터 필터링으로 정확도를 높일 수 있습니다.
다음 코드를 살펴봅시다.
# ChromaDB 설치: pip install chromadb
import chromadb
from chromadb.config import Settings
# 영구 저장소 설정 (파일 시스템)
client = chromadb.PersistentClient(path="./chroma_db")
# 컬렉션 생성 (이미 있으면 가져오기)
collection = client.get_or_create_collection(
name="company_docs",
metadata={"hnsw:space": "cosine"}
)
# 문서와 메타데이터 추가
collection.add(
documents=["휴가 신청은 인사팀에 문의하세요", "급여 문의는 회계팀입니다"],
metadatas=[{"category": "hr"}, {"category": "finance"}],
ids=["hr_001", "fin_001"]
)
# 의미 기반 검색
results = collection.query(
query_texts=["연차 어떻게 써요?"],
n_results=1,
where={"category": "hr"} # 메타데이터 필터
)
print(results['documents']) # [['휴가 신청은 인사팀에 문의하세요']]
이개발 씨는 새로운 파이썬 가상환경을 만들고 시작했습니다. "먼저 깨끗한 환경에서 테스트해봐야겠어요." 설치와 초기 설정 ChromaDB 설치는 정말 간단합니다.
터미널에서 pip install chromadb 한 줄이면 끝입니다. 별도로 MySQL이나 MongoDB처럼 서버를 띄울 필요가 없습니다.
중요한 선택은 저장 방식입니다. chromadb.Client()는 메모리 모드로, 프로그램을 종료하면 데이터가 사라집니다.
테스트용으로는 편하지만 실제 서비스에는 부적합합니다. chromadb.PersistentClient(path="./chroma_db")는 파일 시스템에 데이터를 저장합니다.
path에 지정한 디렉토리에 SQLite 데이터베이스와 벡터 인덱스 파일이 생성됩니다. 프로그램을 재시작해도 데이터가 유지됩니다.
컬렉션 - 테이블과 비슷한 개념 컬렉션은 관계형 DB의 테이블처럼 데이터를 논리적으로 분리하는 단위입니다. 회사 문서용 컬렉션, 고객 문의용 컬렉션 등을 따로 만들 수 있습니다.
get_or_create_collection은 매우 편리한 메서드입니다. 컬렉션이 없으면 새로 만들고, 이미 있으면 기존 것을 가져옵니다.
덕분에 코드를 여러 번 실행해도 오류가 나지 않습니다. metadata에 인덱스 설정을 지정할 수 있습니다.
"hnsw:space"는 거리 계산 방식을 의미합니다. "cosine"은 방향의 유사도를, "l2"는 절대적인 거리를 측정합니다.
대부분의 텍스트 검색에는 "cosine"이 적합합니다. 문서 추가 - 자동 임베딩의 마법 add 메서드가 핵심입니다.
documents에 원본 텍스트를 넣으면 ChromaDB가 자동으로 임베딩 벡터로 변환합니다. 기본적으로는 sentence-transformers 라이브러리의 경량 모델을 사용합니다.
ids는 각 문서의 고유 식별자입니다. 나중에 업데이트하거나 삭제할 때 사용합니다.
중복된 id로 추가하면 기존 문서를 덮어씁니다. metadatas는 추가 정보를 저장합니다.
문서 카테고리, 작성일, 작성자 등을 넣을 수 있습니다. 이 정보는 나중에 필터링에 활용됩니다.
벡터 검색과 메타데이터 필터를 조합하면 매우 강력합니다. 검색 - 의미를 이해하는 쿼리 query 메서드로 검색합니다.
query_texts에 검색어를 넣으면 됩니다. "연차 어떻게 써요?"라는 질문에 "휴가 신청"이라는 답변을 찾아냅니다.
두 문장에 공통 단어가 거의 없지만 의미상 관련되어 있기 때문입니다. n_results는 반환할 결과 개수입니다.
1을 넣으면 가장 유사한 문서 1개만 가져옵니다. 보통 3~10 사이의 값을 사용합니다.
where 파라미터가 매우 유용합니다. 메타데이터 기반 필터링입니다.
"category"가 "hr"인 문서만 검색하므로 재무 관련 문서는 제외됩니다. 이렇게 하이브리드 검색을 쉽게 구현할 수 있습니다.
결과 해석 results는 딕셔너리 형태로 반환됩니다. 'documents'에는 원본 텍스트가, 'ids'에는 문서 ID가, 'distances'에는 유사도 점수가 들어있습니다.
거리 값이 작을수록 유사합니다. 코사인 유사도를 사용하면 0에 가까울수록 거의 같은 의미이고, 2에 가까울수록 완전히 다른 의미입니다.
실전 팁 - 임베딩 모델 변경 기본 임베딩 모델은 영어에 최적화되어 있습니다. 한국어 문서를 다룬다면 한국어 특화 모델로 변경하는 것이 좋습니다.
python from chromadb.utils import embedding_functions # 한국어 임베딩 모델 사용 korean_ef = embedding_functions.SentenceTransformerEmbeddingFunction( model_name="jhgan/ko-sroberta-multitask" ) collection = client.get_or_create_collection( name="korean_docs", embedding_function=korean_ef ) jhgan/ko-sroberta-multitask는 한국어 문장 임베딩에 강력한 모델입니다. 검색 정확도가 크게 향상됩니다.
이개발 씨의 첫 성공 이개발 씨가 코드를 실행했습니다. 터미널에 검색 결과가 출력되었습니다.
"와, 정말 '연차'를 '휴가'로 찾아주네요!" 박시니어 씨가 미소 지었습니다. "이제 진짜 회사 문서를 넣어볼까요?
PDF 파일을 텍스트로 변환해서 ChromaDB에 저장하면 됩니다." 확장 - 더 많은 기능 ChromaDB는 업데이트와 삭제도 지원합니다. collection.update()로 기존 문서를 수정하고, collection.delete()로 삭제할 수 있습니다.
또한 collection.peek()로 저장된 문서를 미리보기하거나, collection.count()로 총 개수를 확인할 수 있습니다. 디버깅할 때 유용합니다.
정리 ChromaDB 구축은 생각보다 간단합니다. 설치, 컬렉션 생성, 문서 추가, 검색까지 20줄 안팎의 코드면 충분합니다.
가장 중요한 것은 직접 실행해보는 것입니다. 샘플 데이터로 테스트하고, 점진적으로 실제 데이터를 넣어보세요.
벡터 검색의 강력함을 직접 체험할 수 있습니다.
실전 팁
💡 - 실서비스에는 반드시 PersistentClient를 사용해 데이터를 영구 저장하세요
- 한국어 문서는 한국어 특화 임베딩 모델로 변경하면 정확도가 크게 향상됩니다
- where 필터로 카테고리나 날짜 범위를 제한하면 검색 품질이 좋아집니다
5. 실습 대용량 문서 인덱싱
샘플 데이터로는 성공했지만, 이제 진짜 도전이 시작됩니다. 이개발 씨 앞에는 회사의 업무 매뉴얼 PDF 파일 500개가 놓여있습니다.
총 5만 페이지가 넘습니다. "이걸 다 ChromaDB에 넣어야 한다고?" 막막했지만 박시니어 씨가 옆에서 차근차근 방법을 알려주기 시작했습니다.
대용량 문서를 벡터 DB에 저장하려면 청킹(chunking), 배치 처리, 임베딩 캐싱이 필요합니다. 문서를 적절한 크기로 나누고, 메모리 효율적으로 배치 삽입하며, 중복 임베딩을 피해야 합니다.
또한 진행 상황을 추적하고 오류 발생 시 재시작할 수 있도록 체크포인트를 구현해야 합니다.
다음 코드를 살펴봅시다.
import chromadb
from chromadb.config import Settings
import PyPDF2
from typing import List
def extract_text_from_pdf(pdf_path: str) -> str:
"""PDF에서 텍스트 추출"""
with open(pdf_path, 'rb') as file:
reader = PyPDF2.PdfReader(file)
text = ""
for page in reader.pages:
text += page.extract_text()
return text
def chunk_text(text: str, chunk_size: int = 500) -> List[str]:
"""텍스트를 chunk_size 단위로 분할"""
words = text.split()
chunks = []
for i in range(0, len(words), chunk_size):
chunk = ' '.join(words[i:i + chunk_size])
chunks.append(chunk)
return chunks
# ChromaDB 설정
client = chromadb.PersistentClient(path="./production_db")
collection = client.get_or_create_collection("manuals")
# PDF 처리 및 배치 삽입
pdf_files = ["manual1.pdf", "manual2.pdf"] # 실제로는 500개
batch_size = 100 # 한 번에 100개씩 삽입
for idx, pdf_file in enumerate(pdf_files):
text = extract_text_from_pdf(pdf_file)
chunks = chunk_text(text, chunk_size=500)
# 배치 단위로 삽입
for i in range(0, len(chunks), batch_size):
batch_chunks = chunks[i:i + batch_size]
batch_ids = [f"{pdf_file}_{j}" for j in range(i, i + len(batch_chunks))]
batch_metadata = [{"source": pdf_file, "chunk_idx": j} for j in range(len(batch_chunks))]
collection.add(
documents=batch_chunks,
ids=batch_ids,
metadatas=batch_metadata
)
print(f"처리 완료: {pdf_file} ({idx+1}/{len(pdf_files)})")
박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다. "대용량 문서 처리는 세 단계로 나눠집니다.
추출, 청킹, 인덱싱입니다." 1단계 - 문서에서 텍스트 추출 PDF, Word, Excel 등 다양한 형식의 문서에서 텍스트를 뽑아야 합니다. PDF는 PyPDF2나 pdfplumber 라이브러리를 사용합니다.
위 코드의 extract_text_from_pdf 함수가 PDF의 모든 페이지를 순회하며 텍스트를 추출합니다. 하지만 스캔된 이미지 PDF는 OCR이 필요합니다.
Word 문서는 python-docx로, Excel은 openpyxl로 처리할 수 있습니다. 웹 페이지는 BeautifulSoup나 Selenium으로 크롤링합니다.
이개발 씨는 회사 문서 중 상당수가 스캔 PDF임을 깨달았습니다. "OCR은 어떻게 하죠?" 박시니어 씨가 답했습니다.
"Tesseract OCR과 pdf2image를 조합하면 됩니다. 시간이 오래 걸리니 병렬 처리를 고려하세요." 2단계 - 청킹 전략 추출한 텍스트를 그대로 저장하면 안 됩니다.
한 PDF가 100페이지라면 수만 단어가 하나의 벡터로 압축됩니다. 이렇게 되면 검색 정확도가 떨어집니다.
예를 들어 "휴가 신청 방법"을 찾는데, 문서 어딘가에 한 문장만 있고 나머지는 전혀 다른 내용이라면 어떨까요? 전체 문서의 평균 의미는 "휴가"와 관련이 없어집니다.
해결책은 청킹입니다. 문서를 적당한 크기로 나눠서 각각 별도의 벡터로 저장합니다.
chunk_text 함수는 단어 개수 기준으로 자릅니다. 500단어씩 나누면 대략 2~3 문단 정도 됩니다.
너무 작으면 문맥을 잃고, 너무 크면 검색 정확도가 떨어집니다. 경험적으로 300~1000 단어가 적절합니다.
더 정교한 방법도 있습니다. 문장 단위로 자르거나, 의미적 유사도를 계산해서 주제가 바뀌는 지점에서 자르는 의미론적 청킹도 있습니다.
LangChain 라이브러리가 다양한 청킹 전략을 제공합니다. 3단계 - 배치 처리로 효율 향상 5만 개의 청크를 하나씩 insert하면 매우 느립니다.
네트워크 왕복이나 인덱스 업데이트가 매번 일어나기 때문입니다. 배치 처리로 해결합니다.
100개씩 묶어서 한 번에 add하면 속도가 크게 향상됩니다. 위 코드에서 batch_size = 100이 바로 이 역할입니다.
너무 큰 배치는 메모리 문제를 일으킬 수 있습니다. 임베딩 모델이 100개 문서를 동시에 처리하려면 GPU 메모리가 충분해야 합니다.
보통 50~200 사이의 값을 사용합니다. 메타데이터로 추적성 확보 각 청크에 메타데이터를 붙이는 것이 중요합니다.
"source"에 원본 파일명, "chunk_idx"에 청크 순서를 저장합니다. 나중에 사용자가 검색했을 때 "이 정보는 manual1.pdf의 3번째 청크에서 나왔습니다"라고 출처를 밝힐 수 있습니다.
신뢰도를 높이고 추가 정보를 찾기 쉽게 만듭니다. 날짜, 작성자, 부서, 버전 같은 메타데이터를 추가하면 더 풍부한 필터링이 가능합니다.
진행 상황 추적과 재시작 500개 파일 처리 중 300번째에서 오류가 나면 어떻게 할까요? 처음부터 다시 하면 낭비입니다.
체크포인트를 구현합니다. 처리한 파일 목록을 JSON 파일에 저장하고, 재시작 시 건너뛰는 것입니다.
processed.add(pdf_file) with open(checkpoint_file, 'w') as f: json.dump(list(processed), f) ``` 이렇게 하면 중간에 멈춰도 안전합니다. **성능 최적화 - 병렬 처리** 단일 스레드로 500개 PDF를 처리하면 시간이 오래 걸립니다.
멀티프로세싱으로 가속할 수 있습니다. ```python from multiprocessing import Pool def process_pdf(pdf_file): # PDF 추출 및 청킹 # ...
return chunks with Pool(4) as pool: all_chunks = pool.map(process_pdf, pdf_files) ``` CPU 코어를 4개 사용하면 4배 빠릅니다. OCR처럼 CPU 집약적 작업에 특히 효과적입니다.
**이개발 씨의 실전 적용** 이개발 씨는 코드를 작성하고 500개 파일로 테스트를 시작했습니다. 진행 상황이 터미널에 출력됩니다.
"처리 완료: manual001.pdf (1/500)" 2시간 후, 모든 파일이 ChromaDB에 저장되었습니다. 총 4만 5천 개의 청크가 인덱싱되었습니다.
테스트 검색을 해봤습니다. "출장 신청서 양식은 어디서 다운로드하나요?" 1초도 안 되어 정확한 답변이 나왔습니다.
메타데이터를 확인하니 "manual_hr_203.pdf"의 12번째 청크였습니다. 박시니어 씨가 만족스러운 표정으로 말했습니다.
"잘했어요. 이제 RAG 시스템의 핵심이 완성됐네요." **주의사항 - 임베딩 비용** OpenAI Embeddings 같은 유료 API를 사용한다면 비용을 고려하세요.
4만 개 청크를 임베딩하면 수천 원에서 수만 원이 나올 수 있습니다. 무료 대안으로는 HuggingFace의 sentence-transformers 모델을 로컬에서 실행하는 것입니다.
속도는 느리지만 무제한 무료입니다. **정리** 대용량 문서 인덱싱은 복잡해 보이지만 단계별로 나누면 명확합니다.
추출, 청킹, 배치 삽입, 체크포인트가 핵심입니다. 한 번 제대로 구축하면 수십만 개 문서도 효율적으로 처리할 수 있습니다.
여러분의 프로젝트에도 적용해 보세요.
**실전 팁**
💡 - 청크 크기는 300~1000 단어가 적절합니다. A/B 테스트로 최적값을 찾으세요
- 메타데이터에 출처 정보를 반드시 포함하세요. 검색 결과의 신뢰도가 높아집니다
- 대량 처리 시 체크포인트를 구현해 중단 시 재시작할 수 있게 하세요
---
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
ReAct 패턴 마스터 완벽 가이드
LLM이 생각하고 행동하는 ReAct 패턴을 처음부터 끝까지 배웁니다. Thought-Action-Observation 루프로 똑똑한 에이전트를 만들고, 실전 예제로 웹 검색과 계산을 결합한 강력한 AI 시스템을 구축합니다.
AI 에이전트의 모든 것 - 개념부터 실습까지
AI 에이전트란 무엇일까요? 단순한 LLM 호출과 어떻게 다를까요? 초급 개발자를 위해 에이전트의 핵심 개념부터 실제 구현까지 이북처럼 술술 읽히는 스타일로 설명합니다.
프로덕션 RAG 시스템 완벽 가이드
검색 증강 생성(RAG) 시스템을 실제 서비스로 배포하기 위한 확장성, 비용 최적화, 모니터링 전략을 다룹니다. AWS/GCP 배포 실습과 대시보드 구축까지 프로덕션 환경의 모든 것을 담았습니다.
RAG 캐싱 전략 완벽 가이드
RAG 시스템의 성능을 획기적으로 개선하는 캐싱 전략을 배웁니다. 쿼리 캐싱부터 임베딩 캐싱, Redis 통합까지 실무에서 바로 적용할 수 있는 최적화 기법을 다룹니다.
실시간으로 답변하는 RAG 시스템 만들기
사용자가 질문하면 즉시 답변이 스트리밍되는 RAG 시스템을 구축하는 방법을 배웁니다. 실시간 응답 생성부터 청크별 스트리밍, 사용자 경험 최적화까지 실무에서 바로 적용할 수 있는 완전한 가이드입니다.