벡터 저장소 이해하기 — LangChain Vector Store
· Vector Store(벡터 저장소)는 임베딩으로 만든 숫자 벡터를 담아 두고, 의미가 비슷한 조각을 빠르게 찾아 주는 저장소입니다.
· 키워드가 정확히 일치하지 않아도 뜻이 가까운 문서를 골라내는 유사도 검색이 핵심 역할입니다.
· 잘 저장해 두면 이후 Retriever → RAG 단계에서 질문에 맞는 근거를 손쉽게 가져올 수 있습니다.
지난 글에서 문서를 조각으로 나누고, 그 조각을 임베딩으로 숫자 벡터로 바꾸는 이야기를 했습니다. 그런데 벡터를 만들기만 해서는 쓸모가 없습니다. 어딘가에 담아 두고, 나중에 질문이 들어오면 비슷한 벡터를 빠르게 찾아 돌려줄 수 있어야 합니다. 이 역할을 맡는 것이 바로 Vector Store(벡터 저장소)입니다. 이번 글에서는 벡터 저장소가 왜 필요한지, 유사도 검색이 어떤 원리로 동작하는지, 그리고 실제 코드까지 정리해 보겠습니다.
🗄️ 벡터 저장소가 필요한 이유
일반적인 데이터베이스는 정확히 일치하는 값을 찾는 데 강합니다. "이름이 홍길동인 행"처럼 조건이 딱 맞아떨어지는 검색이죠. 하지만 문서 검색은 다릅니다. 사용자가 "환불 어떻게 해요?"라고 물어도, 문서에는 "반품 및 대금 반환 절차"라고 적혀 있을 수 있습니다. 단어는 다르지만 뜻은 같은 상황입니다.
핵심은 이렇습니다. 글자가 아니라 '의미'가 가까운 것을 찾는다.
임베딩은 문장의 의미를 숫자 벡터로 표현합니다. 뜻이 비슷한 문장은 벡터 공간에서 서로 가까운 위치에 놓입니다. 벡터 저장소는 이 벡터들을 모아 두고, 질문 벡터와 가까운 벡터들을 빠르게 골라내도록 설계된 저장소입니다. 문서가 수천, 수만 개로 늘어나도 매번 전부 비교하지 않고 효율적으로 찾을 수 있다는 점이 일반 저장 방식과의 결정적 차이입니다.
🎯 유사도 검색은 어떻게 동작할까
흐름은 생각보다 단순합니다. 저장할 때와 찾을 때 모두 같은 임베딩 모델을 사용한다는 점만 기억하면 됩니다.
"가깝다"를 재는 기준은 보통 벡터 사이의 거리나 방향의 유사도입니다. 자주 쓰이는 방식으로는 두 벡터가 이루는 각도를 보는 코사인 유사도가 있습니다. 방향이 비슷할수록, 즉 의미가 가까울수록 점수가 높게 나옵니다. 이렇게 상위 몇 개(흔히 k개)를 뽑아 오는 것을 Top-k 검색이라고 부릅니다.
🧩 대표적인 벡터 저장소 종류
LangChain은 다양한 벡터 저장소를 같은 방식으로 다룰 수 있게 감싸 줍니다. 성격별로 나눠 보면 아래처럼 정리됩니다.
| 종류 | 특징 | 잘 맞는 상황 |
|---|---|---|
| FAISS | 로컬 메모리 기반, 설치가 가볍다 | 실습·프로토타입 |
| Chroma | 로컬에 파일로 저장, 다루기 쉽다 | 소규모 개인 프로젝트 |
| Pinecone | 클라우드 관리형 서비스 | 운영·대규모 서비스 |
| pgvector | PostgreSQL 확장으로 벡터 지원 | 기존 DB와 함께 운영 |
처음 배울 때는 설치와 실행이 간단한 FAISS나 Chroma로 시작하는 것이 편합니다. 나중에 서비스 규모가 커지고 여러 서버가 함께 접근해야 한다면 클라우드 관리형이나 기존 DB에 얹는 방식으로 옮겨 가면 됩니다. 다행히 LangChain에서는 사용법이 대체로 비슷해, 저장소를 바꿔도 코드를 크게 고치지 않는 편입니다.
💻 코드로 저장하고 검색하기
사용 흐름은 앞 단계들과 비슷합니다. 조각과 임베딩 모델을 넘겨 저장소를 만들고 → 질문으로 검색하면 → 관련 조각 목록이 돌아옵니다.
# FAISS 벡터 저장소 만들고 검색하기
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
# 1) 조각(split_docs)을 임베딩해 저장소에 넣기
vectorstore = FAISS.from_documents(split_docs, embeddings)
# 2) 질문과 의미가 가까운 조각 찾기 (상위 k개)
query = "환불은 어떻게 하나요?"
results = vectorstore.similarity_search(query, k=3)
for doc in results:
print(doc.page_content[:80]) # 관련 조각의 앞부분
print(doc.metadata) # 출처 정보가 함께 온다
# 3) 나중에 다시 쓰도록 로컬에 저장 / 불러오기
vectorstore.save_local("faiss_index")
# loaded = FAISS.load_local("faiss_index", embeddings)
눈여겨볼 점은 similarity_search가 단순한 텍스트뿐 아니라 출처가 담긴 metadata까지 함께 돌려준다는 것입니다. 앞선 분할 단계에서 지켜 둔 출처 정보가 여기까지 이어지므로, 검색 결과에 "어느 문서에서 왔는지"를 그대로 표기할 수 있습니다.
⚠️ 자주 부딪히는 문제
벡터 저장소는 넣고 찾기만 하면 될 것 같지만, 아래 지점에서 자주 결과가 어긋납니다.
🔧 실무 팁
- 가벼운 것부터 시작 — 실습·프로토타입은 FAISS나 Chroma로 충분합니다. 운영이 필요할 때 옮기세요.
- 임베딩 모델을 고정 — 저장과 검색에 같은 모델을 쓰고, 모델을 바꾸면 저장소를 다시 만들어야 합니다.
- k는 작게 시작해 조정 — 우선 몇 개만 가져와 결과를 보고, 근거가 부족하면 조금씩 늘려 봅니다.
- metadata를 활용 — 출처·날짜 등을 저장해 두면 나중에 필터링이나 근거 표기에 유용합니다.
❓ 자주 묻는 질문 (FAQ)
Q. 벡터 저장소는 일반 데이터베이스와 무엇이 다른가요?
A. 일반 DB는 값이 정확히 일치하는 것을 찾는 데 강하고, 벡터 저장소는 의미가 비슷한 것을 찾는 데 특화되어 있습니다. 단어가 달라도 뜻이 가까우면 찾아낼 수 있다는 점이 가장 큰 차이입니다.
Q. 저장소를 바꾸면 코드를 많이 고쳐야 하나요?
A. 대부분 그렇지 않습니다. LangChain이 여러 저장소를 비슷한 사용법으로 감싸 두어, 저장소 생성 부분만 바꾸면 검색 코드는 거의 그대로 쓸 수 있는 경우가 많습니다.
Q. 검색한 조각은 바로 답변에 쓰나요?
A. 보통 한 단계가 더 있습니다. 검색 기능을 표준화해 주는 Retriever를 거쳐 관련 조각을 가져오고, 이를 프롬프트에 넣어 모델이 답하게 합니다. 다음 편에서 이 Retriever 이야기로 이어 가겠습니다.
마무리
정리하면, Vector Store는 임베딩으로 만든 벡터를 담아 두고 의미가 가까운 조각을 빠르게 찾아 주는 저장소입니다. 핵심 감각은 두 가지, 글자가 아니라 의미로 검색한다는 것과 저장과 검색에 같은 임베딩을 써야 한다는 것입니다. 처음에는 가벼운 FAISS나 Chroma로 감을 익히고, 규모가 커지면 관리형이나 기존 DB에 얹는 방식으로 확장하면 됩니다. 이렇게 저장해 둔 벡터는 다음 단계에서 검색을 표준화해 주는 Retriever로 이어지는데, 다음 편에서 그 이야기를 다뤄 보겠습니다.
댓글
댓글 쓰기