MSA 설계 & 서비스 분리 — 면접 대비 정리
모놀리식 vs MSA 장단점, 서비스 분리 기준(DDD), 서비스 간 통신 방식, API Gateway, 실무에서 모놀리식을 MSA로 전환한 경험을 정리한다.
모놀리식 vs MSA
모놀리식
모든 기능이 하나의 배포 단위에 있다.
┌─────────────────────────────┐
│ Monolith │
│ ┌──────┐ ┌──────┐ ┌──────┐│
│ │ 주문 │ │ 결제 │ │ 알림 ││
│ └──────┘ └──────┘ └──────┘│
│ Single DB │
└─────────────────────────────┘
장점:
- 개발/배포가 단순하다.
- 트랜잭션이 단순하다 (단일 DB).
- 서비스 간 통신 비용이 없다.
- 초기 개발 속도가 빠르다.
단점:
- 특정 기능만 스케일 아웃 불가능 (전체를 올려야 함).
- 하나가 죽으면 전체가 죽는다.
- 배포 주기가 맞춰져야 한다.
- 코드베이스가 커질수록 빌드/배포 느려진다.
MSA
기능을 독립적인 서비스로 분리한다. 각 서비스는 독립 배포 단위다.
클라이언트
↓
API Gateway
↓ ↓ ↓
주문 서비스 결제 서비스 알림 서비스
DB DB DB
장점:
- 서비스별 독립 스케일 아웃.
- 서비스별 독립 배포.
- 장애 격리 (주문 서비스 장애가 결제에 영향 없음).
- 서비스별 기술 스택 선택 가능.
단점:
- 서비스 간 통신 복잡성.
- 분산 트랜잭션 처리 어려움.
- 운영 복잡성 증가 (모니터링, 배포, 서비스 디스커버리).
- 네트워크 지연과 장애 포인트 증가.
서비스 분리 기준
DDD (Domain-Driven Design) 기반
바운디드 컨텍스트(Bounded Context): 특정 도메인 모델이 일관되게 적용되는 경계.
주문 컨텍스트 결제 컨텍스트 배송 컨텍스트
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Order │ │ Payment │ │ Delivery │
│ OrderItem │ │ Invoice │ │ Tracking │
│ Cart │ │ Refund │ │ Address │
└────────────┘ └────────────┘ └────────────┘
각 컨텍스트 안에서 "Order"가 가지는 의미가 다를 수 있다. 주문 컨텍스트에서 Order는 상품/수량이고, 결제 컨텍스트에서는 금액/결제수단이다.
분리 판단 기준
변경 빈도: 자주 바뀌는 기능을 분리한다. 프로모션/이벤트 기능은 자주 바뀌므로 분리하면 다른 서비스 영향 없이 배포 가능.
스케일 요구사항: 트래픽 패턴이 다른 것을 분리한다. 검색은 읽기 폭발적, 주문은 적당히 → 각각 스케일 전략이 다르다.
팀 경계: 팀이 독립적으로 개발/배포할 수 있는 단위로 분리한다 (Conway's Law).
데이터 소유권: 각 서비스가 자신의 데이터를 소유하고 다른 서비스는 API로만 접근한다.
실무 전환 경험: 모놀리식 → MSA 9개 서비스
기존에는 하나의 Spring Boot 애플리케이션에 위성 영상 수집, 처리, 분석, 서빙이 전부 있었다.
문제점:
- 영상 처리(CPU 집약) 작업이 몰리면 API 응답도 느려짐.
- 영상 처리 배포 시 API 서버도 재시작.
- 특정 기능만 스케일 아웃 불가.
분리 기준:
영상 수집 서비스 (I/O 집약, 독립 스케일)
영상 처리 서비스 (CPU 집약, 독립 스케일)
타일 서빙 서비스 (Go, 읽기 집약)
분석 API 서비스 (Spring Boot, 비즈니스 로직)
사용자 관리 서비스 (변경 빈도 낮음)
인증 서비스 (보안 독립)
알림 서비스 (비동기)
어드민 서비스 (내부)
게이트웨이 (진입점)
처리 파이프라인을 RabbitMQ로 연결했다: 수집 → [MQ] → 처리 → [MQ] → 분석.
결과: 재배포 월 10건 → 1건, 인프라 비용 60% 절감, 영상 처리 스케일 아웃으로 처리 속도 향상.
서비스 간 통신
동기 통신: REST / gRPC
서비스 A ──(HTTP 요청)──→ 서비스 B ──(응답)──→ 서비스 A
REST: 범용적, 디버깅 쉬움, 오버헤드 있음 (JSON 파싱).
gRPC: Protocol Buffers로 직렬화, 빠름, 양방향 스트리밍 지원. 브라우저 직접 호출 어려움.
동기 통신은 강한 결합을 만든다. B가 죽으면 A도 실패한다.
비동기 통신: 메시지 큐
서비스 A ──(이벤트 발행)──→ [메시지 브로커] ──→ 서비스 B
발행자는 구독자의 처리를 기다리지 않는다. 느슨한 결합.
B가 잠시 죽어도 메시지가 큐에 쌓인다. B가 복구되면 이어서 처리.
API Gateway
모든 클라이언트 요청의 진입점.
클라이언트
↓
API Gateway
├── 인증/인가 (JWT 검증)
├── 라우팅 (경로별로 서비스 분기)
├── 로드 밸런싱
├── Rate Limiting
├── 요청/응답 변환
└── 로깅/모니터링
↓
주문서비스 결제서비스 사용자서비스
클라이언트는 각 서비스의 위치를 알 필요 없다. Gateway가 단일 엔드포인트를 제공한다.
Spring Cloud Gateway 예시:
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service # 로드 밸런서
predicates:
- Path=/api/orders/**
filters:
- AuthenticationFilter # JWT 검증
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
- id: payment-service
uri: lb://payment-service
predicates:
- Path=/api/payments/**
서비스 디스커버리
서비스 인스턴스가 동적으로 늘고 줄 때 IP/포트를 어떻게 찾는가.
Client-Side Discovery (Eureka):
서비스 시작 → Eureka에 등록
클라이언트 → Eureka에서 서비스 목록 조회 → 직접 로드 밸런싱
Server-Side Discovery (K8s Service):
클라이언트 → K8s Service (고정 DNS) → kube-proxy → Pod
K8s 환경에서는 K8s Service가 디스커버리를 대신한다. Eureka 없이도 된다.
면접에서 자주 나오는 질문
Q. 모놀리식을 MSA로 전환하는 기준은?
팀 규모 증가로 배포 충돌이 잦을 때, 특정 기능만 스케일이 필요할 때, 장애 격리가 필요할 때. 반대로 팀이 작고 트래픽이 낮은 초기 단계에서는 모놀리식이 낫다.
Q. MSA에서 데이터 일관성은 어떻게 보장하는가?
각 서비스가 자체 DB를 가지면 단일 트랜잭션이 불가능하다. 사가 패턴(Saga)을 사용한다. 각 서비스가 로컬 트랜잭션을 실행하고 이벤트로 다음 서비스를 트리거한다. 실패 시 보상 트랜잭션을 실행한다.
Q. API Gateway의 역할은?
인증/인가 중앙화, 라우팅, 로드 밸런싱, Rate Limiting, 로깅. 각 서비스가 인증 로직을 중복으로 갖지 않아도 된다.
Q. 서비스 간 통신에서 동기/비동기를 언제 선택하는가?
즉각적인 응답이 필요하면 동기(REST/gRPC). 주문 조회, 사용자 정보 확인 등. 응답을 기다릴 필요 없거나 처리에 시간이 걸리면 비동기(메시지 큐). 이메일 발송, 영상 처리, 통계 집계 등.