LLM WikiAccess-protected knowledge portal

WIKI

Milvus 심화: 분산 아키텍처, 인덱스 전략, K8s 배포

Milvus를 선택해야 하는 맥락 Milvus는 수억~수십억 벡터 규모의 프로덕션 검색 시스템을 위해 설계된 목적 특화 벡터 데이터베이스다. pgvector나 Qdrant와 달리 스토리지·컴퓨팅 분리 Storage Compute Separation 아키텍처를 채택해, 수평 확장과 롤링 업데이트를 제어된 방식으로 수행할 수 있다. 대신 운영 복잡도가 높다. etcd, 메시지 큐 Pulsar 또는 Kafka , 오브젝트 스토리지

경로human/study/content/vector-databases/04-milvus-distributed-architecture-index-k8s.md
카테고리Study
태그#architecture #distributed #k8s #kubernetes #milvus #mysql #study

Milvus를 선택해야 하는 맥락

Milvus는 수억~수십억 벡터 규모의 프로덕션 검색 시스템을 위해 설계된 목적 특화 벡터 데이터베이스다. pgvector나 Qdrant와 달리 스토리지·컴퓨팅 분리(Storage-Compute Separation) 아키텍처를 채택해, 수평 확장과 롤링 업데이트를 제어된 방식으로 수행할 수 있다.

대신 운영 복잡도가 높다. etcd, 메시지 큐(Pulsar 또는 Kafka), 오브젝트 스토리지(MinIO 또는 S3)라는 세 가지 외부 의존성이 필요하고, 컴포넌트가 여러 개로 분리된다. "수억 벡터 + 복잡한 메타데이터 필터링 + 초저지연"을 동시에 요구하는 상황, 또는 팀이 이미 K8s 기반 인프라를 운영하고 있는 상황에 가장 잘 맞는다.


4계층 아키텍처

Milvus 2.x는 역할별로 4개 계층으로 구성된다.

Milvus 2.x 분산 아키텍처 ① 접근 계층 (Access Layer) Proxy 로드밸런서·라우팅·인증 Proxy 수평 확장 가능 ← 여러 인스턴스 운영 ② 코디네이터 계층 (Coordinator Layer) RootCoord DDL / 스키마 관리 타임스탬프 오라클 DataCoord 세그먼트 할당 관리 flush 트리거 QueryCoord 세그먼트 로드 조율 쿼리 클러스터 균형 IndexCoord 인덱스 작업 스케줄 IndexNode에 작업 배분 ③ 워커 계층 (Worker Layer) DataNode WAL 구독·세그먼트 플러시 오브젝트 스토리지에 기록 QueryNode sealed/growing 세그먼트 로드 검색·집계 실행 (Segcore) IndexNode sealed 세그먼트 인덱스 빌드 CPU 집약 → 별도 스케일 * Milvus 2.6: StreamingNode DataNode + WAL 통합 ④ 스토리지 계층 (Storage Layer) etcd 메타데이터 / 서비스 디스커버리 Pulsar / Kafka WAL / 메시지 버스 (삽입·삭제 스트림) MinIO / S3 sealed 세그먼트 · 인덱스 파일 저장 코디네이터는 etcd를 통해 메타데이터를 공유하며, 워커는 Pulsar/Kafka를 구독해 증분 데이터를 수신하고 MinIO/S3에서 sealed 데이터를 로드한다.
Milvus 분산 아키텍처: 4계층 구조

쓰기 경로: 삽입 데이터의 흐름

클라이언트 → Proxy → 메시지 큐(Pulsar/Kafka) → DataNode
                            ↓
                  [Growing Segment, 메모리]
                            ↓  (flush 조건 충족)
                  [Sealed Segment, S3/MinIO]
                            ↓  (IndexCoord 스케줄)
                  IndexNode → 인덱스 파일 → S3
                            ↓
                  QueryCoord → QueryNode에 로드 지시

Growing Segment: 삽입이 진행 중인 세그먼트. 메모리에 유지되며 brute-force 탐색을 사용한다.

Sealed Segment: flush된 후 오브젝트 스토리지에 영속화된 세그먼트. IndexNode가 인덱스를 빌드하면 QueryNode가 로드해 ANN 검색에 사용한다.

flush 조건은 세그먼트 크기(dataCoord.segment.maxSize, 기본 512 MB) 도달 또는 일정 시간(dataCoord.segment.sealProportion) 초과 시 자동 발생하고, 클라이언트가 명시적으로 flush()를 호출할 수도 있다.


읽기 경로: 검색 요청 처리

클라이언트 → Proxy
  → 샤드별 QueryNode에 분산 (QueryCoord 라우팅)
      ├── Growing Segment: 메모리 내 brute-force
      └── Sealed Segment: HNSW/IVF/DiskANN ANN 검색
  → 결과 병합 (Proxy에서 Top-K 재순위)
  → 클라이언트 반환

Time Travel: Milvus는 타임스탬프 기반으로 특정 시점의 데이터 스냅샷을 조회할 수 있다. travel_timestamp 파라미터로 과거 상태를 재현한다.


인덱스 전략

Milvus는 용도와 규모에 따라 다양한 인덱스 타입을 제공한다.

인덱스 타입 비교

FLAT 완전 브루트포스 Recall: 100% 속도: 느림 메모리: 높음 → 소규모 / 정확도 필수
IVF_FLAT 클러스터링 + 역 인덱스 파라미터: nlist, nprobe Recall: 높음 (nprobe↑) 메모리: 중간 → 중~대규모 균형점
IVF_SQ8 / IVF_PQ 양자화 압축 포함 SQ8: 4× 절감 PQ: 8×~64× 절감 Recall: 중간 (PQ 손실 큼) → 메모리 제약 대규모
HNSW 그래프 기반 ANN 파라미터: M, ef_construction, ef Recall: 매우 높음 메모리: 높음 → 저지연 프로덕션 기본값
DISKANN 디스크 기반 그래프 NVMe SSD 필요 Recall: 높음 RAM: HNSW의 약 10% → 10억+ 벡터, RAM 한정
선택 기준: 벡터 수 < 100만 → FLAT 또는 HNSW. 100만~수억 → HNSW (메모리 여유) 또는 IVF_SQ8 (메모리 제약). 수억~수십억 + 메모리 한정 → DiskANN + NVMe SSD. GPU 클러스터 보유 → GPU_CAGRA (최고 처리량).
Milvus 인덱스 타입 선택 가이드

HNSW 파라미터

index_params = MilvusClient.prepare_index_params()
index_params.add_index(
    field_name="embedding",
    index_type="HNSW",
    metric_type="COSINE",
    params={
        "M": 16,           # 노드당 최대 연결 수. 높을수록 recall↑, 메모리↑
        "efConstruction": 200,  # 빌드 시 후보 크기. 높을수록 품질↑, 빌드 시간↑
    }
)

검색 시 ef(search)를 조정한다.

results = client.search(
    collection_name="my_collection",
    data=[query_vector],
    limit=10,
    search_params={"ef": 128}   # 클수록 recall↑, 지연↑
)

DiskANN 파라미터

index_params.add_index(
    field_name="embedding",
    index_type="DISKANN",
    metric_type="L2",
    params={
        "search_list": 100   # 빌드 시 후보 크기 (IVF의 nlist에 해당)
    }
)
# 검색 시: search_list (쿼리 시 탐색 크기)
search_params = {"search_list": 200}

DiskANN은 로컬 NVMe SSD가 없으면 성능이 크게 저하된다. EBS gp3 같은 네트워크 블록 스토리지는 레이턴시 특성이 다르므로 실측 필수.


K8s 배포: Helm vs Milvus Operator

Standalone vs Cluster 모드

모드적합한 경우외부 의존성
Standalone개발·테스트, 소규모 (<1억 벡터)etcd, MinIO 내장 가능
Cluster프로덕션, 수평 확장 필요etcd, Pulsar/Kafka, MinIO/S3 필수

Helm 클러스터 설치

helm repo add milvus https://zilliz.github.io/milvus-helm
helm repo update

helm install milvus milvus/milvus \
  --namespace milvus --create-namespace \
  --set cluster.enabled=true \
  --set etcd.replicaCount=3 \
  --set minio.mode=distributed \
  --set pulsar.enabled=true

Helm은 빠르게 시작하기에 적합하지만, 컴포넌트별 세밀한 리소스 설정이나 롤링 업그레이드 자동화에는 Milvus Operator가 유리하다.

Milvus Operator (프로덕션 권장)

Milvus Operator는 CRD(Custom Resource Definition)로 Milvus 클러스터를 선언적으로 관리한다. 버전 업그레이드, 컴포넌트 복구, 스케일링을 K8s 네이티브 방식으로 자동화한다.

# Operator 설치
helm install milvus-operator \
  https://github.com/zilliztech/milvus-operator/releases/download/v1.2.0/milvus-operator-1.2.0.tgz \
  --namespace milvus-operator --create-namespace

# MilvusCluster CR 적용
kubectl apply -f milvus-cluster.yaml
# milvus-cluster.yaml (핵심 부분)
apiVersion: milvus.io/v1beta1
kind: MilvusCluster
metadata:
  name: prod-milvus
  namespace: milvus
spec:
  components:
    proxy:
      replicas: 2
      resources:
        requests: {cpu: "2", memory: "4Gi"}
        limits:   {cpu: "4", memory: "8Gi"}
    queryNode:
      replicas: 4
      resources:
        requests: {cpu: "8", memory: "64Gi"}
        limits:   {cpu: "16", memory: "128Gi"}
    dataNode:
      replicas: 2
      resources:
        requests: {cpu: "2", memory: "8Gi"}
    indexNode:
      replicas: 2
      resources:
        requests: {cpu: "16", memory: "32Gi"}  # CPU 집약 작업
  config:
    milvus:
      log:
        level: info

컴포넌트별 스케일링 전략

QueryNode 병목: 메모리 (벡터 로드) 스케일 기준: 전체 벡터 크기 / 노드당 메모리 HPA: CPU 70% 또는 KEDA 큐 길이 메모리 대형 인스턴스 권장 (384Gi+)
IndexNode 병목: CPU (인덱스 빌드) 피크: 대량 삽입 직후 sealed 전환 시 오프피크 확장 또는 별도 node pool 고CPU 인스턴스 (32코어+)
DataNode 병목: I/O (S3 쓰기) 삽입 처리량 비례로 스케일 S3 멀티파트 업로드 최적화 필요
Milvus K8s 컴포넌트 스케일링 가이드

세그먼트 라이프사이클과 운영 주의점

[Growing]  삽입 수신 → 메모리 버퍼
    ↓  (크기 임계값 또는 명시적 flush)
[Sealed]   S3에 binlog 기록
    ↓  (IndexCoord → IndexNode 작업 생성)
[Indexed]  S3에 인덱스 파일 저장
    ↓  (QueryCoord → QueryNode 로드 지시)
[Loaded]   QueryNode 메모리/디스크에 로드 → 검색 가능

운영 주의사항:

  1. flush 과다 호출 금지: 매 삽입 후 flush를 호출하면 소형 세그먼트가 과다 생성되어 인덱스 빌드 부하가 급증한다. 배치 삽입 완료 후 1회 flush.
  1. Compaction 이해: 소형 sealed 세그먼트는 DataCoord가 자동으로 병합(Compaction)한다. 수동 트리거: client.compact(collection_name).
  1. 메모리 할당 예측:
QueryNode 메모리 = 벡터 크기 + 인덱스 크기 + 페이로드
HNSW 추가 메모리 ≈ N × M × 8 bytes
예: 1억 벡터 × 1536d × 4 bytes = 614 GB (float32)
    HNSW 인덱스 (M=16) ≈ 12.8 GB 추가
  1. etcd 디스크 성능: etcd는 fsync 지연에 매우 민감하다. NVMe SSD에 배치하고 99퍼센타일 fsync 지연 < 10ms를 유지해야 한다.

운영 체크리스트

배포 전

항목확인 내용
벡터 수·차원·QPS 추정Milvus Sizing Tool로 QueryNode 메모리 산출
인덱스 타입 결정메모리 vs 지연 vs recall 트레이드오프 확인
메시지 큐 선택Pulsar (기본) vs Kafka (관측성 우선)
etcd 스토리지NVMe SSD, fsync 지연 측정
오브젝트 스토리지S3/MinIO 버킷 권한, 멀티파트 설정

핵심 모니터링 지표

지표임계값 기준의미
milvus_querynode_segment_num급증 시 이상세그먼트 수 폭발 (flush 과다)
milvus_querynode_load_memory_bytes노드 메모리의 80% 초과OOM 위험
milvus_indexnode_index_task_num적체 증가IndexNode 병목
milvus_proxy_req_latency_p99SLO 기준엔드투엔드 검색 지연
etcd_disk_backend_commit_duration_seconds (p99)> 0.01setcd 디스크 병목

References