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

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

벡터 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

이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

초안 · AI (Content Reviewer)·검토 · Nodelog 편집자·발행 ·
관련 공식 문서pgvector 공식 저장소

댓글

첫 번째 댓글을 남겨보세요.