임베딩과 벡터 검색의 직관
앞 글에서 RAG는 "검색 + 생성"이라고 했다. 그런데 RAG 글을 읽다 보면 자꾸 나오는 말이 있다.
임베딩(embedding)
벡터 검색(vector search)
유사도(similarity)
의미 검색(semantic search)
이게 다 뭐냐. 이번 글에서는 한 줄로 끝나는 비유부터 시작해서, 왜 이게 검색에 쓰이는지를 풀어본다.
임베딩 = 글의 의미를 좌표로 바꾸기
임베딩의 정의는 한 줄이다.
임베딩 = 글의 의미를 N차원 좌표(벡터)로 바꾸는 것
쉬운 비유로 가자. 지도 위에 도시를 점으로 찍는다고 해보자.
서울 (37.5, 127.0)
부산 (35.1, 129.0)
도쿄 (35.6, 139.7)
뉴욕 (40.7, -74.0)
좌표만 보고도 알 수 있다.
서울과 부산은 가깝다 (위도/경도가 비슷)
서울과 뉴욕은 멀다 (위도/경도가 완전 다름)
임베딩은 글에 똑같은 짓을 한다. "의미가 비슷한 글은 가까운 좌표에 찍어두자" 는 게 핵심이다.
"휴가 며칠이야?" → (0.8, 0.2)
"연차 얼마나 있어?" → (0.78, 0.22) ← 가까움 (의미 비슷)
"점심값 얼마야?" → (-0.5, 0.9) ← 멈 (의미 다름)
실제로는 2차원이 아니라 보통 768차원, 1536차원, 3072차원 같은 고차원 공간이다. 하지만 직관은 똑같다.
의미가 비슷한 글 → 좌표가 가까움
의미가 다른 글 → 좌표가 멀음
그래서 검색이 어떻게 되는가
벡터 검색은 이렇게 동작한다.
순서로 풀면 이렇다.
1. 미리 모든 문서를 임베딩해서 좌표로 만들어둠
2. 사용자 질문도 임베딩해서 좌표로 만듦
3. 질문 좌표에 가장 가까운 문서 좌표들을 꺼냄
그림으로 보면 이렇다.
문서A • 문서C •
• 질문
문서B •
문서D •
질문 좌표에 가까운 순서:
1. 문서A ← 가장 가까움
2. 문서B
3. 문서C
4. 문서D ← 가장 멈
이걸 그냥 정렬해서 top-k(예: top-3)를 꺼내면 검색 끝이다.
키워드 검색이랑 뭐가 다른가
지금까지 우리가 익숙한 검색은 대부분 키워드 검색이다.
검색어: "휴가"
↓
"휴가"라는 글자가 들어간 문서를 다 찾음
이건 빠르고 정확하지만, 한 가지 약점이 있다.
질문: "연차 얼마나 있어?"
문서: "휴가는 연 15일이다"
키워드 검색:
"연차"라는 글자가 문서에 없음 → 못 찾음
같은 의미라도 단어가 다르면 못 찾는다. 벡터 검색은 의미로 찾기 때문에 이걸 해결한다.
질문: "연차 얼마나 있어?" → 좌표 (0.78, 0.22)
문서: "휴가는 연 15일이다" → 좌표 (0.80, 0.20)
벡터 검색:
좌표가 가까움 → 찾아냄
이게 의미 검색(semantic search)의 정체다.
그럼 키워드 검색은 버려도 되나
아니다. 오히려 벡터 검색에도 약점이 있다.
질문: "에러 코드 4023이 뭐야?"
문서: "에러 코드 4023은 결제 한도 초과를 의미합니다"
벡터 검색:
"4023"이라는 숫자는 임베딩에서 의미가 약함
→ 의외로 못 찾을 수 있음
키워드 검색:
"4023"이 정확히 매칭됨 → 잘 찾음
정리하면 이렇다.
벡터 검색 : 의미가 비슷한 걸 찾는 데 강함
(유의어, 다른 표현, 풀어쓴 질문)
키워드 검색: 정확한 단어를 찾는 데 강함
(에러 코드, 고유명사, 사람 이름, 숫자, 코드 식별자)
그래서 실무에서는 둘을 같이 쓴다. 이걸 Hybrid Retrieval이라고 부르고, 다음 시리즈에서 자세히 다룬다.
임베딩 모델은 어디서 오나
임베딩을 만들려면 임베딩 모델이 필요하다. 대표적인 옵션은 이렇다.
- OpenAI text-embedding-3-small / -large
- Cohere embed-multilingual
- Voyage AI voyage-3
- BGE 시리즈 (BAAI/bge-m3 등)
- 한국어 특화: KURE, KoSimCSE 등
선택 기준은 보통 이렇다.
- 한국어 잘 되는가
- 차원 수가 너무 크지 않은가 (저장 비용)
- 가격이 합리적인가
- 자체 호스팅이 필요한가 (보안/규제)
실무에서는 처음에는 OpenAI text-embedding-3-small 같은 걸로 시작하고, 한국어 검색 품질이 안 나오면 한국어 특화 모델로 갈아끼우는 식이 흔하다.
벡터 DB는 어디에 저장하나
만든 임베딩은 어딘가에 저장해서 빠르게 검색할 수 있어야 한다. 이걸 해주는 게 벡터 DB 또는 벡터 인덱스다.
전용 벡터 DB
- Pinecone (관리형, 빠름)
- Qdrant (오픈소스)
- Weaviate
- Milvus
기존 DB의 벡터 확장
- PostgreSQL + pgvector
- Elasticsearch + dense_vector
- OpenSearch k-NN
작은 프로젝트라면
- FAISS (인메모리 인덱스, 파이썬 라이브러리)
- Chroma (간단한 로컬 DB)
실무에서 의외로 많이 쓰는 조합은 이렇다.
PostgreSQL + pgvector
- 기존에 쓰던 DB라 운영팀이 익숙함
- SQL 필터 + 벡터 검색을 한 쿼리로 묶기 쉬움
- 작은~중간 규모에서 충분
Elasticsearch + Vector
- 키워드 검색이랑 같이 써야 할 때
- Hybrid Retrieval하기 좋음
정리
- 임베딩은 글의 의미를 좌표(벡터)로 바꾸는 것이다
- 의미가 비슷한 글은 좌표가 가깝다 → 그래서 의미 검색이 된다
- 벡터 검색은 유의어/다른 표현에 강하고, 키워드 검색은 정확한 식별자에 강하다
- 실무에서는 둘을 같이 쓴다 (Hybrid)
- 임베딩 모델은 OpenAI, Cohere, BGE 등에서 고른다
- 저장은 pgvector / Elasticsearch / Pinecone 같은 곳에 한다
다음 글에서는 드디어 코드를 짠다. LangChain + LangGraph + Langfuse를 이용해서 가장 작은 RAG 에이전트를 만들고, Langfuse 대시보드에서 trace를 확인하는 것까지 해본다.