사용자가 질문을 던졌을 때 LLM이 답변을 생성하기까지 5초가 걸린다면 해당 서비스는 사용자 이탈을 피할 수 없습니다. 이 지연 시간의 핵심 주범은 수백만 개의 벡터 데이터 사이에서 길을 잃은 시맨틱 검색(Semantic Search) 과정입니다.
대규모 데이터셋에서 단순한 전수 조사(Flat Search)는 불가능에 가깝습니다. 인덱싱 알고리즘의 파라미터를 단 몇 줄 고치는 것만으로도 정확도 손실 없이 검색 속도를 밀리초(ms) 단위로 줄이는 결과물을 얻습니다.
TL;DR — HNSW 인덱스의 M(연결 수)과 ef_construction(탐색 범위) 파라미터를 데이터 특성에 맞게 최적화하여 메모리 효율과 검색 속도를 동시에 잡습니다.
1. HNSW 인덱싱이란 — 그래프 기반 고속 검색
💡 비유로 이해하기: 전국 고속도로망과 아파트 단지 앞 골목길을 떠올려 보십시오. 서울에서 부산의 특정 번지수를 찾을 때, 처음부터 골목길을 다 뒤지지 않습니다. 경부고속도로(상위 레이어)를 타고 빠르게 부산 인근까지 이동한 뒤, 톨게이트를 빠져나와 국도와 골목길(하위 레이어)로 좁혀 들어가는 과정이 HNSW의 작동 원리입니다.
HNSW(Hierarchical Navigable Small World)는 벡터 데이터를 그래프 구조로 연결하여 근사 최근접 이웃(ANN)을 찾는 알고리즘입니다. 최신 안정 버전인 Milvus 2.4.x나 Pinecone Serverless 환경에서 가장 선호되는 인덱싱 방식입니다. 데이터를 여러 계층으로 나누어 상위 계층에서는 듬성듬성하게 이동하고, 하위 계층으로 내려갈수록 정밀하게 검색합니다.
기존의 brute-force 방식은 데이터가 늘어날수록 검색 시간이 선형적으로 증가하지만, HNSW는 로그 복잡도를 가집니다. 이는 1,000만 건의 데이터에서도 10ms 내외의 응답 속도를 보장하는 핵심 기술입니다. 하지만 기본 설정값만으로는 각 도메인 특화 데이터(금융, 의료 등)의 복잡성을 충분히 담아내지 못하는 한계가 있습니다.
2. 실무 튜닝이 필요한 시나리오 — 지연 시간의 임계점
고객 상담 챗봇이 과거 2년 치의 로그 100만 건을 참조할 때 튜닝은 필수입니다. 초기 단계에서는 기본값으로도 충분한 속도가 나오지만, 데이터가 누적되면서 인덱스 빌드 속도가 급격히 떨어지거나 검색 재현율(Recall)이 80% 미만으로 떨어지는 경우 파라미터 조정을 검토해야 합니다.
실시간 추천 시스템에서 50ms 이내의 응답 속도를 유지해야 할 때 튜닝을 시작합니다. 특히 벡터의 차원(Dimension)이 1536(OpenAI text-embedding-3-small 등) 이상인 고차원 데이터를 다룰 때, 파라미터 설정 오류는 메모리 사용량을 2배 이상 폭증시키는 원인이 됩니다.
3. 단계별 구현 — 최적의 성능 수치 찾기
HNSW 성능의 80%는 M과 ef_construction, 두 가지 변수에서 결정됩니다.
Step 1. M (Maximum Degree) 설정
각 노드가 가질 수 있는 최대 연결 수입니다. 값이 클수록 정확도는 올라가지만 인덱스 파일 크기가 커지고 메모리 점유율이 높아집니다.
// Milvus 또는 관련 Vector DB 인덱스 설정 예시
const indexParams = {
index_type: "HNSW",
metric_type: "L2",
params: {
M: 16, // 일반적인 텍스트 데이터의 권장 시작점
efConstruction: 200
}
};
Step 2. ef_construction (Build-time Search Range)
인덱스를 생성할 때 얼마나 많은 이웃을 탐색할지 결정합니다. 이 값이 크면 인덱싱 시간은 오래 걸리지만, 훨씬 견고한 그래프가 만들어져 검색 품질이 향상됩니다.
# Python SDK를 활용한 인덱스 빌드 예시
index_config = {
"M": 32, # 고차원 데이터의 경우 32~64 권장
"ef_construction": 128 # 정확도가 중요하다면 200 이상 설정
}
vector_db.create_index(field_name="vector", index_params=index_config)
Step 3. ef_search (Runtime Search Range) 동적 조정
가장 중요한 동적 파라미터입니다. 인덱스 생성 시점이 아니라 검색 요청 시점에 설정합니다. 속도가 중요하면 낮게, 정확도가 중요하면 높게 설정하여 유연하게 대응합니다.
# 쿼리 수행 시 ef 설정 (검색 대상 K보다 항상 커야 함)
search_params = {"metric_type": "L2", "params": {"ef": 64}}
results = collection.search(query_vectors, "vector", search_params, limit=10)
4. HNSW vs IVF 인덱스 비교 — 데이터 규모별 선택
모든 상황에서 HNSW가 정답은 아닙니다. 데이터 규모와 가용 메모리에 따라 선택이 달라집니다.
| 기준 | HNSW | IVF-PQ |
|---|---|---|
| 검색 속도 | 매우 빠름 | 보통 (클러스터 수에 의존) |
| 메모리 사용량 | 매우 높음 (Raw 데이터 + 그래프) | 낮음 (압축 가능) |
| 인덱싱 시간 | 상대적으로 오래 걸림 | 빠름 |
| 적합 규모 | 1,000만 건 이하 중소규모 | 1억 건 이상의 대규모 |
메모리 예산이 충분하고 검색 응답 속도가 최우선이라면 HNSW를, 수억 건의 데이터를 저비용으로 운영해야 한다면 IVF-PQ를 선택하는 것이 합리적입니다.
5. 주의사항 — 메모리 부족(OOM)의 덫
⚠️ 가장 자주 하는 실수: M 값을 무조건 크게 설정하면 인덱스 크기가 RAM 용량을 초과하여 시스템이 다운됩니다.
HNSW는 모든 인덱스 구조를 메모리에 상주시켜야 제 속도가 나옵니다. M이 1 증가할 때마다 벡터당 추가로 소모되는 바이트 수를 계산하지 않으면 운영 환경에서 반드시 OOM 에러를 마주하게 됩니다.
Troubleshooting by Error
Error: Memory quota exceeded (OOM).
원인: M=64, ef_construction=512로 과도하게 설정된 인덱스가 가용 RAM 점유.
해결법: M을 16으로 낮추고, 부족한 정확도는 쿼리 시점에 ef_search를 높여 보완할 것.
6. 실전 팁 — 성능 극대화를 위한 가이드
Recall(재현율) 95%를 목표로 잡는다면 M=16, ef_construction=128에서 시작하여 점진적으로 수치를 올리는 것이 효율적입니다. 무조건 높은 수치를 쓴다고 해서 비례해서 성능이 좋아지지 않는 '수익 체감의 법칙'이 적용되는 영역입니다.
실제 벤치마크 결과에 따르면, M 값을 16에서 32로 두 배 늘려도 검색 정확도는 약 2~3% 향상에 그치지만 메모리 사용량은 40% 이상 증가합니다. 하드웨어 자원이 한정적이라면 알고리즘 튜닝보다 벡터의 차원을 축소하는 PCA 기법을 병행하는 것이 낫습니다.
📌 핵심 요약
- HNSW는 그래프 계층 구조를 이용해 로그 시간 복잡도로 벡터를 검색합니다.
- M은 그래프의 밀도를, ef_construction은 빌드 시 정밀도를 결정합니다.
- 성능 병목 시 M을 줄이고 쿼리 타임 파라미터인 ef_search를 조정하여 균형을 맞춥니다.
Frequently Asked Questions
Q. HNSW 인덱싱 후 데이터를 추가할 수 있나요?
A. 네, 실시간 노드 추가를 지원하여 증분 업데이트가 가능합니다.
Q. M 값은 보통 얼마가 적당한가요?
A. 일반적인 RAG 용도로는 16에서 32 사이가 가장 효율적입니다.
Q. ef_search는 k(검색 결과 수)보다 커야 하나요?
A. 반드시 k와 같거나 커야 하며 보통 2~3배 수준으로 설정합니다.
Post a Comment