- Published on
Milvus 인덱스 빌드 느림·메모리 폭증 해결 7가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Milvus를 운영하다 보면 create_index가 끝나지 않거나(수십 분~수 시간), 빌드 중 메모리가 급격히 치솟아 OOM으로 죽는 문제가 반복됩니다. 이 현상은 단순히 “데이터가 커서”라기보다, 세그먼트 구조·인덱스 타입·빌드 병렬성·메모리 오버헤드·컴팩션 상태가 서로 맞물릴 때 발생합니다.
이 글은 원인을 빠르게 좁히는 관찰 포인트와 함께, 인덱스 빌드 느림·메모리 폭증을 줄이는 7가지 처방을 제공합니다. (특히 IVF 계열과 HNSW에서 체감이 큽니다.)
관련 튜닝은 아래 글도 함께 보면 연결이 좋습니다.
- Milvus IVF_FLAT·HNSW 튜닝으로 지연 50% 줄이기
- Pinecone·Milvus 하이브리드검색 튜닝 7가지
- 장애 시 컨테이너 관점 점검은 K8s CrashLoopBackOff 원인 10분 진단법도 유용합니다.
먼저: “느림”과 “메모리 폭증”의 대표 원인 지도
인덱스 빌드는 대략 다음 단계를 거치며, 각 단계에서 병목이 달라집니다.
- 세그먼트 로딩: 원본 벡터/스칼라 필드 데이터를 읽어 빌드 프로세스가 접근 가능한 상태로 만듭니다.
- 학습 또는 그래프 구성: IVF는
nlist중심으로 학습/클러스터링, HNSW는M,efConstruction에 따라 그래프를 구성합니다. - 인덱스 파일 생성 및 업로드: 로컬 디스크에 생성 후 오브젝트 스토리지로 업로드(또는 로컬/분산 스토리지 저장).
- 메타 업데이트 및 로드 전환: QueryNode가 새 인덱스를 로드하면서 검색 경로가 바뀝니다.
여기서 메모리 폭증은 보통 다음 패턴으로 발생합니다.
- 빌드 중 원본 데이터 + 작업 버퍼 + 인덱스 구조가 동시에 메모리에 존재
- 세그먼트가 너무 작거나 너무 많아 동시에 빌드되는 단위가 과도
- HNSW 파라미터가 과격해 그래프가 비대해짐
- IVF
nlist가 데이터 규모 대비 과도해 centroid/리스트 관리 비용 증가
이제 해결책을 “즉시 효과가 큰 것”부터 정리합니다.
1) 세그먼트 크기부터 정상화: 작은 세그먼트가 빌드를 망친다
Milvus는 내부적으로 데이터를 세그먼트 단위로 관리합니다. 세그먼트가 지나치게 작으면(예: 수만 벡터 단위로 쪼개짐) 인덱스 빌드가 다음 이유로 급격히 비효율적이 됩니다.
- 세그먼트마다 빌드 오버헤드가 반복(초기화/메타/IO)
- 동시에 빌드되는 세그먼트 수가 늘며 메모리 피크가 커짐
- 오브젝트 스토리지 업로드 파일이 잘게 쪼개져 IO 병목
체크 포인트
- 컬렉션에 세그먼트가 과도하게 많지 않은가
- flush 주기가 너무 짧지 않은가
- compaction이 밀려 “작은 세그먼트 더미”가 쌓이지 않았나
처방
- flush/segment 정책을 조정해 세그먼트가 너무 잘게 쪼개지지 않게 합니다.
- 이미 쪼개졌다면 compaction을 먼저 정상화한 뒤 인덱스를 빌드합니다.
운영에서는 “인덱스 빌드 전에 compaction부터”가 의외로 가장 큰 효과를 줍니다. 인덱스는 세그먼트별로 만들어지므로, compaction으로 세그먼트 수를 줄이면 빌드 횟수 자체가 줄어듭니다.
2) 빌드 동시성(병렬성)을 제한해 메모리 피크를 낮춘다
인덱스 빌드는 CPU도 쓰지만, **메모리 피크는 ‘동시에 몇 개를 빌드하느냐’**에 크게 좌우됩니다. 특히 Kubernetes에서 노드 메모리가 충분해도 Pod limit에 걸려 OOM이 나는 경우가 많습니다.
체크 포인트
- 인덱스 빌드 시점에 QueryNode/DataNode/IndexNode 중 누가 OOM 나는가
- Pod 메모리 limit이 빌드 피크를 감당하는가
- 동시에 여러 컬렉션/파티션에 인덱스를 걸고 있지 않은가
처방
- IndexNode의 빌드 동시성을 낮추고, 대신 빌드 시간을 받아들여 안정성을 확보합니다.
- 야간 배치로 “한 컬렉션씩” 인덱스 빌드하도록 오케스트레이션합니다.
실무 팁은 “빌드가 느려도 죽지 않게” 만드는 것입니다. 죽어서 재시도하면 총 소요시간은 오히려 늘고, 오브젝트 스토리지에 불완전 산출물이 쌓여 관리가 더 어려워집니다.
3) IVF 계열: nlist를 데이터 규모에 맞추지 않으면 학습·메모리가 터진다
IVF(IVF_FLAT, IVF_PQ, IVF_SQ8)에서 nlist는 클러스터 수입니다. nlist를 크게 잡으면 검색에서 nprobe 조절로 리콜/지연을 이쁘게 만들 수 있지만, 빌드 단계에서 비용이 급증합니다.
- 학습(centroid 추정) 비용 증가
- 리스트/메타 관리 비용 증가
- 세그먼트가 많을수록 “세그먼트별 IVF”의 총비용이 커짐
경험칙(출발점)
- 데이터가
N개일 때,nlist는 대략sqrt(N)근방에서 시작해 조정하는 경우가 많습니다. - 단, 차원
dim, 분포, 세그먼트 크기, 하드웨어에 따라 달라집니다.
코드 예시: IVF_FLAT 인덱스 생성
from pymilvus import connections, Collection
connections.connect(uri="http://localhost:19530")
col = Collection("my_vectors")
index_params = {
"metric_type": "COSINE",
"index_type": "IVF_FLAT",
"params": {"nlist": 2048}
}
col.create_index(field_name="embedding", index_params=index_params)
nlist를 무작정 올리기 전에, 먼저 보수적으로 시작해 빌드 안정성을 확보하고, 검색 튜닝은 nprobe로 풀어가는 접근이 안전합니다. 검색 튜닝의 큰 흐름은 Milvus IVF_FLAT·HNSW 튜닝으로 지연 50% 줄이기에 더 자세히 정리해두었습니다.
4) HNSW: M과 efConstruction이 메모리 폭증의 1순위
HNSW는 빌드 시 그래프를 구성합니다. 이때 M(노드당 링크 수)과 efConstruction(구성 품질/탐색 폭)이 커지면:
- 빌드 시간이 증가
- 그래프 엣지 수가 늘어 메모리 사용량이 크게 증가
- 특히 고차원·대규모 데이터에서 피크가 가파르게 상승
처방
- 초기에는
M과efConstruction을 보수적으로 설정해 빌드 안정성을 확보합니다. - 리콜이 부족하면 검색 시
ef를 올리는 방식으로 먼저 보완합니다.
코드 예시: HNSW 인덱스 생성
from pymilvus import Collection
col = Collection("my_vectors")
index_params = {
"metric_type": "IP",
"index_type": "HNSW",
"params": {
"M": 16,
"efConstruction": 128
}
}
col.create_index(field_name="embedding", index_params=index_params)
운영에서 자주 보는 실수는 “리콜이 안 나오니 efConstruction부터 크게 올리는 것”입니다. 그 결과 빌드가 느려지고 메모리가 터집니다. 리콜은 먼저 **검색 파라미터(ef)**로 조정하고, 인덱스 빌드 파라미터는 마지막에 만지는 편이 안전합니다.
5) 인덱스 타입을 ‘빌드 비용’ 관점에서 재선정한다
인덱스 선택은 검색 지연만이 아니라 빌드 시간·빌드 메모리·저장 공간의 트레이드오프입니다.
IVF_FLAT: 빌드는 상대적으로 단순하지만,nlist가 과하면 학습/메모리 부담 증가IVF_PQ/IVF_SQ8: 검색 메모리를 줄이지만, 빌드 과정이 더 무겁고 파라미터에 민감HNSW: 쿼리 성능이 좋지만, 빌드 메모리 피크가 커지기 쉬움
처방
- “빌드가 너무 무겁다”면, 일단
IVF_FLAT로 안정화한 뒤 점진적으로IVF_PQ또는 HNSW로 이동합니다. - 데이터가 계속 유입되는 환경이라면, 증분 빌드/주기적 재빌드 전략까지 포함해 선택해야 합니다.
하이브리드 검색(벡터+BM25/필터)까지 엮이면 인덱스 선택 기준이 더 복잡해지는데, 그 경우 Pinecone·Milvus 하이브리드검색 튜닝 7가지에서 “필터가 IVF/HNSW에 미치는 영향” 관점이 도움이 됩니다.
6) 메모리 ‘총량’이 아니라 ‘피크’를 줄여라: 빌드/로드 타이밍 분리
현장에서 자주 발생하는 케이스는 이겁니다.
- IndexNode가 인덱스를 빌드하며 메모리를 많이 씀
- 동시에 QueryNode가 세그먼트를 로드/리밸런싱하며 메모리를 또 씀
- 둘이 겹치는 순간 피크가 튀어 OOM
처방
- 인덱스 빌드 시간대에 로드/리밸런싱/대량 ingest를 피합니다.
- 가능하면 “빌드 전용 시간창”을 만들고, 트래픽이 낮을 때 수행합니다.
- 컬렉션을 여러 개 동시에 재인덱싱하지 말고 큐로 직렬화합니다.
Kubernetes라면 OOM 이후 CrashLoopBackOff로 이어지기 쉬우니, 증상 확인 및 빠른 원인 분리는 K8s CrashLoopBackOff 원인 10분 진단법 체크리스트가 그대로 적용됩니다.
7) “데이터 품질/형상”이 빌드를 느리게 한다: 차원·정규화·중복을 점검
인덱스 빌드는 데이터 분포의 영향을 받습니다. 특히 다음이 누적되면 빌드가 비정상적으로 느려지거나 메모리 사용이 증가할 수 있습니다.
- 차원(
dim)이 과도: 벡터 1개당 메모리/IO가 증가 - 정규화 불일치:
COSINE을 쓰면서 정규화를 안 했거나,IP/L2선택이 데이터에 맞지 않아 분포가 나빠짐 - 중복 벡터가 과다: 동일/유사 벡터가 대량이면 클러스터링/그래프 구성에서 비효율이 커질 수 있음
처방
- 임베딩 파이프라인에서 차원 축소(예: PCA) 또는 모델 교체를 검토합니다.
COSINE이면 보통 L2 정규화를 일관되게 적용합니다.- 중복이 의심되면 샘플링해서 near-duplicate 비율을 측정하고, 중복 제거 후 인덱스를 재빌드합니다.
아래는 COSINE을 위한 간단한 정규화 예시입니다.
import numpy as np
def l2_normalize(v: np.ndarray, eps: float = 1e-12) -> np.ndarray:
norm = np.linalg.norm(v)
return v / (norm + eps)
vec = np.array([0.1, 0.2, 0.3], dtype=np.float32)
vec = l2_normalize(vec)
데이터 중복/조인 실수로 레코드가 폭증하는 형태라면(예: upstream에서 데이터가 예상보다 수배로 늘어남) 원인 규명이 먼저입니다. 벡터 DB 이전 단계에서 행 폭증이 발생하는 케이스는 Pandas merge에서 행 폭증? 중복키 진단법과 같은 “중복키 진단” 접근이 그대로 통합니다.
운영에서 바로 쓰는 점검 순서(권장)
인덱스 빌드가 느리거나 메모리가 터질 때, 아래 순서로 보면 시행착오가 줄어듭니다.
- 세그먼트 수/크기 확인, compaction 적체 해소
- 동시에 빌드되는 작업 수 제한(직렬화)
- 인덱스 파라미터 보수화: IVF는
nlist부터, HNSW는M/efConstruction부터 - 빌드 시간대에 ingest/로드 이벤트 겹침 제거
- 데이터 차원/정규화/중복 점검
- 그래도 안 되면 인덱스 타입 재선정(빌드 비용 관점)
마무리: “검색 성능 튜닝”보다 먼저 “빌드 안정성”을 잡자
Milvus 인덱스 문제는 대부분 파라미터 하나로 해결되지 않습니다. **세그먼트 구조(컴팩션)와 빌드 동시성(피크 메모리)**을 먼저 안정화한 뒤, IVF/HNSW 파라미터를 조정하는 흐름이 가장 재현성이 좋습니다.
다음 단계로는 “빌드가 끝난 뒤 검색 지연/리콜을 어떻게 최적화할지”가 남습니다. 그 부분은 위에서 링크한 Milvus 튜닝 글을 기준으로, nprobe/ef를 중심으로 점진적으로 올리는 전략을 권합니다.