fos-blog/study
01 / 홈02 / 카테고리03 / 시리즈
01 / 홈02 / 카테고리03 / 시리즈

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • langgraph 페이지로 이동
    • 사람용 CLI와 AI 에이전트용 CLI는 설계가 다르다
    • agents.md
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code 메모리: CLAUDE.md와 .claude/rules를 규칙으로 쓰는 법
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code를 5주 더 쓴 결과 — 스킬·CLAUDE.md를 키워가는 방식
    • Claude Code를 11일 동안 쓴 결과 — 데이터로 본 나의 사용 패턴
    • Claude Code 멀티 에이전트 — Teams
    • AI 에이전트와 디자인의 새 컨벤션 — DESIGN.md, Google Stitch, Claude Design
    • Docling — IBM Research 의 문서 파싱 toolkit 상세 정리
    • 하네스 엔지니어링 실전 — 4인 에이전트 팀으로 코딩 파이프라인 구축하기
    • 하네스 엔지니어링 — 오래 실행되는 AI 에이전트를 위한 설계
    • 멀티모달 LLM (Multimodal Large Language Model)
    • AI 에이전트와 함께 MVP 만들기 — dooray-cli 사례
    • OpenClaw는 context와 memory를 어떻게 관리하나 — 나만의 에이전트를 구성하는 법
    • OpenClaw vs Hermes Agent — 갈아탈까 고민하며 정리한 비교
    • 스킬 문서를 신경망처럼 학습시킨다 — Microsoft SkillOpt 분석
  • ai 페이지로 이동
    • agent 페이지로 이동
    • [초안] AI 제품 백엔드 안정성 — 지연·비용·권한·관측·도구 실패·폴백/재시도/사람 에스컬레이션
    • [초안] LLM 평가 프레임워크: 골든셋, 회귀 테스트, LLM-as-a-judge, 사람 피드백 루프
  • algorithm 페이지로 이동
    • live-coding 페이지로 이동
    • 분산 계산을 위한 알고리즘
  • apartment 페이지로 이동
    • 구리 럭키아파트 24평 인테리어 레퍼런스 모음
  • architecture 페이지로 이동
    • [초안] 시니어 백엔드를 위한 API 설계 실전 스터디 팩 — REST · 멱등성 · 페이지네이션 · 버전 전략
    • [초안] API Versioning과 Backward Compatibility: 시니어 백엔드 관점 정리
    • 캐시 설계 전략 총정리
    • [초안] 커머스 Spring 서비스에 Clean/Hexagonal Architecture를 실용적으로 적용하기
    • [초안] 커머스 도메인 모델링: 주문·재고·노출의 세 축을 분리해서 설계하기
    • 커머스 주문 상태와 데이터 정합성 기본기
    • [초안] 쿠폰/프로모션 동시성과 정합성 기본기 — 선착순·중복 사용 방지·발급/사용/복구
    • [초안] DDD와 도메인 모델링: 시니어 백엔드 관점의 전술/전략 패턴 실전 가이드
    • [초안] Decorator & Chain of Responsibility — 행동을 체인으로 조립하는 두 가지 방식
    • 디자인 패턴
    • [초안] 분산 아키텍처 완전 정복: Java 백엔드 시니어 인터뷰 대비 실전 가이드
    • [초안] 분산 트랜잭션과 Outbox 패턴 — 왜 2PC를 피하고 어떻게 대신할 것인가
    • 분산 트랜잭션
    • [초안] e-Commerce 주문·결제 도메인 모델링: 상태머신, 멱등성, Outbox/Saga 실전 정리
    • [초안] Event Sourcing과 CQRS — 상태가 아니라 변화를 저장한다는 발상
    • [초안] F&B 쿠폰·프로모션·멤버십·포인트 설계
    • [초안] F&B · e-Commerce 디지털 채널 도메인 한 장 정리
    • [초안] F&B 주문/매장/픽업 상태머신 설계
    • [초안] F&B 이커머스 결제·환불·정산 운영 가이드
    • [초안] Hexagonal / Clean Architecture를 Spring 백엔드에 적용하기
    • [초안] 대규모 커머스 트래픽 처리 패턴 — 대규모 회원과 메가 프로모션을 버티는 설계
    • [초안] 레거시 JSP/jQuery 화면과 신규 API가 공존하는 백엔드 운영 전략
    • [초안] MSA 서비스 간 통신: Redis [Cache-Aside](../database/redis/cache-aside.md) × Kafka 이벤트 하이브리드 설계
    • [초안] Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • [초안] Outbox / Inbox Pattern 심화 — 분산 메시징의 정합성 문제를 DB 트랜잭션으로 풀어내기
    • [초안] 결제 도메인 멱등성과 트랜잭션 재시도 기본기
    • [초안] 시니어 백엔드를 위한 Resilience 패턴 실전 가이드 — Timeout, Retry, Circuit Breaker, Bulkhead, Backpressure
    • [초안] REST API 버저닝과 모바일 앱 하위 호환성 — 디지털 채널 백엔드 관점
    • [초안] Spring Batch vs Event-Driven — 같은 비동기처럼 보이지만 전혀 다른 두 패러다임
    • [초안] Strategy Pattern — 분기문을 없애는 설계, 시니어 백엔드 인터뷰 핵심 패턴
    • [초안] 시니어 백엔드를 위한 시스템 설계 입문 스터디 팩
    • [초안] 템플릿 메서드 패턴 - 백엔드 처리 골격을 강제하는 가장 오래되고 가장 위험한 패턴
    • [초안] 대규모 트래픽 중 무중단 마이그레이션 — Feature Flag + Shadow Mode 실전
  • database 페이지로 이동
    • milvus 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • redis 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • [초안] DB Connection Pool Saturation과 Thread Pool 격리
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • [초안] JPA N+1과 커머스 조회 모델: 주문/메뉴/쿠폰 도메인에서 살아남기
    • [초안] MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • [초안] MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 벡터 DB 어떻게 고를까 — OpenSearch · Milvus · Qdrant · Vespa 비교
    • 벡터 DB를 실제로 도입한 사례 — 빅테크 프로덕션
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • observability 페이지로 이동
    • [초안] 커머스/F&B 채널 장애 첫 5분과 관측성 기본기
    • [초안] 운영 데이터 정합성 장애 대응 — 결제 취소 누락과 중복 적재 런북
    • Envoy Proxy
    • [초안] F&B / e-Commerce 운영 장애 대응과 모니터링 — 백엔드 관점 정리
    • Graceful Shutdown
    • [초안] 시니어 백엔드를 위한 SLO와 Error Budget 기반 장애 대응
  • http 페이지로 이동
    • HTTP Connection Pool
    • HTTPS는 어떻게 안전한가 — TLS, 인증서, 그리고 termination
  • interview 페이지로 이동
    • [초안] AI 서비스 팀 경험 기반 시니어 백엔드 면접 질문 뱅크 — Spring Batch RAG / gRPC graceful shutdown / 전략 패턴 / 12일 AI 웹툰 MVP
    • Observability — 면접 답변 프레임
    • [초안] 시니어 Java 백엔드 면접 마스터 플레이북 — 김병태
    • [초안] NSC 슬롯팀 경험 기반 질문 은행 — 도메인 모델링·동시성·성능·AI 협업
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • [초안] Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • [초안] JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • npm vs pnpm — 어떤 기준으로 선택했나
    • `setImmediate()`
  • kafka 페이지로 이동
    • [초안] Kafka 기본 개념 — 토픽, 파티션, 오프셋, 복제
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • [초안] Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • 메시지 전송 신뢰성
    • [초안] Spring Kafka 컨슈머 오프셋 커밋과 트랜잭션 정렬: AckMode, manual ack, 멱등 처리
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • mlops 페이지로 이동
    • Python CUDA 버전 생태계 — nvidia-smi, nvcc, pip, conda가 다 다른 버전을 말하는 이유
    • GPU 컨테이너의 CUDA 버전 호환성 — nvidia-smi부터 이미지 다이어트까지
    • Kubernetes GPU 노드에서 /run tmpfs가 꽉 차서 Pod가 안 뜰 때
    • GPU·CUDA·MPS 기초 — 자바 백엔드 개발자가 처음 만나는 그림
    • Multi-process GPU 워크로드 — 자바 ThreadPool 사용자가 만나는 모델 차이
    • ML 서비스 성능 분석 워크플로 — 자바 백엔드 트러블슈팅과 다른 점
    • 한 GPU 를 여러 프로세스가 나눠 쓰기 — Time-Slicing 과 MPS
  • network 페이지로 이동
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • python 페이지로 이동
    • Python async/await — CompletableFuture·Reactor 와 다른 점, 그리고 blocking I/O 함정
    • Python 의존성 관리 — Java Maven/Gradle 사용자가 만나는 첫 충격
    • FastAPI 기초 — Spring Boot 사용자가 빠르게 익히는 법
    • Java 개발자를 위한 Python 심화 — OOP·데코레이터·컨텍스트 매니저
    • PyTorch 기초 — 텐서, 디바이스, 그리고 모델 로딩이 무거운 이유
    • Java 개발자를 위한 Python 문법 핵심
    • OCR 동작 원리 — Layout · Text · Post-process 3단계
    • Python 서버의 RSS 가 안 줄어드는 이유 — gc.collect 의 한계와 malloc_trim
  • rabbitmq 페이지로 이동
    • [초안] RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • [초안] RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • security 페이지로 이동
    • [초안] 시니어 백엔드를 위한 보안 / 인증 스터디 팩 — Spring Security, JWT, OAuth2, OWASP Top 10
    • [초안] Spring Security 6.x OAuth2 + JWT 상용 인증 설계 — Grant 선택, Resource Server, Refresh Rotation, 로그아웃
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • testing 페이지로 이동
    • [초안] 시니어 Java 백엔드를 위한 테스트 전략 완전 정리 — 피라미드부터 TestContainers, 마이크로벤치, Contract까지
  • travel 페이지로 이동
    • 오사카 3박 4일 일정표: 우메다 쇼핑, USJ, 난바·도톤보리, 오사카성
  • web 페이지로 이동
    • [초안] HTTP / Cookie / Session / Token 인증 기본기 — 레거시 JSP와 모바일 API가 공존하는 백엔드 관점
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/database/Milvus 벡터 데이터베이스 입문 — 아키…
db

Milvus 벡터 데이터베이스 입문 — 아키텍처와 동작, 그리고 실무 규모에서의 성능

RAG 시스템을 OpenSearch 의 k-NN 으로 운영해 오다가, 전용 벡터 데이터베이스인 Milvus 를 본격적으로 들여다볼 일이 생겼다. 막상 공부해 보니 일반적인 DB 와 구조가 꽤 달랐다. 컴포넌트가 예닐곱 개로 쪼개져 있고, "storage 와 compute 를 분리했다"는 말이 핵심이라는데 처음엔 그게 왜 중요한지 잘 와닿지 않았다. 이 글은...

2026.06.18·10 min read·8 views

RAG 시스템을 OpenSearch 의 k-NN 으로 운영해 오다가, 전용 벡터 데이터베이스인 Milvus 를 본격적으로 들여다볼 일이 생겼다. 막상 공부해 보니 일반적인 DB 와 구조가 꽤 달랐다. 컴포넌트가 예닐곱 개로 쪼개져 있고, "storage 와 compute 를 분리했다"는 말이 핵심이라는데 처음엔 그게 왜 중요한지 잘 와닿지 않았다. 이 글은 Milvus 가 무엇이고, 어떤 구조로 어떻게 동작하며, 우리가 다루는 정도의 규모(수천만 벡터)에서 어느 정도 리소스가 필요한지를 공부하며 정리한 기록이다.

직접 로컬에 Milvus 2.6 과 OpenSearch 를 같이 띄우고 같은 데이터로 비교해 본 결과도 군데군데 섞었다.


벡터 DB 와 Milvus 는 왜 필요한가

검색에는 크게 두 가지 방식이 있다.

  • dense 검색 — 의미로 찾기. 문장을 임베딩 모델로 1024차원 같은 실수 벡터로 압축한 뒤, 벡터 간 거리로 "뜻이 비슷한" 문서를 찾는다. "자동차"로 검색해도 "차량"이 걸린다. 모든 차원에 값이 빽빽이 차 있어서 dense, 곧 밀집이다.
  • sparse 검색 — 키워드로 찾기. BM25 같은 통계 기반으로 단어 출현을 본다. 어휘 사전 크기만큼 차원이 크지만 등장한 단어만 값이 있고 나머지는 0이라 sparse, 곧 희소다. 정확한 단어·고유명사·코드값에 강하다.

RAG 에서는 이 둘을 합친 하이브리드 검색이 사실상 표준이다. 의미로도 찾고 키워드로도 찾은 뒤 점수를 합친다.

벡터 DB 는 dense 벡터의 최근접 이웃(ANN) 검색을 빠르게 하기 위한 전용 저장소다. 물론 OpenSearch·PostgreSQL(pgvector) 같은 범용 엔진에도 벡터 기능이 붙어 있다. 그럼에도 Milvus 같은 전용 DB 를 보는 이유는 성능이라기보다 기능의 폭이다 — 학습형 sparse(SPLADE), multi-vector, GPU 인덱스, DiskANN 같은 것들이 처음부터 1급 기능으로 들어가 있다.


아키텍처 — storage 와 compute 를 분리한다

Milvus 의 가장 큰 특징은 저장(storage)과 연산(compute)을 완전히 분리한 구조다. 데이터는 오브젝트 스토리지(S3/MinIO)에 두고, 검색·색인을 담당하는 노드들은 상태를 거의 갖지 않는다. 그래서 "검색이 느리면 검색 노드만 늘리고, 색인이 밀리면 색인 노드만 늘리는" 식으로 컴포넌트별 독립 확장이 된다. 이게 분리 구조의 실익이다.

전체는 네 계층으로 나뉜다.

계층역할
Access Layer (Proxy)클라이언트 요청의 진입점. 무상태 프록시들이 요청을 검증·라우팅하고 결과를 모아 돌려준다.
Coordinator클러스터의 두뇌. 스키마(DDL) 관리, 타임스탬프 발급(TSO), 작업 스케줄링, 일관성 보장.
Worker Nodes코디네이터 지시를 따르는 실행기. Streaming / Query / Data 세 종류.
Storage영속성 담당. 메타 저장소(etcd) + 오브젝트 스토리지(S3) + WAL.

worker 노드 세 종류의 분업이 핵심이다.

  • Streaming Node — 실시간으로 들어오는 데이터를 받아 WAL 에 쓰고, 아직 영속화되지 않은 최신 데이터(growing segment)에 대한 검색을 담당한다. 2.6 에서 역할이 명확히 분리됐다.
  • Query Node — 이미 오브젝트 스토리지에 저장된 과거 데이터(sealed segment)를 메모리에 올려 검색한다.
  • Data Node — 컴팩션·인덱스 빌드 같은 오프라인 처리를 한다.

저장층은 셋으로 나뉜다.

  • 메타 저장소 — etcd. 컬렉션 스키마, 토폴로지, 소비 체크포인트 같은 핵심 메타데이터를 담는다. 강한 일관성이 필요해 etcd 를 쓴다.
  • 오브젝트 스토리지 — S3/MinIO. 실제 벡터 데이터, 인덱스 파일, 로그 스냅샷.
  • WAL — Write-Ahead Log, 데이터 내구성의 토대. 예전엔 Kafka/Pulsar 같은 메시지 큐가 필요했는데, 2.6 의 Woodpecker 는 별도 디스크 없이 오브젝트 스토리지에 직접 기록하는 zero-disk 모드를 지원한다.

참고로 Woodpecker 는 상업적 사용 라이선스 이슈가 거론되기도 해서, 환경에 따라 Kafka 를 메시지 큐로 쓰기도 한다.

잠깐 — etcd · WAL · 오브젝트 스토리지가 뭔가

처음 보면 생소한 기반 기술들이라 짧게 짚고 간다.

  • etcd — 분산 환경에서 쓰는 key-value 저장소다. 여러 노드가 동시에 읽어도 항상 같은 값을 보장한다(강한 일관성, Raft 합의). 쿠버네티스도 클러스터 상태를 etcd 에 저장한다. Milvus 는 "어떤 컬렉션이 있고 어디에 배치됐는지" 같은, 작지만 절대 틀리면 안 되는 메타데이터를 여기 둔다.
  • WAL — Write-Ahead Log. 변경을 실제 데이터에 적용하기 전에 로그로 먼저 적어두는 기법이다. 갑자기 죽어도 로그를 다시 재생하면 복구되니, 데이터 유실을 막는 안전장치다. 거의 모든 DB 의 내구성이 이 위에 선다. Milvus 는 들어온 데이터를 WAL 에 먼저 커밋한 뒤 천천히 정리한다.
  • 오브젝트 스토리지 — S3, MinIO 같은 대용량 파일 저장소다. 파일을 통째로 넣고 꺼낸다. AWS S3 가 대표격이고, MinIO 는 사내에 직접 띄울 수 있는 S3 호환 구현이다. 싸고 거의 무한히 확장돼서, Milvus 는 벡터·인덱스 같은 큰 데이터를 여기에 영구 저장한다.
  • 메시지 큐 — Kafka, Pulsar, Woodpecker. 데이터를 순서대로 흘려보내는 통로다. 쓰기 요청이 한꺼번에 몰려도 큐에 쌓아두고 차례로 처리해 안정성을 높인다. Woodpecker 는 Milvus 가 자체 도입한 경량 방식으로, 별도 큐 서버 없이 오브젝트 스토리지에 바로 쓴다.

정리하면, etcd 가 메타데이터를, 오브젝트 스토리지가 실제 데이터를, WAL 과 메시지 큐가 안전한 전달을 맡아 Milvus 저장층을 떠받친다.

배포 모드 — Lite / Standalone / Distributed

이 복잡한 구조가 부담스럽다면 다행히 규모별 배포 모드가 있다.

  • Milvus Lite — 파이썬 라이브러리로 임포트해서 쓰는 임베디드 모드. 노트북·프로토타이핑·엣지용.
  • Standalone — 단일 Docker(또는 컴포넌트 묶음)로 띄우는 모드. 수천만 벡터 정도까지 충분하다.
  • Distributed — 위 컴포넌트들을 Kubernetes 에 펼쳐 수십억 벡터까지 확장하는 모드.

같은 API 로 노트북 실험부터 분산 운영까지 이어진다는 게 장점이다.


동작 방식 — 데이터가 들어와서 검색되기까지

데이터 인입

  1. Proxy 가 쓰기 요청을 샤드별로 쪼갠다. 각 가상 채널(vchannel)이 물리 채널(pchannel)에 매핑돼 Streaming Node 로 간다.
  2. Streaming Node 가 각 데이터에 타임스탬프(TSO)를 붙여 전체 순서를 정하고, WAL 에 먼저 안전하게 커밋한다.
  3. WAL 엔트리는 비동기로 잘려 segment 가 된다.

여기서 segment 두 종류를 이해하면 동작이 한눈에 들어온다.

구분Growing segmentSealed segment
상태아직 오브젝트 스토리지에 영속화 안 됨전부 영속화됨, 불변(immutable)
위치Streaming Node 메모리오브젝트 스토리지
검색 담당Streaming NodeQuery Node

Streaming Node 가 해당 segment 의 WAL 을 다 기록하면 flush 가 일어나 growing 이 sealed 로 바뀌고, 읽기에 최적화된 상태가 된다.

인덱스 빌드

Data Node 가 오브젝트 스토리지에서 로그 스냅샷을 읽어 메모리에 올리고, 인덱스를 만들어 다시 직렬화해 저장한다. 이때 SIMD(AVX2/AVX512) 가속을 쓴다.

검색 흐름

검색은 "최신 데이터와 과거 데이터를 동시에 뒤져서 합치는" 구조다.

  1. Proxy 가 요청을 관련 샤드의 Streaming Node 들에 뿌린다.
  2. Streaming Node 는 자기 growing 데이터를, Query Node 는 sealed segment 를 동시에 검색한다.
  3. 각 샤드 결과를 모아 한 샤드 결과로 합치고, Proxy 가 전체 샤드 결과를 다시 병합해 돌려준다.

데이터가 sealed 로 바뀌면 Coordinator 가 그 부담을 Query Node 들에 고르게 재분배(handoff)해 메모리·CPU 를 최적화한다.


인덱스 종류와 선택 기준

Milvus 가 범용 엔진과 벌어지는 지점이 인덱스의 폭이다.

인덱스성격언제
FLAT무손실 전수 검색정확도 100%, 소규모
IVF 계열클러스터로 나눠 후보만 탐색속도·정확도 균형
HNSW그래프 기반, 고QPS·고recall인메모리 기본 선택지
DiskANN그래프를 디스크에 두고 일부만 메모리RAM 초과하는 대용량
GPU 계열(CAGRA 등)GPU 가속 빌드·검색초대규모·고처리량

핵심 트레이드오프는 메모리 대 디스크, 그리고 recall 대 QPS 다. HNSW 는 빠르고 정확하지만 벡터를 메모리에 올려야 한다. 데이터가 RAM 을 넘어서면 DiskANN 으로 디스크에 두되 PQ 코드만 메모리에 남기는 식으로 비용을 낮춘다. 다만 DiskANN 도 PQ 코드는 데이터 크기에 대략 비례해서 메모리가 "사라지는" 건 아니고 "줄어드는" 것이다.


한국어 하이브리드 검색

한국어는 띄어쓰기만으로 단어가 갈리지 않아서 형태소 분석이 필요하다. OpenSearch 에서는 보통 nori 를 쓴다. Milvus 도 2.5 에서 BM25 기반 full-text search 가 들어왔고, 2.6 에서 다국어가 강화되면서 lindera 토크나이저 + ko-dic 으로 한국어 형태소 분석을 지원한다. ko-dic 은 MeCab 기반 한국어 사전이다.

설정은 nori 와 거의 1:1 로 대응한다.

OpenSearchMilvus
nori_tokenizerlindera + dict_kind ko-dic
nori_part_of_speech 필터korean_stop_tags 필터

직접 로컬에서 같은 한국어 문장을 양쪽에 넣어 토큰을 비교해 봤다.

  • nori: 서울 / 맛있 / 음식 / 먹
  • lindera(ko-dic, 품사 필터 적용): 서울 / 맛있 / 음식 / 먹

조사·어미를 걸러내면 의미 토큰이 사실상 같았다. 오히려 복합명사는 lindera 쪽이 더 자연스러웠다 — nori 가 "데이터베이스"를 "데이터/베이스"로 쪼개는 반면 lindera 는 통째로 유지했다.

dense 는 같은 임베딩 모델(bge-m3, 1024차원)을 양쪽에 넣었더니, OpenSearch 와 Milvus 의 top-10 결과가 약 95% 겹쳤다. 같은 임베딩이면 의미 검색 결과는 사실상 동등하다는 뜻이다.


우리 정도 규모에서의 성능과 리소스

공부하면서 가장 궁금했던 건 "그래서 우리 규모에 이게 과한가?"였다.

기준을 잡아보면 수천만 벡터 규모(약 1,600만 청크, 1024차원)에 검색 트래픽은 초당 1건도 안 되는 저부하다. raw 벡터 데이터만 보면 1,600만 × 1024차원 × 4바이트(float32) ≈ 약 68GB 다. HNSW 그래프 오버헤드를 더해도 단일~소수 노드가 감당하는 규모다.

여기서 배운 게 두 가지다.

  • 이 정도 규모는 분산까지 갈 필요가 없다. Standalone 또는 작은 클러스터로 충분하다. "수십억 벡터 확장"은 Milvus 의 강점이지만, 그 강점이 필요해서 전용 DB 를 쓰는 건 아니다.
  • 저부하에서 검색 지연은 검색 엔진이 아니라 주변 파이프라인이 좌우한다. 실제로 RAG 검색 한 번에 수 초가 걸려도, 그 시간의 대부분은 쿼리 임베딩 생성과 재정렬(rerank)이지 벡터 검색 자체가 아니다. 그래서 "벡터 DB 를 바꾸면 빨라진다"는 기대는 대체로 빗나간다.

정리하면 이 규모에서 Milvus 를 고려하는 이유는 속도가 아니라 기능이다 — 하이브리드, sparse, 멀티테넌시, 멀티링궐 같은 것들. 성능은 OpenSearch 든 Milvus 든 이 규모에선 충분하다.


공부하고 나서

처음엔 컴포넌트가 너무 많아서 과하다고 느꼈는데, "storage-compute 분리 + segment(growing/sealed) + WAL" 세 개념을 잡으니 나머지가 그 위에 얹히는 구조로 읽혔다. 정작 실무 판단에서 중요한 건, 우리 규모에선 분산이나 GPU 같은 화려한 기능보다 한국어 하이브리드가 제대로 되는지, 멀티테넌시를 어떻게 설계하는지가 더 실질적이라는 점이었다.

다음엔 멀티테넌시를 collection/partition 으로 어떻게 나누는지, 그리고 sparse(BM25/SPLADE)와 dense 를 RRF 로 합치는 하이브리드 검색을 더 깊게 파볼 생각이다.


용어 한눈에

본문에 나온 생소한 용어를 모았다.

아키텍처·동작

  • TSO — Timestamp Oracle. 모든 작업에 전역 순서를 매기는 중앙 시계다. 분산 환경에서 "누가 먼저 일어난 일인지"를 정한다.
  • vchannel / pchannel — 가상 채널과 물리 채널. 데이터를 샤드 단위로 흘려보내는 논리·물리 통로다.
  • 샤드 — shard. 데이터를 나눠 담는 조각이다. 나눠야 여러 노드가 병렬로 처리할 수 있다.
  • flush — 메모리에 쌓인 growing segment 를 오브젝트 스토리지로 내려 sealed 로 굳히는 동작이다.
  • 컴팩션 — compaction. 잘게 쌓인 작은 segment 들을 합쳐 검색 효율을 높이는 정리 작업이다.
  • handoff — sealed 된 데이터를 Query Node 들에 고르게 재분배하는 것이다.
  • DDL — Data Definition Language. 컬렉션 생성·삭제 같은 스키마 정의 명령이다.

인덱스·성능

  • SIMD — AVX2/AVX512 같은 CPU 병렬 연산 명령이다. 여러 숫자를 한 번에 계산해 벡터 연산을 가속한다.
  • PQ — Product Quantization. 벡터를 잘게 쪼개 코드로 압축하는 양자화 기법이다. 메모리를 크게 아낀다.
  • IVF — 벡터를 여러 클러스터로 나눈 뒤 쿼리와 가까운 클러스터만 탐색하는 인덱스다.
  • DiskANN — 그래프 인덱스를 디스크에 두고 일부만 메모리에 올려 대용량을 처리하는 방식이다.
  • CAGRA — GPU 에 최적화된 그래프 인덱스다.
  • QPS — Queries Per Second. 초당 처리하는 쿼리 수로, 부하를 나타내는 지표다.
  • recall — 전수 비교로 찾은 진짜 정답 중 근사 검색이 회수한 비율이다. 검색 정확도 척도.

RAG 검색

  • BM25 — 단어 빈도 기반의 전통적인 키워드 검색 점수 방식이다. sparse 검색의 대표격.
  • SPLADE — 신경망으로 만든 학습형 sparse 다. 단순 빈도를 넘어 의미까지 반영한 키워드 검색.
  • multi-vector — 한 문서에 여러 벡터(제목·본문 등)를 두고 함께 검색·재정렬하는 기능이다.
  • RRF — Reciprocal Rank Fusion. 여러 검색 결과의 순위를 합쳐 하나로 융합하는 방식이다.
  • dense / sparse — dense 는 의미를 담은 밀집 벡터, sparse 는 단어 출현 기반의 희소 벡터다.

참고 링크

  • Milvus Architecture Overview
  • Milvus Data Processing
  • Milvus Main Components
  • Full-Text Search (BM25)
  • Lindera Tokenizer
  • Index Explained
on this page
  • 01벡터 DB 와 Milvus 는 왜 필요한가
  • 02아키텍처 — storage 와 compute 를 분리한다
  • 잠깐 — etcd · WAL · 오브젝트 스토리지가 뭔가
  • 배포 모드 — Lite / Standalone / Distributed
  • 03동작 방식 — 데이터가 들어와서 검색되기까지
  • 데이터 인입
  • 인덱스 빌드
  • 검색 흐름
  • 04인덱스 종류와 선택 기준
  • 05한국어 하이브리드 검색
  • 06우리 정도 규모에서의 성능과 리소스
  • 07공부하고 나서
  • 08용어 한눈에
  • 09참고 링크

이런 글도

  • 벡터 DB를 실제로 도입한 사례 — 빅테크 프로덕션
    벡터 DB를 공부하다 보면 "실제 큰 서비스가 전용 벡터 DB를 운영에 올린 사례"가 궁금하다. ANN 라이브러리(FAISS·Annoy)나 임베딩 모델이 아니라, 벡터 DB 제품을 프로덕션에 도입한 사례를 회사 엔지니어링 블로그(1차 출처) 중심으로 모았다. | 회사 | 도입 | 규모 | use case | | --- | --- | --- | --- | |...
    🗄️ db
    db
    2026.06.18
  • 벡터 DB 어떻게 고를까 — OpenSearch · Milvus · Qdrant · Vespa 비교
    RAG 를 만들면 임베딩한 벡터를 어딘가에 저장하고 검색해야 한다. 처음엔 쓰던 검색엔진(OpenSearch)에 벡터 기능을 얹어 시작했는데, 전용 벡터 DB 로 옮길지 고민이 생기면서 후보들을 제대로 비교해 봤다. 결론부터 말하면, 규모가 크지 않으면 뭘 골라도 성능은 충분하고 선택을 가르는 건 성능이 아니라 기능과 운영이라는 것이었다. 이 글은 Open...
    🗄️ db
    db
    2026.06.18
  • OpenSearch vs Milvus — 벡터 검색, 무엇이 어떻게 다른가
    RAG 를 OpenSearch 의 k-NN 으로 운영하다가 전용 벡터 DB 인 Milvus 로 옮길지 검토하면서, 두 제품을 아키텍처부터 검색·메모리·운영까지 1:1 로 뜯어봤다. 4제품을 훑는 선택 가이드와 달리, 이 글은 실제로 저울에 올린 두 후보를 깊게 대조한다. 한 줄 요약 — 검색 품질은 사실상 동등하고, 진짜 차이는 아키텍처 철학(범용 검색엔진...
    🗄️ db
    db
    2026.06.18
  • [초안] Redis Streams 소비자 그룹 신뢰성 — PEL, 재할당, 멱등성까지
    > 이 문서는 Redis Streams의 소비자 그룹(Consumer Group)이 어떻게 메시지 유실 없이 분산 처리를 보장하는가를 운영·장애 관점에서 정리한다. Stream의 기본 명령어와 Pub/Sub과의 비교는 pub-sub.md에 이미 있으므로, 본 문서는 그 위에서 한 단계 더 들어간다 — 소비자가 죽었을 때 메시지는 어디에 남고, 누가 다시 처...
    🗄️ db
    db
    2026.06.11

댓글 (0)