/AI & 자동화/Vector DB 마스터 클래스 2편: HNSW 인덱스와 하이브리드 검색 최적화
AI & Automation벡터DBHNSW

Vector DB 마스터 클래스 2편: HNSW 인덱스와 하이브리드 검색 최적화

This article is not yet available in English. Showing the original Korean version.

벡터 DB를 느리게 만드는 진짜 원인 1편에서 임베딩 생성과 기본 검색을 다뤘습니다. 실서비스 규모로 가면 정확도와 속도가 함께 무너지는 지점이 있습니다. 인덱스 전략과 하이브리드 검색이 그 해결책입니다. 인덱스 알고리즘 비교 벡터 검색의 성능은 인덱스 알고리즘 선택에서 90% 결정됩니다. 알고리즘 검색 속도 정확도 메모리 적합한 규모 ----…

Vector DB 마스터 클래스 2편: HNSW 인덱스와 하이브리드 검색 최적화

벡터 DB를 느리게 만드는 진짜 원인

1편에서 임베딩 생성과 기본 검색을 다뤘습니다. 실서비스 규모로 가면 정확도와 속도가 함께 무너지는 지점이 있습니다. 인덱스 전략과 하이브리드 검색이 그 해결책입니다.

인덱스 알고리즘 비교

벡터 검색의 성능은 인덱스 알고리즘 선택에서 90% 결정됩니다.

알고리즘검색 속도정확도메모리적합한 규모
Flat (브루트포스)느림100%낮음~10만 건
IVF빠름95~98%중간10만~1000만
HNSW매우 빠름97~99%높음모든 규모
ScaNN매우 빠름97~99%낮음1억 건 이상

HNSW 파라미터 튜닝

Python
import hnswlib
import numpy as np

def build_hnsw_index(vectors: np.ndarray, dim: int) -> hnswlib.Index:
    index = hnswlib.Index(space="cosine", dim=dim)

    index.init_index(
        max_elements=len(vectors),
        ef_construction=200,  # 높을수록 정확, 느린 인덱싱 (권장: 100~400)
        M=16,                 # 연결 수. 높을수록 정확, 많은 메모리 (권장: 8~48)
    )
    index.add_items(vectors)

    # 검색 시 ef: 높을수록 정확, 느린 쿼리 (권장: ef_construction 이상)
    index.set_ef(50)
    return index

# 정확도 vs 속도 트레이드오프 측정
def benchmark(index, queries, ground_truth, ef_values):
    for ef in ef_values:
        index.set_ef(ef)
        labels, _ = index.knn_query(queries, k=10)
        recall = compute_recall(labels, ground_truth)
        print(f"ef={ef:3d} → recall={recall:.3f}")

하이브리드 검색: 의미 + 키워드

순수 벡터 검색은 정확한 키워드·코드·고유명사에 취약합니다. BM25와 결합하면 상호보완됩니다.

Python
from rank_bm25 import BM25Okapi
import numpy as np

class HybridSearcher:
    def __init__(self, docs: list[str], embeddings: np.ndarray):
        tokenized = [d.split() for d in docs]
        self.bm25 = BM25Okapi(tokenized)
        self.embeddings = embeddings
        self.docs = docs

    def search(self, query: str, query_emb: np.ndarray,
               alpha: float = 0.5, top_k: int = 10) -> list:
        # BM25 점수 (키워드 매칭)
        bm25_scores = self.bm25.get_scores(query.split())
        bm25_norm = bm25_scores / (bm25_scores.max() + 1e-9)

        # 코사인 유사도 (의미 매칭)
        cosine_scores = self.embeddings @ query_emb
        cosine_norm = (cosine_scores + 1) / 2  # [-1,1] → [0,1]

        # 가중 합산 (RRF 방식도 가능)
        final = alpha * cosine_norm + (1 - alpha) * bm25_norm
        top_idx = np.argsort(final)[::-1][:top_k]
        return [(self.docs[i], float(final[i])) for i in top_idx]

alpha 값 선택 기준

  • alpha 0.7~0.9: 의미 중심 (일반 Q&A, 개념 검색)
  • alpha 0.3~0.5: 키워드 중심 (코드 검색, 제품명, 날짜)
  • alpha 0.5: 균형 (대부분의 RAG 유스케이스)

메타데이터 필터링으로 검색 범위 축소

Python
# pgvector 예시 — 필터 후 벡터 검색
async def filtered_search(
    query_emb: list[float],
    department: str,
    date_from: str,
    limit: int = 10
) -> list:
    return await db.fetch("""
        SELECT id, title, content,
               1 - (embedding <=> $1::vector) AS similarity
        FROM documents
        WHERE department = $2
          AND created_at >= $3
          AND status = 'published'
        ORDER BY embedding <=> $1::vector
        LIMIT $4
    """, query_emb, department, date_from, limit)

필터를 먼저 적용하면 검색 대상을 줄여 2~5배 속도 향상 효과가 있습니다.

임베딩 캐싱

동일 쿼리를 반복 임베딩하는 비용을 제거합니다.

Python
import hashlib
import redis

class EmbeddingCache:
    def __init__(self, ttl: int = 3600):
        self.redis = redis.Redis()
        self.ttl = ttl

    def _key(self, text: str, model: str) -> str:
        h = hashlib.sha256(f"{model}:{text}".encode()).hexdigest()[:16]
        return f"emb:{h}"

    async def get_or_embed(self, text: str, model: str, embed_fn) -> list[float]:
        key = self._key(text, model)
        cached = self.redis.get(key)
        if cached:
            return json.loads(cached)
        emb = await embed_fn(text)
        self.redis.setex(key, self.ttl, json.dumps(emb))
        return emb

3편에서는 멀티테넌트 격리, 증분 인덱스 갱신, 프로덕션 모니터링으로 Vector DB 운영을 완성합니다.

증상별 튜닝 의사결정표

운영 중 성능 문제가 생기면 아래 표에서 증상으로 시작하세요.

증상유력 원인우선 조치
정확도(recall)가 낮다ef_search가 낮음 / 하이브리드 미적용ef_search를 단계적으로 2배씩 올리며 recall-지연 곡선 측정 → 한계면 alpha 조정
p99 지연이 튄다ef_search 과다 / 필터가 인덱스 뒤에 적용메타데이터 사전 필터(pre-filter) 지원 여부 확인, ef_search 상한 재설정
메모리가 계속 증가HNSW 그래프(M 값) 과다 / 삭제 벡터 미정리M을 낮춘 재색인 검토, 주기적 compaction/재빌드
색인 시간이 너무 길다ef_construction 과다정확도 요구와 함께 낮추기 — 색인 후 ef_search로 보완 가능
키워드 정확 일치가 안 잡힌다순수 벡터 검색만 사용하이브리드(BM25+벡터) 도입, alpha를 검색 로그로 튜닝

측정 없이 튜닝하지 마세요. 파라미터 변경 전후로 동일한 golden query 세트(대표 질의 50~100개)의 recall@k와 p95 지연을 기록하는 것이 유일하게 신뢰할 수 있는 방법입니다.

✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 ·
관련 공식 문서pgvector 공식 저장소

Comments

Be the first to comment.