- Published on
Pinecone·Milvus 인덱싱 느림? HNSW 튜닝
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서빙은 빠른데 인덱싱(업서트/빌드)이 유독 느리면, 대부분은 “벡터 DB가 느리다”가 아니라 HNSW 파라미터와 데이터 적재 방식이 현재 워크로드에 맞지 않아서입니다. Pinecone와 Milvus는 내부 구현과 운영 방식이 다르지만, HNSW의 병목 지점은 놀랄 만큼 비슷합니다.
이 글에서는 다음을 목표로 합니다.
- 인덱싱이 느려지는 대표 원인을 HNSW 메커니즘 관점에서 분해
M,efConstruction이 인덱싱 속도/메모리/리콜에 미치는 영향 정리- Pinecone·Milvus에서 실무적으로 적용 가능한 튜닝 순서 제시
- “빨라지긴 했는데 검색 품질이 망가짐”을 막는 검증 루틴 제공
RAG 파이프라인에서 검색 품질 검증까지 같이 묶어 운영한다면, 출력 형식 강제와 품질 관리 관점은 이 글도 함께 참고할 만합니다: RAG 환각을 줄이는 JSON Schema 강제 출력법
1) HNSW 인덱싱이 느려지는 진짜 이유
HNSW(Hierarchical Navigable Small World)는 삽입 시 매번 “그래프에 노드를 끼워 넣고, 근접 이웃과 연결”을 수행합니다. 이 과정이 느려지는 이유는 크게 4가지입니다.
1-1. efConstruction이 과도하게 큼
삽입 시 후보 이웃을 넓게 탐색할수록(= efConstruction 증가) 연결 품질이 좋아져 리콜이 오르지만, 삽입 비용이 거의 선형에 가깝게 증가합니다.
- 체감 증상: 업서트 QPS가 점점 떨어지고, CPU가 꽉 차며, tail latency가 튐
- 흔한 실수: 검색 리콜을 올리려고
ef(검색) 대신efConstruction을 무작정 올림
1-2. M이 과도하게 큼
M은 노드당 최대 연결 수(대략적인 degree)입니다.
M증가 효과- 장점: 그래프가 촘촘해져 리콜 상승, 특히 고차원에서 안정적
- 단점: 삽입 시 연결 후보 평가량 증가, 메모리 사용량 증가, 빌드 시간 증가
1-3. 배치 업서트가 잘못됨(너무 작거나, 너무 큼)
벡터 DB는 보통 네트워크/직렬화/쓰기 경로가 병목이 되기도 합니다.
- 너무 작은 배치: 요청 오버헤드가 지배 → QPS 하락
- 너무 큰 배치: 한 번에 처리하는 동안 큐가 막히고, 메모리/GC/머지 작업이 폭증 → tail latency 증가
1-4. 세그먼트/컴팩션(머지) 비용이 뒤에서 터짐
특히 Milvus 계열에서 흔한데, 지속적으로 삽입하면 세그먼트가 늘고, 백그라운드 컴팩션이 시작되면서 인덱싱과 컴팩션이 자원을 경쟁합니다.
- 체감 증상: 초반엔 빠르다가 어느 시점부터 갑자기 느려짐
- 해결 방향: 인덱스 빌드 타이밍/세그먼트 정책/리소스 분리
2) HNSW 핵심 파라미터: M과 efConstruction을 어떻게 잡나
HNSW 튜닝은 결국 3가지 균형입니다.
- 인덱싱 속도(삽입 처리량)
- 메모리(그래프 엣지 저장 비용)
- 검색 품질(리콜/정확도)
2-1. M 가이드
일반적인 출발점은 다음과 같습니다.
- 384~768 차원 임베딩(예: sentence-transformers, OpenAI 계열):
M=16또는M=24 - 1024~1536 차원:
M=24또는M=32
인덱싱이 느리다면 먼저 M을 줄이는 게 즉효가 큰 편입니다. 다만 M을 너무 낮추면 리콜이 급락하거나 검색 결과가 불안정해질 수 있습니다.
2-2. efConstruction 가이드
efConstruction은 삽입 시 탐색 폭입니다. 보통 다음처럼 시작합니다.
- 빠른 인덱싱 우선:
efConstruction=64또는100 - 품질 우선(오프라인 빌드):
efConstruction=200~400
실무적으로는 efConstruction을 먼저 낮춰서 인덱싱 병목을 풀고, 검색 시 ef(검색 파라미터)를 올려 리콜을 보정하는 접근이 안전합니다.
2-3. 검색 파라미터 ef(또는 유사 파라미터)와의 관계
많은 시스템에서 검색 시 ef를 올리면 리콜이 오르지만 지연이 늘어납니다.
- 인덱싱이 느리다:
efConstruction을 낮추는 게 정답일 가능성이 큼 - 검색이 부정확하다: 먼저
ef를 올려보고, 그래도 부족하면 그때M/efConstruction을 재검토
3) Pinecone에서 인덱싱이 느릴 때 체크리스트
Pinecone은 관리형이라 내부 세그먼트/컴팩션을 직접 만지기 어렵지만, 대신 “인덱스 타입/파라미터/적재 패턴”이 핵심입니다.
3-1. 업서트 배치 크기와 동시성부터 조정
가장 먼저 할 일은 배치 크기와 병렬 업서트를 튜닝하는 것입니다.
- 배치 크기: 100~500부터 시작해 점진적으로 증가
- 동시성: 4~32 사이에서 지표를 보며 조정
아래는 Python에서 스레드 풀로 업서트를 병렬화하는 예시입니다.
from concurrent.futures import ThreadPoolExecutor, as_completed
BATCH = 200
WORKERS = 16
# vectors: list[tuple[str, list[float], dict]] # (id, vector, metadata)
def chunks(xs, n):
for i in range(0, len(xs), n):
yield xs[i:i+n]
def upsert_batch(index, batch):
# Pinecone client 형태는 버전에 따라 다를 수 있음
return index.upsert(vectors=batch)
futures = []
with ThreadPoolExecutor(max_workers=WORKERS) as ex:
for batch in chunks(vectors, BATCH):
futures.append(ex.submit(upsert_batch, index, batch))
for f in as_completed(futures):
_ = f.result()
튜닝 요령은 간단합니다.
- 처리량이 낮다: 배치 또는 동시성 증가
- tail latency가 튄다/에러가 늘어난다: 배치 또는 동시성 감소
3-2. 메타데이터가 과도하게 크지 않은지 확인
Pinecone에서 메타데이터는 필터링에 유용하지만, 적재 비용과 저장 비용에 영향을 줍니다.
- 큰 원문 텍스트를 메타데이터에 그대로 넣는 패턴은 피하기
- 원문은 오브젝트 스토리지나 DB에 두고, 메타데이터에는 키만 저장
3-3. 인덱스 스펙과 리소스(파드/노드 규모)를 의심
관리형에서도 결국 리소스가 부족하면 인덱싱이 밀립니다.
- 증상: CPU/메모리 제한에 가까운 패턴, 지속적인 backpressure
- 해결: 스펙 상향, 샤딩/파티션 전략 재검토
4) Milvus에서 인덱싱이 느릴 때 체크리스트
Milvus는 구성 요소가 많아(Proxy, QueryNode, DataNode, IndexNode 등) 병목이 더 다양합니다. 대신 조절 가능한 레버도 많습니다.
4-1. HNSW 인덱스 파라미터 예시
Milvus에서 HNSW를 만들 때는 보통 아래처럼 파라미터를 지정합니다(버전/SDK에 따라 API는 다를 수 있습니다).
index_params = {
"index_type": "HNSW",
"metric_type": "COSINE",
"params": {
"M": 16,
"efConstruction": 100
}
}
collection.create_index(
field_name="embedding",
index_params=index_params
)
인덱싱이 느리다면 다음 순서로 낮춰보는 것을 권장합니다.
efConstruction을 200->100->64로 단계적으로 감소- 그래도 느리면
M을 32->24->16으로 감소
위 화살표 표기는 반드시 인라인 코드로 감쌌습니다.
4-2. “실시간 삽입 + 즉시 인덱싱”을 강요하지 않기
Milvus에서 가장 흔한 운영 실수는 다음입니다.
- 계속 insert
- 동시에 인덱스 빌드/머지
- 동시에 서빙 쿼리
이 3개를 같은 리소스 풀에서 돌리면, 인덱싱이 느려지거나 쿼리 지연이 튀기 쉽습니다.
권장 패턴은 2가지입니다.
- 오프라인 빌드형: 대량 적재 후 인덱스 생성/빌드, 그 다음 서빙
- 하이브리드형: 최신 데이터는 brute force(또는 작은 인덱스)로 보조 검색, 일정 주기로 메인 인덱스에 병합
4-3. 컴팩션/세그먼트 정책으로 뒤통수 맞는 경우
인덱싱이 “처음엔 빠르다가 점점 느려지는” 패턴이면 컴팩션을 의심해야 합니다.
- 세그먼트가 너무 잘게 쪼개져 있으면, 인덱스 빌드 단위가 비효율적
- 컴팩션이 인덱스 노드/데이터 노드 자원을 잡아먹으면 업서트가 밀림
해결은 운영 환경에 따라 다르지만, 핵심은 다음입니다.
- 인덱스 빌드와 대량 적재를 같은 시간대에 겹치지 않게 스케줄링
- 인덱스 노드 리소스를 분리하거나 확장
- 모니터링으로 “insert rate, flush, compaction, index build queue”를 함께 보기
5) 튜닝 절차: 빠르게 원인 좁히는 6단계
아래 순서로 하면 “파라미터를 이것저것 바꿔봤는데 왜 좋아졌는지 모르는 상태”를 피할 수 있습니다.
5-1. 목표를 수치로 고정
- 인덱싱 처리량: 초당 업서트 벡터 수
- 검색 품질: top-k 리콜(가능하면 정답셋 기반)
- 검색 지연: p50/p95
5-2. 적재 방식부터 최적화
- 배치 크기, 동시성, 네트워크 오버헤드 제거
- 메타데이터 최소화
5-3. efConstruction을 먼저 낮추기
인덱싱 느림 이슈의 체감 효과가 가장 큰 레버입니다.
5-4. M을 조정
리콜이 유지되는 최소 M을 찾는 방향이 좋습니다.
5-5. 검색 시 ef로 리콜 보정
인덱싱을 위해 efConstruction을 낮췄다면, 검색 시 ef를 올려 리콜을 맞춥니다.
5-6. 회귀 테스트(품질) 자동화
튜닝은 성능만 올리면 실패합니다. 검색 품질이 떨어지면 RAG 답변 품질이 바로 흔들립니다.
간단한 리콜 측정 예시는 아래처럼 만들 수 있습니다.
def recall_at_k(retrieved_ids, relevant_ids):
# retrieved_ids: list[str]
# relevant_ids: set[str]
hit = 0
for rid in retrieved_ids:
if rid in relevant_ids:
hit += 1
return hit / max(1, len(relevant_ids))
# 예: 쿼리별 relevant set을 준비해두고, 파라미터 변경 전후를 비교
6) 흔한 오해 5가지(실무에서 자주 터짐)
6-1. “리콜이 낮으니 efConstruction부터 올리자”
대량 삽입 환경에서는 인덱싱 비용이 폭증해 전체 파이프라인이 무너질 수 있습니다. 먼저 검색 ef로 보정 가능한지 확인하세요.
6-2. “M을 올리면 무조건 품질이 좋아진다”
품질은 좋아질 수 있지만, 메모리/인덱싱 시간이 같이 증가합니다. 특히 고차원에서 M=48 같은 값은 비용이 급격히 커질 수 있습니다.
6-3. “인덱싱이 느리면 DB가 문제다”
실제로는 업서트 배치가 너무 작거나, 메타데이터가 과도하거나, 네트워크 병목인 경우가 많습니다.
6-4. “실시간으로 다 인덱싱돼야 한다”
실시간성이 정말 필요한 범위(예: 최근 1시간 데이터)만 별도로 다루고, 나머지는 배치/주기적 병합으로 설계하면 비용이 크게 줄어듭니다.
6-5. “품질 검증은 나중에 하자”
HNSW 튜닝은 품질과 직결됩니다. 최소한의 회귀 테스트(리콜/정답률)를 파라미터 변경과 함께 묶어야 합니다.
7) 운영 팁: 컨테이너/노드 환경에서 성능이 흔들릴 때
Milvus를 Kubernetes나 Docker에서 돌릴 때, CPU 제한/NUMA/스토리지/네트워크가 인덱싱 속도에 큰 영향을 줍니다. 특히 GPU를 쓰는 구성(또는 GPU가 필요하다고 믿는 구성)에서는 “호스트는 되는데 컨테이너에서 장치를 못 잡는” 류의 문제도 성능/안정성을 악화시킵니다.
GPU/드라이버/컨테이너 런타임 이슈를 겪고 있다면 이 글이 진단에 도움이 됩니다: Docker에서 NVIDIA GPU가 안 잡힐 때 nvidia-smi는 되는데 컨테이너에서 CUDA not found 뜨는 7가지 원인과 해결
8) 결론: 가장 안전한 HNSW 튜닝 전략
- 인덱싱이 느리면, 먼저 업서트 배치/동시성/메타데이터를 점검
- 그 다음
efConstruction을 낮춰 삽입 비용을 줄이기 - 필요하면
M을 낮추되, 리콜 회귀 테스트로 품질 하락을 감시 - 검색 품질은 가능하면 검색
ef로 보정하고, 오프라인 빌드/병합 전략으로 운영 복잡도를 낮추기
이 과정을 지표 기반으로 반복하면 Pinecone든 Milvus든 “인덱싱이 병목이라 전체 RAG 파이프라인이 밀리는 상태”에서 벗어나, 비용과 성능을 예측 가능한 범위로 가져올 수 있습니다.