본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 2. · 18 Views
Vector Database 활용 완벽 가이드
벡터 데이터베이스의 기본 개념부터 Chroma, Pinecone, Faiss까지 실무에서 바로 활용할 수 있는 벡터 검색 기술을 다룹니다. 유사도 검색과 메타데이터 필터링까지 초급자도 쉽게 이해할 수 있도록 설명합니다.
목차
1. 벡터 DB란
김개발 씨는 회사에서 문서 검색 시스템을 만들라는 과제를 받았습니다. "사용자가 질문하면 관련된 문서를 찾아주는 시스템을 만들어 주세요." 김개발 씨는 고민에 빠졌습니다.
키워드 검색으로는 "맛있는 음식"과 "delicious food"가 같은 의미라는 것을 어떻게 알 수 있을까요?
벡터 데이터베이스는 텍스트, 이미지, 음성 등을 숫자 배열인 벡터로 변환하여 저장하고 검색하는 특수한 데이터베이스입니다. 마치 도서관 사서가 책의 내용을 이해하고 비슷한 주제의 책을 추천해주는 것처럼, 벡터 DB는 데이터의 의미를 이해하여 유사한 것들을 찾아줍니다.
이것을 제대로 이해하면 AI 기반 검색, 추천 시스템, RAG 등 다양한 분야에 활용할 수 있습니다.
다음 코드를 살펴봅시다.
# 텍스트를 벡터로 변환하는 기본 개념
from sentence_transformers import SentenceTransformer
# 임베딩 모델 로드
model = SentenceTransformer('all-MiniLM-L6-v2')
# 텍스트를 벡터로 변환
texts = ["맛있는 음식", "delicious food", "프로그래밍 언어"]
vectors = model.encode(texts)
# 벡터의 형태 확인 (384차원 벡터)
print(f"벡터 차원: {vectors[0].shape}")
# 출력: 벡터 차원: (384,)
# 유사도 계산 - 의미가 비슷할수록 1에 가까움
from sklearn.metrics.pairwise import cosine_similarity
similarity = cosine_similarity([vectors[0]], [vectors[1]])
print(f"유사도: {similarity[0][0]:.4f}")
김개발 씨는 입사 6개월 차 주니어 개발자입니다. 어느 날 팀장님이 다가와 새로운 프로젝트를 맡겼습니다.
"우리 회사 문서가 수만 개인데, 직원들이 원하는 문서를 쉽게 찾을 수 있는 검색 시스템을 만들어 주세요." 김개발 씨는 처음에 MySQL의 LIKE 검색을 생각했습니다. 하지만 곧 문제를 깨달았습니다.
"휴가 신청 방법"을 검색했을 때 "연차 사용 절차"라는 문서는 어떻게 찾을 수 있을까요? 키워드가 하나도 겹치지 않는데 말입니다.
선배 개발자 박시니어 씨가 지나가다 김개발 씨의 고민을 들었습니다. "벡터 데이터베이스를 써보는 게 어때요?
요즘 AI 검색은 다 벡터 DB를 사용하거든요." 그렇다면 벡터 데이터베이스란 정확히 무엇일까요? 쉽게 비유하자면, 벡터 데이터베이스는 마치 뛰어난 도서관 사서와 같습니다.
일반 검색이 책 제목에 있는 단어만 찾는다면, 벡터 DB는 책의 내용을 이해하고 있어서 "재미있는 판타지 소설"이라고 하면 "해리포터"를 추천해줄 수 있습니다. 단어가 아니라 의미를 이해하는 것입니다.
이것이 가능한 이유는 임베딩이라는 기술 덕분입니다. 임베딩은 텍스트, 이미지, 음성 같은 데이터를 수백 개의 숫자로 이루어진 벡터로 변환합니다.
이 숫자들은 데이터의 의미를 담고 있어서, 의미가 비슷한 데이터는 비슷한 숫자 패턴을 가지게 됩니다. 벡터 DB가 없던 시절에는 어땠을까요?
개발자들은 동의어 사전을 만들거나, 복잡한 자연어 처리 파이프라인을 구축해야 했습니다. "휴가"를 검색하면 "연차", "휴직", "쉬는 날"도 함께 검색되도록 일일이 매핑해야 했습니다.
프로젝트가 커질수록 관리가 불가능해졌습니다. 위의 코드를 살펴보겠습니다.
먼저 SentenceTransformer라는 임베딩 모델을 불러옵니다. 이 모델은 문장을 384차원의 벡터로 변환해줍니다.
"맛있는 음식"과 "delicious food"를 벡터로 변환하면, 두 벡터 사이의 코사인 유사도가 0.8 이상으로 매우 높게 나옵니다. 언어가 다르지만 의미가 같기 때문입니다.
실제 현업에서는 어떻게 활용할까요? 고객 문의 시스템을 예로 들어보겠습니다.
고객이 "배송이 너무 늦어요"라고 문의하면, 벡터 DB는 "배송 지연", "택배 문제", "물류 이슈" 관련 FAQ를 모두 찾아줍니다. 키워드 매칭으로는 불가능한 일입니다.
하지만 주의할 점도 있습니다. 벡터 DB는 의미 검색에는 뛰어나지만, 정확한 키워드 매칭에는 전통적인 DB가 더 나을 수 있습니다.
예를 들어 주문번호 "ORD-12345"를 검색할 때는 벡터 검색보다 정확히 일치하는 것을 찾는 게 맞습니다. 따라서 용도에 맞게 선택해야 합니다.
박시니어 씨의 조언을 들은 김개발 씨는 눈이 번쩍 뜨였습니다. "그래서 ChatGPT 같은 서비스들이 관련 정보를 잘 찾아주는 거군요!" 벡터 데이터베이스를 이해하면 AI 시대의 핵심 기술 한 가지를 마스터하는 셈입니다.
실전 팁
💡 - 임베딩 모델 선택이 검색 품질을 좌우합니다. 한국어는 multilingual 모델을 사용하세요.
- 벡터 차원이 클수록 정확하지만 저장 공간과 검색 속도에 영향을 줍니다.
2. Chroma 시작하기
김개발 씨는 벡터 DB의 개념을 이해했지만, 어디서부터 시작해야 할지 막막했습니다. "설치도 복잡하고 설정도 어려우면 어쩌지?" 걱정하던 김개발 씨에게 박시니어 씨가 말했습니다.
"Chroma로 시작해봐요. 5분이면 돌아가요."
Chroma는 가장 쉽게 시작할 수 있는 오픈소스 벡터 데이터베이스입니다. 마치 SQLite처럼 별도의 서버 설치 없이 바로 사용할 수 있어서 프로토타입이나 학습용으로 완벽합니다.
pip 한 줄로 설치하고, 몇 줄의 코드로 벡터 저장과 검색이 가능합니다.
다음 코드를 살펴봅시다.
# Chroma 설치: pip install chromadb
import chromadb
# 클라이언트 생성 (로컬 저장)
client = chromadb.PersistentClient(path="./chroma_db")
# 컬렉션 생성 (테이블과 비슷한 개념)
collection = client.get_or_create_collection(
name="documents",
metadata={"description": "회사 문서 모음"}
)
# 문서 추가 - 임베딩은 자동 생성됨
collection.add(
documents=["휴가 신청은 3일 전에 해야 합니다", "연차는 1년에 15일입니다"],
ids=["doc1", "doc2"],
metadatas=[{"category": "hr"}, {"category": "hr"}]
)
# 유사한 문서 검색
results = collection.query(query_texts=["쉬는 날 규정"], n_results=2)
print(results["documents"])
김개발 씨는 구글에서 벡터 데이터베이스를 검색해보았습니다. Pinecone, Milvus, Weaviate, Qdrant...
종류가 너무 많았습니다. 각각의 장단점을 비교하다 보니 하루가 다 갔습니다.
다음 날 박시니어 씨가 물었습니다. "진도는 어때요?" 김개발 씨는 한숨을 쉬었습니다.
"아직 뭘 써야 할지도 못 정했어요. 다 장단점이 있더라고요." 박시니어 씨가 웃으며 말했습니다.
"처음 배울 때는 Chroma로 시작하세요. 가장 단순하고 배우기 쉬워요.
나중에 서비스가 커지면 그때 다른 걸로 바꿔도 돼요." Chroma는 마치 Python 생태계의 SQLite와 같습니다. SQLite가 복잡한 DB 서버 설치 없이 파일 하나로 데이터베이스를 사용하게 해주듯, Chroma도 pip install 한 줄이면 바로 벡터 DB를 사용할 수 있습니다.
별도의 도커 컨테이너도, 복잡한 설정도 필요 없습니다. Chroma의 핵심 개념은 컬렉션입니다.
관계형 데이터베이스의 테이블과 비슷하다고 생각하면 됩니다. 하나의 컬렉션에는 관련된 문서들을 모아서 저장합니다.
예를 들어 "hr_documents", "tech_documents" 같은 컬렉션을 만들 수 있습니다. 위의 코드에서 가장 편리한 부분은 자동 임베딩 기능입니다.
collection.add()에 문서를 넣으면, Chroma가 내부적으로 임베딩 모델을 사용해서 벡터로 변환해줍니다. 개발자가 직접 임베딩 모델을 관리할 필요가 없습니다.
검색도 마찬가지로 간단합니다. query_texts에 검색어를 넣으면, Chroma가 검색어를 벡터로 변환하고, 저장된 문서들과 유사도를 계산해서 가장 비슷한 것들을 반환합니다.
"쉬는 날 규정"이라고 검색하면 "휴가 신청"이나 "연차" 관련 문서가 나오는 것입니다. PersistentClient를 사용하면 데이터가 디스크에 저장됩니다.
프로그램을 종료해도 데이터가 유지되어, 다음에 다시 불러올 수 있습니다. 반면 **Client()**를 사용하면 메모리에만 저장되어 프로그램 종료 시 사라집니다.
테스트할 때는 메모리 모드가 편리합니다. 실무에서 Chroma는 어떤 상황에 적합할까요?
문서 수가 수십만 개 이하이고, 동시 사용자가 많지 않은 경우에 좋습니다. 사내 문서 검색, 개인 프로젝트, MVP 개발 등에 적합합니다.
대규모 서비스라면 다른 선택이 필요하지만, 대부분의 초기 프로젝트에는 Chroma로 충분합니다. 주의할 점은 프로덕션 환경에서의 확장성입니다.
Chroma는 단일 노드에서 동작하기 때문에, 수백만 개의 벡터를 다루거나 높은 동시성이 필요하면 한계가 있습니다. 이럴 때는 Pinecone이나 Milvus 같은 분산 시스템을 고려해야 합니다.
김개발 씨는 30분 만에 첫 번째 벡터 검색 프로토타입을 완성했습니다. "이렇게 쉬울 줄 몰랐어요!" 박시니어 씨가 말했습니다.
"도구는 단순할수록 좋아요. 복잡한 건 나중에 필요할 때 배우면 됩니다."
실전 팁
💡 - 개발 중에는 Client()로 메모리 모드를 사용하면 테스트가 빠릅니다.
- 커스텀 임베딩 모델을 사용하려면 embedding_function 파라미터를 설정하세요.
3. Pinecone 클라우드 서비스
김개발 씨의 프로토타입이 호응을 얻어 정식 서비스로 발전하게 되었습니다. 하지만 문제가 생겼습니다.
사용자가 늘어나면서 로컬 Chroma로는 감당이 안 되기 시작한 것입니다. "서버 관리까지 해야 하나요?" 걱정하는 김개발 씨에게 박시니어 씨가 새로운 선택지를 알려주었습니다.
Pinecone은 완전 관리형 클라우드 벡터 데이터베이스 서비스입니다. 마치 AWS RDS가 데이터베이스 서버 관리를 대신해주듯, Pinecone은 벡터 DB의 스케일링, 백업, 고가용성을 모두 알아서 처리합니다.
개발자는 API만 호출하면 되므로 인프라 걱정 없이 비즈니스 로직에 집중할 수 있습니다.
다음 코드를 살펴봅시다.
# Pinecone 설치: pip install pinecone-client
from pinecone import Pinecone, ServerlessSpec
# 클라이언트 초기화 (API 키 필요)
pc = Pinecone(api_key="your-api-key")
# 인덱스 생성 (처음 한 번만)
pc.create_index(
name="documents",
dimension=384,
metric="cosine",
spec=ServerlessSpec(cloud="aws", region="us-east-1")
)
# 인덱스 연결
index = pc.Index("documents")
# 벡터 업서트 (삽입 또는 업데이트)
index.upsert(vectors=[
{"id": "doc1", "values": [0.1, 0.2, ...], "metadata": {"title": "휴가 규정"}},
{"id": "doc2", "values": [0.3, 0.1, ...], "metadata": {"title": "연차 안내"}}
])
# 유사 벡터 검색
results = index.query(vector=[0.15, 0.18, ...], top_k=5, include_metadata=True)
프로토타입이 성공하자 팀장님이 기뻐했습니다. "좋아요!
이제 전사 서비스로 확대합시다." 김개발 씨는 기쁘면서도 걱정이 됐습니다. 수십 명이 쓰던 시스템을 수천 명이 쓰게 되면 어떻게 될까요?
Chroma를 서버에 올리는 방법도 있었지만, 김개발 씨는 인프라 전문가가 아니었습니다. 로드밸런싱, 레플리카, 백업...
생각만 해도 머리가 아팠습니다. 박시니어 씨가 제안했습니다.
"클라우드 서비스를 쓰는 건 어때요? Pinecone이라고, 벡터 DB계의 AWS RDS 같은 거예요." Pinecone은 완전 관리형 서비스입니다.
이 말은 서버 프로비저닝, 스케일링, 백업, 보안 패치 등 모든 인프라 작업을 Pinecone이 대신 해준다는 뜻입니다. 개발자는 API 키를 받아서 코드만 작성하면 됩니다.
마치 스마트폰에서 앱을 설치하듯, 복잡한 내부 동작은 몰라도 사용할 수 있습니다. Pinecone의 핵심 개념은 인덱스입니다.
Chroma의 컬렉션과 비슷하지만, 인덱스를 생성할 때 벡터의 차원을 미리 지정해야 합니다. 임베딩 모델마다 출력 차원이 다르기 때문입니다.
all-MiniLM-L6-v2 모델은 384차원, OpenAI의 text-embedding-ada-002는 1536차원입니다. 위의 코드에서 눈여겨볼 부분은 ServerlessSpec입니다.
과거 Pinecone은 Pod라는 고정 서버를 선택해야 했지만, Serverless 옵션이 생기면서 사용량에 따라 자동으로 확장됩니다. 트래픽이 적을 때는 비용이 적게 나오고, 많을 때는 자동으로 처리량이 늘어납니다.
Chroma와 다른 점 중 하나는 임베딩을 직접 전달해야 한다는 것입니다. Chroma는 텍스트를 넣으면 자동으로 벡터로 변환해주지만, Pinecone에는 이미 변환된 벡터를 넣어야 합니다.
따라서 별도로 임베딩 모델을 호출하는 코드가 필요합니다. 이것이 불편해 보일 수 있지만, 대신 어떤 임베딩 모델이든 자유롭게 선택할 수 있다는 장점이 있습니다.
upsert라는 용어도 눈에 띕니다. update와 insert를 합친 말로, 해당 ID가 없으면 삽입하고 있으면 업데이트합니다.
문서가 수정되었을 때 간편하게 동기화할 수 있습니다. 비용 측면에서 Pinecone의 Serverless는 무료 티어를 제공합니다.
벡터 10만 개까지는 무료로 사용할 수 있어서, 사이드 프로젝트나 스타트업 초기에 부담 없이 시작할 수 있습니다. 다만 대규모로 확장하면 비용이 올라가므로, 예산을 미리 계산해보는 것이 좋습니다.
주의할 점은 네트워크 지연입니다. Pinecone 서버가 미국에 있으므로 한국에서 호출하면 응답 시간이 수십 밀리초 추가됩니다.
실시간 응답이 중요한 서비스라면 이 점을 고려해야 합니다. 김개발 씨는 Pinecone으로 마이그레이션한 후 밤잠을 편히 잘 수 있게 되었습니다.
"서버가 죽으면 어쩌지?" 하는 걱정을 더 이상 하지 않아도 되었기 때문입니다.
실전 팁
💡 - 무료 티어로 시작해서 트래픽을 확인한 후 유료 플랜을 고려하세요.
- 네임스페이스를 활용하면 하나의 인덱스에서 데이터를 논리적으로 분리할 수 있습니다.
4. Faiss 로컬 검색
어느 날 김개발 씨에게 특별한 요구사항이 들어왔습니다. "보안 때문에 데이터를 외부로 보낼 수 없어요.
완전히 오프라인에서 동작해야 합니다." 클라우드 서비스는 사용할 수 없는 상황이었습니다. 이럴 때 사용할 수 있는 강력한 도구가 있습니다.
Faiss는 페이스북 AI 연구소에서 만든 고성능 벡터 검색 라이브러리입니다. 마치 레이싱카처럼 순수한 속도에 최적화되어 있어서, 수억 개의 벡터에서도 밀리초 단위로 검색이 가능합니다.
완전히 로컬에서 동작하므로 보안이 중요한 환경에서 활용할 수 있습니다.
다음 코드를 살펴봅시다.
# Faiss 설치: pip install faiss-cpu (또는 faiss-gpu)
import faiss
import numpy as np
# 벡터 차원 설정
dimension = 384
# 플랫 인덱스 생성 (정확한 검색)
index = faiss.IndexFlatL2(dimension)
# 벡터 준비 (float32 타입이어야 함)
vectors = np.array([
[0.1, 0.2, 0.3, ...], # 문서1의 임베딩
[0.4, 0.5, 0.6, ...], # 문서2의 임베딩
], dtype=np.float32)
# 벡터 추가
index.add(vectors)
print(f"저장된 벡터 수: {index.ntotal}")
# 검색 (query도 float32 배열이어야 함)
query = np.array([[0.15, 0.25, 0.35, ...]], dtype=np.float32)
distances, indices = index.search(query, k=5) # 상위 5개 검색
print(f"가장 유사한 문서 인덱스: {indices[0]}")
김개발 씨가 맡은 새 프로젝트는 금융권 고객사를 위한 것이었습니다. 금융 데이터는 규제가 엄격해서 클라우드 서비스 사용이 제한되었습니다.
모든 데이터가 사내 서버를 벗어나면 안 되었습니다. "그럼 Chroma를 쓰면 되지 않나요?" 김개발 씨가 물었습니다.
박시니어 씨가 대답했습니다. "Chroma도 괜찮지만, 데이터가 수천만 건이래요.
속도가 문제가 될 수 있어요. 이럴 때는 Faiss가 제격이에요." Faiss는 Facebook AI Research에서 개발한 벡터 검색 라이브러리입니다.
다른 벡터 DB와 달리, Faiss는 순수하게 벡터 검색 알고리즘에만 집중합니다. 데이터베이스 기능이 없어서 ID나 메타데이터는 별도로 관리해야 하지만, 그 대신 검색 속도가 극도로 빠릅니다.
Faiss가 빠른 이유는 다양한 인덱싱 알고리즘을 지원하기 때문입니다. IndexFlatL2는 가장 기본적인 인덱스로, 모든 벡터와 거리를 계산하는 정확한 검색을 합니다.
데이터가 작을 때는 이것만으로 충분합니다. 하지만 데이터가 수백만 개를 넘어가면 근사 검색 인덱스를 사용합니다.
IndexIVFFlat, IndexHNSW 같은 인덱스는 100% 정확하지는 않지만, 수십 배 빠른 속도를 제공합니다. 99%의 정확도로 1000배 빠르게 검색할 수 있다면, 대부분의 경우 좋은 선택입니다.
위의 코드에서 주의할 점은 데이터 타입입니다. Faiss는 numpy 배열을 사용하며, 반드시 float32 타입이어야 합니다.
float64를 넣으면 에러가 발생합니다. 또한 검색 결과로 인덱스만 반환하므로, 실제 문서 내용은 별도의 리스트나 딕셔너리에서 가져와야 합니다.
GPU 버전인 faiss-gpu를 사용하면 NVIDIA GPU의 병렬 처리 능력을 활용해서 더욱 빠른 검색이 가능합니다. 수억 개의 벡터를 다루는 대규모 시스템에서는 GPU 버전이 필수입니다.
Faiss의 또 다른 장점은 인덱스 저장과 로드입니다. faiss.write_index()와 faiss.read_index()로 인덱스를 파일로 저장하고 불러올 수 있습니다.
서버가 재시작되어도 인덱스를 처음부터 다시 만들 필요가 없습니다. 단점은 러닝 커브입니다.
Chroma나 Pinecone에 비해 사용법이 복잡하고, 벡터 검색에 대한 이해가 필요합니다. 또한 메타데이터 필터링, 텍스트 자동 임베딩 같은 편의 기능이 없어서 직접 구현해야 합니다.
김개발 씨는 Faiss로 벡터 검색 엔진을 구축하고, 별도로 문서 메타데이터를 관리하는 시스템을 만들었습니다. 처음에는 복잡했지만, 완성된 시스템은 놀라운 속도를 보여주었습니다.
수천만 건의 문서에서 10밀리초 안에 검색 결과가 나왔습니다.
실전 팁
💡 - 데이터가 100만 개 이상이면 IndexIVFFlat 같은 근사 검색 인덱스를 사용하세요.
- 벡터와 함께 원본 데이터를 저장할 별도의 매핑 시스템이 필요합니다.
5. 유사도 검색 전략
김개발 씨는 벡터 검색 시스템을 운영하면서 이상한 점을 발견했습니다. 분명히 관련 있어 보이는 문서가 검색 결과 상위에 나오지 않는 경우가 있었습니다.
"왜 이 문서가 더 높은 순위가 아니지?" 유사도 검색의 세계는 생각보다 복잡했습니다.
유사도 검색 전략은 벡터 간의 거리를 어떻게 측정할지 결정하는 방법입니다. 마치 두 사람이 얼마나 비슷한지를 키로 비교할지, 체중으로 비교할지, 성격으로 비교할지 정하는 것과 같습니다.
코사인 유사도, 유클리드 거리, 내적 등 다양한 방법이 있으며, 데이터 특성에 맞는 전략을 선택해야 좋은 검색 결과를 얻을 수 있습니다.
다음 코드를 살펴봅시다.
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances
# 예제 벡터
query = np.array([[0.5, 0.3, 0.8]])
doc1 = np.array([[0.5, 0.3, 0.9]]) # 쿼리와 방향 비슷
doc2 = np.array([[1.0, 0.6, 1.6]]) # 쿼리의 2배 (방향 같음)
doc3 = np.array([[0.1, 0.9, 0.2]]) # 방향 다름
# 코사인 유사도: 방향만 비교 (크기 무시)
cos_sim1 = cosine_similarity(query, doc1)[0][0] # ~0.99
cos_sim2 = cosine_similarity(query, doc2)[0][0] # ~1.0 (방향 같음)
cos_sim3 = cosine_similarity(query, doc3)[0][0] # ~0.5
# 유클리드 거리: 절대적 거리 비교
euc_dist1 = euclidean_distances(query, doc1)[0][0] # ~0.1
euc_dist2 = euclidean_distances(query, doc2)[0][0] # ~1.0 (크기 차이)
euc_dist3 = euclidean_distances(query, doc3)[0][0] # ~0.9
print(f"코사인: {cos_sim1:.2f}, {cos_sim2:.2f}, {cos_sim3:.2f}")
print(f"유클리드: {euc_dist1:.2f}, {euc_dist2:.2f}, {euc_dist3:.2f}")
어느 날 마케팅팀에서 불만이 들어왔습니다. "검색 결과가 이상해요.
이 문서가 왜 저 문서보다 아래에 있죠?" 김개발 씨는 로그를 분석해보았습니다. 문제는 유사도 측정 방법에 있었습니다.
벡터 간의 유사도를 측정하는 방법은 여러 가지가 있고, 각각 다른 결과를 냅니다. 마케팅팀이 기대한 결과와 시스템이 사용하는 방법이 맞지 않았던 것입니다.
가장 널리 쓰이는 방법은 코사인 유사도입니다. 두 벡터 사이의 각도를 측정합니다.
벡터의 크기는 무시하고 방향만 봅니다. 예를 들어 [1, 0]과 [100, 0]은 크기는 다르지만 방향이 같으므로 코사인 유사도가 1입니다.
코사인 유사도가 텍스트 검색에 많이 쓰이는 이유가 있습니다. 문서의 길이가 다르면 임베딩 벡터의 크기도 달라질 수 있습니다.
긴 문서가 항상 더 관련성이 높은 것은 아니므로, 크기를 무시하고 방향만 보는 것이 합리적입니다. 유클리드 거리는 두 점 사이의 직선 거리입니다.
고등학교에서 배운 피타고라스 정리를 다차원으로 확장한 것입니다. 거리가 가까울수록 유사합니다.
유클리드 거리는 벡터의 크기도 고려하므로, 코사인과 다른 결과가 나올 수 있습니다. 위의 코드를 보면 doc2는 query의 정확히 2배입니다.
코사인 유사도로는 둘이 완벽히 같은 방향이므로 1.0이 나옵니다. 하지만 유클리드 거리로는 1.0이라는 상당한 거리가 있습니다.
어떤 것이 맞을까요? 정답은 없습니다.
용도에 따라 다릅니다. 내적은 두 벡터를 곱해서 더한 값입니다.
코사인 유사도와 비슷하지만, 벡터의 크기도 반영됩니다. 추천 시스템에서 사용자 벡터와 아이템 벡터의 내적을 구하면, 크기가 큰 벡터가 더 강한 선호를 나타낸다고 해석할 수 있습니다.
실무에서 어떤 방법을 선택해야 할까요? 텍스트 임베딩 검색에는 코사인 유사도가 대부분 좋은 선택입니다.
OpenAI, Sentence-Transformers 등 대부분의 임베딩 모델이 코사인 유사도에 최적화되어 있습니다. 하지만 특수한 경우도 있습니다.
이미지 검색에서는 유클리드 거리가 더 나을 수 있습니다. 추천 시스템에서 선호도 강도를 반영하려면 내적이 적합합니다.
정답은 데이터와 실험으로 찾아야 합니다. 김개발 씨는 마케팅팀과 함께 A/B 테스트를 진행했습니다.
코사인 유사도와 유클리드 거리로 각각 검색 결과를 보여주고, 사용자 만족도를 측정했습니다. 결국 이 프로젝트에서는 코사인 유사도가 더 나은 결과를 보였습니다.
하지만 이것은 이 데이터셋에서의 결과일 뿐, 다른 프로젝트에서는 다를 수 있습니다.
실전 팁
💡 - 텍스트 검색은 코사인 유사도로 시작하고, 결과가 안 좋으면 다른 방법을 시도하세요.
- 벡터 정규화(normalize)를 하면 코사인 유사도와 유클리드 거리가 같은 순위를 냅니다.
6. 메타데이터 필터링
검색 시스템이 잘 동작하자 새로운 요구사항이 생겼습니다. "기술 문서만 검색하고 싶어요", "최근 1년 내 문서만 보여주세요." 단순히 의미가 비슷한 문서를 찾는 것만으로는 부족했습니다.
조건에 맞는 문서 중에서만 검색해야 했습니다.
메타데이터 필터링은 벡터 검색에 조건을 추가하는 기능입니다. 마치 온라인 쇼핑몰에서 "운동화"를 검색하면서 "브랜드: 나이키, 가격: 10만원 이하"로 필터링하는 것과 같습니다.
벡터 유사도로 관련성을 찾고, 메타데이터 필터로 범위를 좁힘으로써 더 정확한 검색 결과를 제공할 수 있습니다.
다음 코드를 살펴봅시다.
import chromadb
client = chromadb.Client()
collection = client.create_collection("products")
# 메타데이터와 함께 문서 추가
collection.add(
documents=["나이키 에어맥스 운동화", "아디다스 런닝화", "나이키 조던 농구화"],
ids=["p1", "p2", "p3"],
metadatas=[
{"brand": "nike", "category": "running", "price": 150000},
{"brand": "adidas", "category": "running", "price": 120000},
{"brand": "nike", "category": "basketball", "price": 200000}
]
)
# 메타데이터 필터링과 함께 검색
results = collection.query(
query_texts=["편한 운동화"],
n_results=10,
where={
"$and": [
{"brand": {"$eq": "nike"}},
{"price": {"$lte": 160000}}
]
}
)
# 결과: 나이키 에어맥스만 반환 (나이키 + 16만원 이하)
print(results["documents"])
김개발 씨의 문서 검색 시스템은 이제 전사적으로 사용되고 있었습니다. 그런데 법무팀에서 요청이 왔습니다.
"계약 관련 문서를 검색하고 싶은데, 최근 2년 이내 문서만 보고 싶어요. 오래된 문서는 법률이 바뀌어서 참고하면 안 되거든요." 단순한 벡터 검색만으로는 이 요구사항을 충족할 수 없었습니다.
"계약서 작성 방법"을 검색하면, 10년 전 문서든 어제 올라온 문서든 의미적으로 비슷하면 모두 나왔습니다. 시간 조건을 걸 방법이 필요했습니다.
바로 이때 필요한 것이 메타데이터 필터링입니다. 벡터와 함께 저장한 추가 정보를 기준으로 검색 범위를 제한하는 기능입니다.
메타데이터는 벡터에 붙이는 라벨이라고 생각하면 됩니다. 문서라면 작성일, 작성자, 부서, 카테고리 같은 정보를 메타데이터로 저장할 수 있습니다.
상품이라면 브랜드, 가격, 카테고리 등이 될 수 있습니다. 위의 코드에서 where 절이 핵심입니다.
$and로 여러 조건을 결합하고, $eq는 같음을, $lte는 이하를 의미합니다. SQL의 WHERE 절과 비슷한 역할을 합니다.
"편한 운동화"라는 의미 검색과 "나이키 브랜드, 16만원 이하"라는 조건 필터가 동시에 적용됩니다. 지원되는 연산자는 벡터 DB마다 조금씩 다르지만, 대부분 기본적인 비교 연산자를 지원합니다.
$eq(같음), $ne(다름), $gt(초과), $gte(이상), $lt(미만), $lte(이하), $in(포함) 등이 있습니다. Pinecone에서는 비슷하게 filter 파라미터를 사용합니다.
문법은 약간 다르지만 개념은 같습니다. Faiss는 자체적인 필터링 기능이 없어서, 검색 후 결과를 별도로 필터링하거나, 필터링된 인덱스를 따로 만들어야 합니다.
실무에서 메타데이터 필터링은 매우 중요합니다. 대부분의 검색 시나리오에서 순수한 의미 검색만으로는 부족하기 때문입니다.
쇼핑몰에서 "멋진 옷"을 검색할 때 상의만 보고 싶을 수 있고, 뉴스 검색에서 최근 기사만 보고 싶을 수 있습니다. 주의할 점은 인덱싱입니다.
자주 필터링하는 메타데이터 필드는 인덱스가 있어야 성능이 좋습니다. 대부분의 벡터 DB가 자동으로 인덱스를 생성하지만, 대규모 데이터에서는 확인이 필요합니다.
또 다른 고려사항은 필터 순서입니다. 일부 벡터 DB는 먼저 벡터 검색을 하고 결과를 필터링합니다.
이 경우 필터 조건이 엄격하면 결과가 너무 적을 수 있습니다. 반대로 먼저 필터링하고 벡터 검색을 하면 더 정확하지만 성능이 떨어질 수 있습니다.
김개발 씨는 문서에 작성일, 부서, 문서유형 등의 메타데이터를 추가했습니다. 이제 법무팀은 "계약 관련, 최근 2년, 법무팀 작성"이라는 조건으로 정확히 원하는 문서만 검색할 수 있게 되었습니다.
실전 팁
💡 - 자주 사용하는 필터 조건은 메타데이터에 포함시키고, 값을 정규화해서 저장하세요.
- 필터 조건이 너무 엄격하면 좋은 검색 결과를 놓칠 수 있으니 적절히 조절하세요.
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.