Ollama Embedding 실전: 로컬 벡터 검색과 RAG 구축

컴퓨터에 저장된 PDF 문서 200여 개를 뒤져 반년 전에 읽었던 기술 설계의 세부 내용을 찾으려 한 적이 있습니다. 키워드 검색으로는 소용이 없었습니다. 기억나는 것은 문장의 의미이지 원문 표현이 아니었기 때문입니다. 거의 한 시간이 지나서야 문서를 찾았고, 그때 ‘의미를 이해하는 검색 도구가 있다면 얼마나 좋을까’라는 생각이 들었습니다.
더 큰 문제는 이 문서들이 회사 내부 아키텍처와 관련되어 있다는 점이었습니다. 클라우드에 업로드해 벡터 검색을 수행하는 것은 애초에 선택지가 아니었습니다. 데이터 프라이버시에 관한 선을 넘을 수 없었기 때문입니다.
그러다 Ollama의 Embedding 기능이 이 문제에 꼭 맞는다는 것을 알게 되었습니다. 로컬에서 실행되므로 데이터가 외부로 나가지 않으면서도 시맨틱 검색을 구현할 수 있습니다. 며칠 동안 mxbai, nomic, Qwen3 세 가지 모델을 모두 사용해 보고 벡터 데이터베이스 선택 기준도 자세히 살펴봤습니다. 예상보다 함정이 많았습니다. 모델을 잘못 고르면 검색 품질이 떨어지고, 규모가 작은 데이터베이스를 선택하면 나중에 확장하기가 어렵습니다.
이 글에서는 그 과정에서 얻은 시행착오를 정리합니다. 문서 처리부터 시맨틱 검색까지 전체 코드가 포함되어 있어, 끝까지 읽으면 직접 로컬 RAG 시스템을 구축할 수 있습니다.
Ollama Embedding 모델 한눈에 보기
Ollama는 여러 종류의 Embedding 모델을 지원합니다. 처음에는 저도 무엇을 선택해야 할지 몰랐습니다. 공식 문서에서는 mxbai가 OpenAI의 text-embedding-3-large보다 뛰어나다고 설명합니다. 꽤 인상적으로 들리지만, 실제 성능은 어떨까요? 여러 모델을 직접 사용해 본 뒤 내린 결론을 바로 알려드리겠습니다.
먼저 비교표부터 살펴보겠습니다.
| 모델 | 벡터 차원 | 컨텍스트 길이 | 모델 크기 | 특징 |
|---|---|---|---|---|
| mxbai-embed-large | 1024 | 512 tokens | 670M | 범용 우선 추천, MTEB 상위권 |
| nomic-embed-text | 768 | 8192 tokens | 274M | 긴 텍스트 지원, 컨텍스트 확장 지원 |
| Qwen3 Embedding | 1024 | 8192 tokens | 약 600M | 2026년 신형 모델, 중국어 처리에 강함 |
제가 가장 많이 사용하는 모델은 mxbai-embed-large입니다. 이유는 간단합니다. 사용하기 쉽고 결과가 안정적입니다. 1024차원 벡터는 대부분의 환경에 충분하며, MTEB(Massive Text Embedding Benchmark) 순위에서도 상위권에 올라 실제로 OpenAI의 text-embedding-3-large보다 점수가 조금 높습니다. 일반적인 문서 검색이나 코드 검색이라면 이 모델을 선택해도 좋습니다.
nomic-embed-text의 강점은 8192 tokens의 컨텍스트 길이입니다. 문서 한 편 전체나 긴 대화 기록을 처리할 때 유용합니다. 모델 크기도 274M으로 작아 mxbai보다 조금 더 빠르게 실행됩니다. 다만 벡터 차원이 768로 낮아 이론적으로 의미 표현력이 다소 약할 수 있습니다. 실제 테스트에서는 짧은 텍스트 검색의 차이가 크지 않았고, 긴 텍스트에는 nomic이 더 잘 맞았습니다.
Qwen3 Embedding은 Alibaba가 2026년 4월에 새로 공개한 모델입니다. 중국어 성능이 실제로 좋습니다. 기술 문서 몇 편을 테스트했을 때 ‘분산 시스템 내결함성 메커니즘’과 ‘내결함성 설계’를 서로 연관된 내용으로 찾아냈지만, mxbai의 결과는 다소 아쉬웠습니다. 주로 중국어 콘텐츠를 처리한다면 시도해 볼 만합니다.
선택 기준은 간단합니다. 입문자는 고민하지 말고 mxbai부터 사용하고, 긴 텍스트에는 nomic, 중국어 콘텐츠에는 Qwen3를 우선 선택하세요. 세 모델을 모두 테스트하는 데도 30분이면 충분합니다. 결국 중요한 것은 실제 검색 결과입니다.
벡터 데이터베이스 선택 가이드
모델을 골랐다면 이제 데이터를 어디에 저장할지 정해야 합니다. 벡터 데이터베이스 선택은 모델보다 더 실수하기 쉬운 부분입니다. 규모가 너무 작으면 나중에 확장하기 어렵고, 너무 크면 리소스를 낭비하게 됩니다. 대표적인 세 가지 방식을 비교해 보겠습니다.
| 데이터베이스 | 적합한 환경 | 데이터 규모 | 특징 |
|---|---|---|---|
| ChromaDB | 입문 개발, 개인 프로젝트 | < 10만 건 | 바로 사용 가능, 별도 설정 불필요 |
| FAISS | 단일 머신 고성능, 연구 실험 | 10만~100만 건 | Meta 오픈 소스, 매우 빠른 속도 |
| Milvus | 프로덕션 배포, 엔터프라이즈 | 수백만 건 이상 | 분산형, 확장 가능, 다양한 기능 |
ChromaDB는 입문용으로 가장 추천하는 선택입니다. pip install chromadb 명령어 하나로 설치할 수 있습니다. API가 친절하게 설계되어 있어 몇 줄의 코드만으로 데이터를 저장하고 검색할 수 있습니다. HNSW(Hierarchical Navigable Small World) 인덱스를 사용하므로 소규모 데이터에서는 검색 속도도 충분히 빠릅니다. 단점은 단일 머신 배포 방식이라 데이터가 10만 건을 넘으면 성능이 떨어지기 시작한다는 점입니다.
FAISS는 Meta가 오픈 소스로 공개한 오래된 도구입니다. 순수 C++로 구현되어 속도가 정말 빠릅니다. 50만 건의 데이터로 테스트했을 때도 검색 지연 시간이 밀리초 단위로 안정적으로 유지되었습니다. 다만 완전한 데이터베이스라기보다 벡터 검색 라이브러리에 가깝습니다. 스토리지와 인덱스 파일을 직접 관리해야 합니다. 직접 세밀하게 구성하고 싶거나 성능 요구가 매우 높은 환경에 적합합니다.
Milvus는 성격이 다릅니다. 처음부터 프로덕션 환경을 겨냥한 제품입니다. 분산 배포와 영구 스토리지를 지원하고 여러 인덱스 유형을 제공하며, 클라우드 서비스 버전인 Zilliz Cloud도 있습니다. 하지만 설정이 복잡하고 배포 비용이 높습니다. 수백만 건의 데이터, 고가용성, 팀 협업이 필요한 상황에 적합합니다.
저의 선택 기준은 이렇습니다. 개인 프로젝트는 빠르게 시작할 수 있는 ChromaDB, 연구 프로젝트나 성능이 중요한 환경에는 FAISS, 실제 프로덕션에는 Milvus 또는 클라우드 서비스를 사용합니다. 나중에 ChromaDB에서 Milvus로 옮기면 된다고 가볍게 생각해서는 안 됩니다. 데이터 형식과 API가 달라 마이그레이션 비용이 적지 않습니다.
전체 RAG 흐름 실전
이론만 길게 설명하기보다 바로 코드를 살펴보겠습니다. 아래 예제는 PDF 문서부터 시맨틱 검색까지 Ollama + ChromaDB로 구현한 완전한 로컬 RAG입니다.
환경 준비
먼저 의존성을 설치합니다.
pip install ollama chromadb langchain langchain-community pypdf
Ollama가 실행 중이고 필요한 모델을 내려받았는지 확인합니다.
ollama pull mxbai-embed-large
ollama pull qwen2.5:7b # 답변 생성용
코드 구현
import ollama
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
import chromadb
# 1. PDF 문서 로드
loader = PyPDFLoader("./your_document.pdf")
docs = loader.load()
# 2. 문서 청크 분할 — chunk가 너무 크면 검색이 부정확하고, 너무 작으면 정보가 손실되므로 신중하게 설정해야 합니다.
splitter = RecursiveCharacterTextSplitter(
chunk_size=800, # 청크당 800자
chunk_overlap=100, # 경계 정보 손실을 막기 위해 100자 중복
)
chunks = splitter.split_documents(docs)
# 3. Embedding을 생성해 ChromaDB에 저장
client = chromadb.Client()
collection = client.create_collection("my_docs")
for i, chunk in enumerate(chunks):
# Ollama API를 호출해 벡터 생성
response = ollama.embed(
model="mxbai-embed-large",
input=chunk.page_content,
)
embedding = response["embeddings"][0]
# 벡터 데이터베이스에 저장
collection.add(
ids=[str(i)],
embeddings=[embedding],
documents=[chunk.page_content],
metadatas=[{"source": chunk.metadata.get("source", "unknown")}],
)
print(f"{len(chunks)}개의 문서 조각을 저장했습니다.")
# 4. 시맨틱 검색
query = "분산 시스템의 내결함성 메커니즘이란 무엇인가요?"
query_embedding = ollama.embed(
model="mxbai-embed-large",
input=query,
)["embeddings"][0]
results = collection.query(
query_embeddings=[query_embedding],
n_results=3, # 관련성이 가장 높은 조각 3개 반환
)
# 5. 검색 결과로 답변 생성
context = "\n\n".join(results["documents"][0])
response = ollama.chat(
model="qwen2.5:7b",
messages=[
{
"role": "system",
"content": "아래 문서 내용을 바탕으로 질문에 답하세요. 문서에 관련 정보가 없다면 솔직하게 설명하세요.",
},
{"role": "user", "content": f"문서 내용: {context}\n\n질문: {query}"},
],
)
print(f"답변: {response['message']['content']}")
직접 실행해 보세요. 전체 흐름은 생각보다 복잡하지 않습니다. 문서 청크 분할 → 벡터 생성 → 데이터베이스 저장 → 검색 → 답변 구성의 순서입니다.
몇 가지 주의할 점이 있습니다.
첫째, chunk_size를 임의로 설정하지 마세요. 청크당 200자로 테스트했을 때 검색 결과가 모두 조각난 정보라 완전한 답변을 구성할 수 없었습니다. 500~1000자 범위가 비교적 안정적입니다.
둘째, 문서가 많다면 배치 처리를 사용해야 합니다. Ollama API를 한 건씩 호출하면 매우 느립니다. 여러 건을 모아 한 번에 처리할 수 있습니다.
# Embedding을 배치로 생성하면 속도가 크게 향상됩니다.
batch_texts = [chunk.page_content for chunk in chunks[:50]]
batch_embeddings = ollama.embed(
model="mxbai-embed-large",
input=batch_texts,
)["embeddings"]
셋째, 유사도 임계값입니다. ChromaDB는 관련성 여부와 상관없이 기본적으로 n_results에 지정한 수만큼 결과를 반환합니다. 상황에 따라 관련 없는 결과를 걸러내야 합니다.
# 사용자 지정 거리 임계값으로 필터링
results = collection.query(
query_embeddings=[query_embedding],
n_results=10,
)
# 거리가 0.3 미만인 결과만 유지합니다(거리가 작을수록 유사합니다).
filtered = [
doc for doc, dist in zip(results["documents"][0], results["distances"][0])
if dist < 0.3
]
이 코드까지 실행하면 자신만의 로컬 RAG가 완성됩니다. PDF를 자신의 문서로 바꾸고 query를 원하는 질문으로 수정하면 됩니다. 나머지는 그대로 두어도 됩니다.
성능 최적화와 실전 팁
시스템이 작동한다면 이제 최적화할 차례입니다. 몇 가지 매개변수가 결과에 직접 영향을 줍니다. 제가 겪었던 문제를 바탕으로 정리해 보겠습니다.
chunk_size는 얼마로 설정해야 할까요?
제가 경험상 권하는 범위는 500~1000자입니다. 너무 작으면 의미가 불완전해집니다. 문장 하나가 둘로 잘려 질문과 일치하지 않을 수 있습니다. 너무 크면 검색 노이즈가 생깁니다. 하나의 chunk에 여러 주제가 섞여 경계가 흐려지기 때문입니다.
문서 유형에 따라서도 차이가 있습니다. 구조가 명확한 기술 문서는 문단 단위로 나눌 수 있고, 대화 기록처럼 파편화된 텍스트는 500자 단위가 더 적합합니다. 모든 환경에 통하는 정답은 없으므로 실제 상황에서 여러 번 테스트해야 합니다.
batch 배치 처리로 속도 향상
Ollama API를 한 건씩 호출하면 매번 네트워크 왕복을 기다려야 합니다. 50~100건을 한 배치로 묶어 보내면 속도가 몇 배 빨라집니다. 단, 배치 크기가 너무 커지지 않도록 주의하세요. Embedding 모델에는 입력 길이 제한이 있으며 이를 넘으면 오류가 발생합니다.
유사도 임계값 설정 방법
정확도 요구 수준에 따라 0.7~0.85 범위에서 조정합니다. 임계값이 0.85처럼 높으면 관련성이 매우 높은 결과만 유지하므로 재현율은 낮지만 정확합니다. 임계값이 0.7처럼 낮으면 더 많은 결과를 찾지만 노이즈가 섞일 수 있습니다. 문서 저장소가 잘 정리되어 있고 질문이 명확하다면 높게 설정하고, 질문이 모호해 더 많은 정보가 필요하다면 낮게 설정하세요.
작은 팁: 먼저 50~100건의 데이터로 검색 결과를 확인한 뒤 매개변수를 결정하세요. 전체 데이터를 넣은 다음 조정하면 시간이 너무 많이 듭니다. 작은 범위에서 반복적으로 실행하며 개선하는 편이 낫습니다.
정리
핵심은 몇 가지로 정리할 수 있습니다.
모델은 용도에 맞게 선택합니다. 범용 작업에는 mxbai, 긴 텍스트에는 nomic, 중국어에는 Qwen3를 우선 사용하세요. 데이터베이스는 입문용 ChromaDB, 성능 중심 FAISS, 프로덕션 환경 Milvus로 나눌 수 있습니다. 전체 흐름을 구현한 코드도 제공했으므로 상황에 맞게 조금만 수정하면 바로 실행할 수 있습니다.
이 방식의 장점은 분명합니다. 로컬에 배포하므로 데이터 프라이버시를 관리할 수 있고, Ollama 모델은 무료라 비용이 들지 않으며, ChromaDB는 사용하기 쉬워 진입 장벽이 낮습니다. 단점도 있습니다. 단일 머신의 성능에는 한계가 있으며, 데이터가 수백만 건을 넘으면 시스템 업그레이드를 고려해야 합니다.
먼저 글의 코드를 그대로 실행하고 문서 50개를 넣어 결과를 확인해 보세요. 검색 품질이 좋은지, 답변이 정확한지는 직접 테스트해야 알 수 있습니다. 매개변수를 조정한 다음 전체 데이터로 확장하는 것이 좋습니다.
LangChain과 Ollama를 결합해 더 복잡한 애플리케이션을 만드는 방법이 궁금하다면, 이전에 작성한 《LangChain + Ollama 통합 실전》도 참고해 보세요. 그 글은 대화 체인과 도구 호출을 다루고, 이번 글은 벡터 검색에 초점을 맞춥니다. 두 글을 함께 보면 로컬 LLM 애플리케이션 개발의 전체 흐름을 이해할 수 있습니다.
로컬 RAG 시스템 구축하기
Ollama + ChromaDB를 사용해 로컬 벡터 검색 시스템을 구축합니다.
⏱️ Estimated time: 30 min
- 1
Step 1: 의존성 설치 및 모델 준비
다음 명령어를 실행해 의존성을 설치합니다.
```bash
pip install ollama chromadb langchain langchain-community pypdf
ollama pull mxbai-embed-large
ollama pull qwen2.5:7b
```
Ollama 서비스가 실행 중인지 확인합니다. - 2
Step 2: 문서 로드 및 청크 분할
PyPDFLoader로 PDF를 불러오고 RecursiveCharacterTextSplitter로 청크를 나눕니다.
```python
loader = PyPDFLoader("./your_document.pdf")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=100,
)
chunks = splitter.split_documents(docs)
```
chunk_size는 500~1000자로 설정하는 것이 좋습니다. - 3
Step 3: 벡터를 생성해 데이터베이스에 저장
Ollama API로 Embedding을 생성한 뒤 ChromaDB에 저장합니다.
```python
client = chromadb.Client()
collection = client.create_collection("my_docs")
for i, chunk in enumerate(chunks):
response = ollama.embed(
model="mxbai-embed-large",
input=chunk.page_content,
)
embedding = response["embeddings"][0]
collection.add(
ids=[str(i)],
embeddings=[embedding],
documents=[chunk.page_content],
)
```
문서가 많다면 배치 처리를 사용합니다. - 4
Step 4: 시맨틱 검색 및 답변 생성
쿼리를 벡터로 변환해 관련 문서를 검색한 다음 LLM으로 답변을 생성합니다.
```python
query_embedding = ollama.embed(
model="mxbai-embed-large",
input=query,
)["embeddings"][0]
results = collection.query(
query_embeddings=[query_embedding],
n_results=3,
)
context = "\n\n".join(results["documents"][0])
response = ollama.chat(
model="qwen2.5:7b",
messages=[
{"role": "system", "content": "문서 내용을 바탕으로 질문에 답하세요"},
{"role": "user", "content": f"문서: {context}\n질문: {query}"},
],
)
```
필요에 따라 유사도 임계값을 조정해 결과를 필터링합니다.
FAQ
Ollama의 Embedding 모델 중 어떤 것이 가장 좋나요?
• mxbai-embed-large: 범용 우선 추천 모델로 효과가 안정적이며 대부분의 상황에 적합합니다.
• nomic-embed-text: 긴 텍스트에 적합하고 8192 tokens를 지원합니다.
• Qwen3 Embedding: 중국어 처리에 강한 2026년 신형 모델입니다.
세 모델을 모두 사용해 보고 실제 결과를 기준으로 판단하는 것이 좋습니다.
ChromaDB, FAISS, Milvus 중 무엇을 선택해야 하나요?
• ChromaDB: 입문용으로 적합하며 별도 설정 없이 10만 건 이하 데이터를 처리하기 좋습니다.
• FAISS: 성능을 우선할 때 적합하며 단일 머신에서 수백만 건을 처리할 수 있지만 스토리지를 직접 관리해야 합니다.
• Milvus: 프로덕션 환경과 분산 배포, 수백만 건 이상의 데이터에 적합합니다.
개인 프로젝트에는 ChromaDB, 프로덕션 환경에는 Milvus를 추천합니다.
chunk_size는 어느 정도로 설정해야 하나요?
Embedding 생성 속도는 어떻게 높일 수 있나요?
유사도 임계값은 얼마로 설정해야 하나요?
로컬 RAG의 주요 장단점은 무엇인가요?
단점: 단일 머신의 성능에 한계가 있고, 데이터가 수백만 건을 넘으면 시스템 업그레이드가 필요하며 서비스를 직접 유지 관리해야 합니다.
2분 읽기 · 게시일: 2026년 4월 8일 · 수정일: 2026년 9월 4일
Ollama 로컬 LLM 가이드
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
LangChain + Ollama 통합 실전: 로컬 LLM 애플리케이션 개발 완벽 가이드
LangChain과 Ollama를 통합하는 전체 과정을 Chat, RAG, Agent 세 가지 실전 코드 예제와 함께 설명하고, OpenAI와 Ollama 전환 전략까지 비교해 로컬 모델로 엔터프라이즈급 LLM 애플리케이션을 만드는 방법을 안내합니다.
14편 중 10편
다음
Ollama 모델 양자화 실전: GGUF 형식과 정확도 손실 완전 분석
Ollama GGUF 양자화 원리를 자세히 설명하고, Red Hat의 50만 건 이상 평가 데이터를 바탕으로 정확도 손실의 실상을 살펴봅니다. 하드웨어 구성별 양자화 선택 기준도 제시해 소비자용 그래픽 카드에서 대규모 모델을 실행할 수 있도록 돕습니다.
14편 중 12편



댓글
GitHub로 로그인하여 댓글을 남기세요