백엔드 데이터 인프라 77편. Redis Vector Database — AI 임베딩 유사도 검색. VectorField·HNSW·FLAT 인덱스, COSINE·L2·IP 거리 메트릭, RAG(검색 보강 생성)·시맨틱 검색 패턴, Pinecone·Weaviate·pgvector 와의 비교까지 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 130편 중 77편이에요. 76편 까지 Time Series 를 풀었다면, 이번 77편은 가장 최근에 핫해진 영역 — Vector Database. RediSearch(Redis 검색·인덱스 모듈) 의 VectorField 로 AI 임베딩 기반 유사도 검색. RAG (검색 보강 생성) 의 핵심 인프라. Part 4-7 모듈의 마지막 글.
Vector Database가 어렵게 느껴지는 이유
"벡터·임베딩·HNSW·코사인 유사도" 같은 ML 용어 가 한꺼번에 등장. 백엔드 개발자에게는 처음 보는 어휘.
첫째, 임베딩이 뭔지 가 안 잡힙니다. "단어·문장·이미지를 숫자 배열로 변환" — 그게 왜 검색에 유용한지 가 직관적이지 않음.
둘째, HNSW·FLAT 알고리즘 이 새 영역. "FLAT 은 brute force, HNSW 는 그래프 기반" — 추상 개념을 알아도 실무 선택 기준 이 명확하지 않음.
셋째, 유사도 메트릭 3가지 (COSINE·L2·IP) 가 언제 어느 걸 쓸지 헷갈림.
이 글에서 임베딩 기초·VectorField 인덱스·HNSW/FLAT 선택·메트릭 3가지·RAG 패턴·전문 Vector DB 와의 비교까지.
임베딩 기초 — 한 줄
임베딩 (embedding) = 텍스트·이미지·오디오를 N차원 숫자 배열로 변환한 것.
예제:
"고양이" → [0.12, 0.34, -0.56, ..., 0.78] # 768차원
"강아지" → [0.15, 0.31, -0.52, ..., 0.71] # 비슷한 위치
"자동차" → [-0.42, 0.91, 0.05, ..., -0.23] # 다른 위치
핵심 — 의미가 비슷한 것은 벡터 공간에서 가까운 위치. 유사도 = 벡터 거리.
생성 모델 예시:
- 텍스트 — OpenAI
text-embedding-3-small(1536차원) - 한국어 — KoBERT, KoSimCSE 등
- 이미지 — CLIP
- 다국어 — Multilingual-E5
VectorField — Vector 인덱스 생성
from redis.commands.search.field import VectorField, TextField, NumericField
from redis.commands.search.index_definition import IndexDefinition, IndexType
schema = (
TextField("$.title", as_name="title"),
NumericField("$.price", as_name="price"),
VectorField(
"$.embedding",
"HNSW", # 알고리즘
{
"TYPE": "FLOAT32",
"DIM": 768, # 차원
"DISTANCE_METRIC": "COSINE",
"M": 16, # HNSW 파라미터
"EF_CONSTRUCTION": 200,
},
as_name="vector",
),
)
definition = IndexDefinition(prefix=["doc:"], index_type=IndexType.JSON)
client.ft("idx:docs").create_index(fields=schema, definition=definition)
핵심 옵션:
HNSWvsFLAT— 알고리즘DIM— 임베딩 차원 (모델에 따라)DISTANCE_METRIC— 유사도 메트릭
HNSW vs FLAT — 선택
FLAT — Brute Force
모든 벡터와 exhaustively 거리 계산. 정확하지만 느림.
- 장점 — 100% 정확한 결과
- 단점 — O(N) — 100만 벡터에 수 초~수십 초
- 적합 — 수천~수만 벡터, 정확도 결정적
HNSW (Hierarchical Navigable Small World) — Approximate
그래프 기반 근사 알고리즘. 매우 빠르지만 약간 부정확.
- 장점 — O(log N) ~ — 수백만 벡터에 수 ms
- 단점 — 약간 부정확 (recall(검색 적중률) 90~99%)
- 적합 — 수십만~수억 벡터, 속도 결정적
파라미터:
M= 그래프 연결 수 (기본 16) — 클수록 정확도 ↑ + 메모리 ↑EF_CONSTRUCTION= 인덱스 빌드 시 탐색 폭 (기본 200) — 클수록 정확 + 빌드 시간 ↑EF_RUNTIME= 쿼리 시 탐색 폭 — 클수록 정확 + 응답 시간 ↑
대부분 환경 = HNSW + 기본 파라미터.
유사도 메트릭 3가지
COSINE (코사인 유사도)
벡터 사이 각도 기준. 방향만 본다 (크기 무시).
similarity = (A · B) / (||A|| × ||B||)
- 범위 = -1 ~ 1 (RediSearch 는 거리 로 0~2 변환)
- 자주 쓰는 자리 = 텍스트 임베딩 (대부분 정규화되어 있음)
L2 (Euclidean Distance)
직선 거리.
distance = sqrt(sum((A_i - B_i)^2))
- 자주 쓰는 자리 = 이미지 픽셀·정규화 안 된 벡터
IP (Inner Product)
내적 (dot product).
- 자주 쓰는 자리 = 추천 시스템·임베딩이 정규화 됐을 때만 COSINE 과 동등
선택
99% 환경에서 COSINE. OpenAI·대부분 텍스트 모델이 정규화된 임베딩 반환 → 코사인이 표준.
검색 — KNN 쿼리
KNN(K-Nearest Neighbors, K개 최근접 이웃) 으로 가장 가까운 k 개 벡터를 뽑는다.
from redis.commands.search.query import Query
query_vector = np.array([0.12, 0.34, ...]).astype(np.float32).tobytes()
q = Query("*=>[KNN 5 @vector $vec AS score]") \
.return_fields("title", "score") \
.sort_by("score") \
.dialect(2)
results = client.ft("idx:docs").search(q, query_params={"vec": query_vector})
for doc in results.docs:
print(doc.title, doc.score)
핵심 syntax — *=>[KNN k @vector $param AS alias]:
*= 사전 필터 (모두) — 다른 필드 필터 가능KNN k= top k 유사@vector= vector 필드 이름$param= 바인드된 query 벡터AS alias= 점수 alias
사전 필터 (Hybrid Search)
@price:[100 500] @category:{electronics} =>[KNN 5 @vector $vec AS score]
벡터 검색 전에 일반 필터 적용. 가격대 안에서 유사한 상품 같은 hybrid search.
RAG (Retrieval Augmented Generation) 패턴
LLM 활용의 가장 흔한 패턴.
흐름
1. 문서를 chunk 로 분할 (수백 token 단위)
2. 각 chunk → 임베딩 모델 → 벡터
3. Redis 에 (chunk text + vector) 저장
4. 사용자 질문 → 임베딩 → 벡터
5. Redis 에서 KNN 으로 가장 유사한 chunk 5~10 개 검색
6. 검색된 chunks + 질문 → LLM 프롬프트
7. LLM 이 chunks 기반으로 답변 생성
여기서 chunk 는 문서를 잘게 자른 짧은 텍스트 조각 을 가리킨다.
코드 스케치
# 색인 단계
for chunk in document_chunks:
embedding = openai.embeddings.create(model="text-embedding-3-small", input=chunk)
r.json().set(f"doc:{chunk_id}", "$", {
"text": chunk,
"embedding": embedding.data[0].embedding,
})
# 질의 단계
question = "Redis Vector 가 뭔가요?"
q_embed = openai.embeddings.create(model="text-embedding-3-small", input=question)
q_bytes = np.array(q_embed.data[0].embedding, dtype=np.float32).tobytes()
results = client.ft("idx:docs").search(
Query("*=>[KNN 5 @vector $vec AS score]").dialect(2),
{"vec": q_bytes}
)
context = "\n\n".join([d.text for d in results.docs])
answer = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": f"다음 문서들 기반으로 답: {context}"},
{"role": "user", "content": question},
]
)
Redis Vector vs 전문 Vector DB
| 항목 | Redis | Pinecone | Weaviate | pgvector | Milvus |
|---|---|---|---|---|---|
| 호스팅 모델 | self-host | SaaS only | self-host / SaaS | self-host | self-host |
| 지연 시간 | μs | ms | ms | ms | ms |
| 메모리 모델 | 인메모리 | 관리형 | 디스크 | 디스크 (PG) | 디스크 |
| 데이터 크기 | 메모리 한계 | 수십억 벡터 | 수십억 | 수백만 | 수십억 |
| Hybrid Search | ◯ (RediSearch 통합) | ◯ | ◯ | ◯ (SQL) | ◯ |
| 메타데이터 + 필터 | ◯ | ◯ | ◯ | ◯ (SQL JOIN) | ◯ |
| 운영 복잡도 | 낮음 | 매우 낮음 (SaaS) | 중간 | 낮음 (PG 있으면) | 높음 |
| 비용 (대규모) | 메모리 비용 큼 | 사용량 과금 | 자체 호스팅 | PG 비용 | 자체 호스팅 |
선택 가이드
- 이미 Redis 있음 + 수십만~수백만 벡터 + μs 응답 → Redis Vector
- 수억 벡터 + 관리형 SaaS → Pinecone
- 이미 PG 있음 + 수백만 벡터 + SQL 결합 필요 → pgvector
- 수십억 벡터 + 자체 호스팅 → Milvus / Weaviate
대부분의 중소 RAG 응용 은 Redis 또는 pgvector 로 충분. 진짜 거대 벡터 인덱스 만 전문 Vector DB.
Spring 통합
Spring AI 의 Vector Store 추상화에 Redis 포함:
@Configuration
public class VectorConfig {
@Bean
public VectorStore vectorStore(JedisPooled jedisPooled, EmbeddingModel embeddingModel) {
return RedisVectorStore.builder(jedisPooled, embeddingModel)
.indexName("idx:docs")
.prefix("doc:")
.build();
}
}
@Autowired
private VectorStore vectorStore;
vectorStore.add(List.of(new Document("Redis 는 ...")));
List<Document> results = vectorStore.similaritySearch("Redis 뭔가요?");
한계·실무 함정
1. 메모리 비용
차원 768 × float32 4byte × 100만 벡터 = 3GB. 거대 벡터 인덱스는 메모리 비용 큼. Pinecone·Weaviate 가 자주 선택되는 이유.
2. 임베딩 모델 비용
OpenAI text-embedding-3-small = $0.02 / 1M tokens. 100만 문서 × 평균 500 tokens = $10. 거대 색인은 비용 고려.
3. recall 트레이드오프
HNSW = 약간 부정확. 정확도 결정적 인 자리는 FLAT 또는 EF_RUNTIME 크게.
4. 임베딩 모델 일관성
색인 시점과 쿼리 시점 같은 모델 사용 필수. 다른 모델 = 완전 다른 벡터 공간 = 무의미한 결과.
5. 사전 필터 vs 사후 필터
*=>[KNN k @vector $vec] 패턴은 사전 필터. 메타데이터 필터를 KNN 결과 후 적용하는 사후 필터 도 가능하지만 결과 수가 줄어듦 — 일반적으로 사전 필터 권장.
시험 직전 한 번 더 — Vector Database 함정 압축 노트
- 임베딩 = 텍스트·이미지·오디오 → N차원 숫자 배열
- 의미 비슷 = 벡터 공간 가까운 위치
- VectorField — RediSearch 안에서 정의 (
HNSW·FLAT알고리즘) - HNSW vs FLAT — HNSW = O(log N) 빠른 근사 / FLAT = O(N) 정확
- HNSW 파라미터 = M (그래프 연결 수)·EF_CONSTRUCTION·EF_RUNTIME
- 대부분 환경 = HNSW + 기본 파라미터
- 3가지 메트릭 = COSINE (텍스트, 99%) · L2 (이미지) · IP (정규화 시 COSINE 과 동등)
- 99% 텍스트 환경 = COSINE
- 검색 syntax =
*=>[KNN k @vector $param AS score] - Hybrid Search =
@price:[100 500] =>[KNN 5 @vector $vec]사전 필터 - RAG 패턴 = chunk 분할 → 임베딩 → 색인 → 질의 임베딩 → KNN → LLM 프롬프트
- vs 전문 Vector DB — Pinecone(SaaS), Weaviate, pgvector, Milvus
- Redis Vector = 작은~중간 + μs 응답 + 이미 Redis 있음
- Pinecone = 수억 벡터 SaaS
- pgvector = PG 있음 + SQL 결합
- Milvus/Weaviate = 자체 호스팅 거대
- Spring AI =
VectorStore추상화 + Redis 통합 - 함정 — 메모리 비용 크다 (768차원 × float32 × 100만 = 3GB)
- 함정 — 임베딩 모델 비용
- 함정 — 색인과 쿼리 같은 모델 필수
- 함정 — HNSW recall 트레이드오프
- 함정 — 사전 필터 vs 사후 필터 선택
공식 문서: Redis Vector Search 에서 자세한 사양과 예제를 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 72편 — Redis 메모리 최적화 (Encoding · Hash 압축 · Fragmentation)
- 73편 — Spring Data Redis (RedisTemplate · @Cacheable)
- 74편 — RedisJSON (JSON.SET · JSONPath · PG JSONB 비교)
- 75편 — RediSearch (FT.CREATE · FT.SEARCH · FT.AGGREGATE)
- 76편 — Redis Time Series (TS.ADD · Labels · Compaction)
다음 글: