설계Hard#100
마이크로서비스 아키텍처(MSA)와 모놀리식(Monolithic) 아키텍처의 장단점을 비교해주세요.
#설계#MSA#아키텍처#마이크로서비스
힌트
확장성, 배포 독립성, 운영 복잡도를 기준으로 생각해보세요.
정답 및 해설
마이크로서비스 아키텍처(MSA)와 모놀리식(Monolithic) 아키텍처의 장단점을 비교해주세요.
모놀리식(Monolithic) 아키텍처는 애플리케이션의 모든 기능을 하나의 코드베이스와 하나의 배포 단위로 구성하는 방식이고, 마이크로서비스 아키텍처(MSA)는 각 기능을 독립적으로 배포 가능한 작은 서비스들의 집합으로 구성하는 방식입니다. 어느 것이 더 좋다고 할 수 없으며, 팀의 규모, 서비스의 복잡성, 성장 단계에 따라 적합한 아키텍처가 다릅니다.
모놀리식 아키텍처
구조
┌─────────────────────────────────────────────────────┐
│ 모놀리식 애플리케이션 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 사용자 │ │ 주문 │ │ 결제 │ │
│ │ 모듈 │ │ 모듈 │ │ 모듈 │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 상품 │ │ 알림 │ │ 검색 │ │
│ │ 모듈 │ │ 모듈 │ │ 모듈 │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
│ │
│ ┌──────────────────┐ │
│ │ 단일 데이터베이스 │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
모놀리식 코드 예시
프로젝트 구조:
my-ecommerce/
├── src/
│ ├── users/
│ │ ├── user.controller.ts
│ │ ├── user.service.ts
│ │ └── user.repository.ts
│ ├── orders/
│ │ ├── order.controller.ts
│ │ ├── order.service.ts
│ │ └── order.repository.ts
│ ├── payments/
│ │ └── payment.service.ts
│ └── products/
│ └── product.service.ts
├── package.json ← 하나의 패키지
└── docker-compose.yml ← 하나의 배포
// 모놀리식에서 서비스 간 호출 (직접 함수 호출)
// order.service.ts
class OrderService {
constructor(
private userService: UserService, // 직접 의존성 주입
private productService: ProductService,
private paymentService: PaymentService,
private notificationService: NotificationService,
) {}
async createOrder(userId: number, items: OrderItem[]): Promise<Order> {
// 트랜잭션 내에서 모든 작업 처리
return await this.db.transaction(async (trx) => {
const user = await this.userService.findById(userId); // 직접 호출
const products = await this.productService.findByIds(items.map(i => i.productId));
// 재고 확인 및 차감
await this.productService.decreaseStock(items, trx); // 같은 트랜잭션 공유!
const order = await this.orderRepository.create({ userId, items }, trx);
// 결제 처리
await this.paymentService.process(order, trx);
// 알림 전송 (같은 프로세스에서 실행)
await this.notificationService.sendOrderConfirmation(user, order);
return order;
});
// 하나라도 실패하면 전체 롤백 → ACID 보장 용이
}
}
장단점
✅ 모놀리식 장점:
1. 개발/배포 단순
- 단일 코드베이스로 관리
- 하나의 빌드 파이프라인
- 로컬 개발 환경 설정 단순
2. 트랜잭션 처리 용이
- 단일 DB 사용으로 ACID 트랜잭션 자연스럽게 보장
- 분산 트랜잭션 불필요
3. 서비스 간 통신 오버헤드 없음
- 함수 호출 수준의 빠른 통신
- 네트워크 레이턴시 없음
4. 디버깅/모니터링 단순
- 단일 로그 스트림
- 스택 트레이스 명확
5. 테스트 용이
- 전체 시스템 통합 테스트 쉬움
- 서비스 간 계약 테스트 불필요
❌ 모놀리식 단점:
1. 규모 커질수록 복잡성 증가
- 코드베이스가 커질수록 빌드/배포 시간 증가
- 팀 간 코드 충돌 증가
2. 부분 장애가 전체에 영향
- 메모리 누수, 특정 기능 버그가 전체 다운 유발
3. 기술 스택 단일화
- 특정 기능에 최적화된 언어/프레임워크 사용 어려움
- 예: 데이터 처리에 Python, API에 Node.js 혼용 불가
4. 특정 기능만 확장 어려움
- 검색 기능만 스케일아웃하고 싶어도 전체 서버 확장 필요
5. 긴 배포 주기
- 작은 변경도 전체 재배포 필요
- 큰 팀에서 배포 조율 어려움
마이크로서비스 아키텍처 (MSA)
구조
클라이언트
↓
┌─────────────────┐
│ API Gateway │ ← 단일 진입점, 인증/라우팅/로드밸런싱
└────────┬────────┘
│
┌────┴────────────────────────────┐
↓ ↓ ↓ ↓
┌───────┐ ┌───────┐ ┌────────┐ ┌──────────┐
│ User │ │ Order │ │Payment │ │ Product │
│Service│ │Service│ │Service │ │ Service │
│:3001 │ │:3002 │ │:3003 │ │ :3004 │
└───┬───┘ └───┬───┘ └───┬────┘ └────┬─────┘
│ │ │ │
┌───┴──┐ ┌──┴───┐ ┌──┴────┐ ┌──┴────┐
│ UserDB│ │OrderDB│ │PayDB │ │ProdDB │
│(MySQL)│ │(MySQL)│ │(PG) │ │(Mongo)│
└──────┘ └──────┘ └───────┘ └───────┘
서비스 간 통신: REST API 또는 메시지 큐(Kafka, RabbitMQ)
MSA 코드 예시
// 주문 서비스 (Order Service) - 독립적인 마이크로서비스
// 다른 서비스는 HTTP 요청 또는 메시지로 통신
class OrderService {
constructor(
private httpClient: HttpClient,
private messageQueue: MessageQueue,
) {}
async createOrder(userId: number, items: OrderItem[]): Promise<Order> {
// 1. 사용자 서비스에 HTTP 요청 (네트워크 통신)
const user = await this.httpClient.get(`http://user-service/users/${userId}`);
// 2. 상품 서비스에 재고 확인 요청
const products = await this.httpClient.post(
'http://product-service/products/check-stock',
{ items }
);
// 3. 주문 생성 (자신의 DB에만 저장)
const order = await this.orderRepository.create({ userId, items });
// 4. 이벤트 발행 (다른 서비스가 구독하여 처리)
await this.messageQueue.publish('order.created', {
orderId: order.id,
userId,
items,
totalAmount: order.total,
});
// Payment Service, Notification Service가 이 이벤트를 받아 각자 처리
return order;
// 분산 트랜잭션 필요 → Saga Pattern, 2PC 등 복잡한 처리 필요
}
}
# Docker Compose로 MSA 로컬 환경 구성
version: '3.8'
services:
api-gateway:
image: nginx
ports:
- "80:80"
depends_on:
- user-service
- order-service
user-service:
build: ./user-service
ports:
- "3001:3001"
environment:
- DB_URL=mysql://user-db:3306/users
order-service:
build: ./order-service
ports:
- "3002:3002"
environment:
- DB_URL=mysql://order-db:3306/orders
payment-service:
build: ./payment-service
ports:
- "3003:3003"
kafka:
image: confluentinc/cp-kafka
ports:
- "9092:9092"
장단점
✅ MSA 장점:
1. 독립 배포 및 확장
- 결제 서비스만 배포하고 싶으면 결제 서비스만 재배포
- 트래픽 많은 검색 서비스만 스케일아웃 가능
2. 기술 스택 자유
- 각 서비스가 최적의 언어/DB 사용 가능
- 검색: Elasticsearch, 결제: Java, 알림: Node.js
3. 장애 격리
- 알림 서비스 다운되어도 주문은 정상 처리
- 하나의 서비스 문제가 전체로 전파 안 됨
4. 팀 자율성 (Conway's Law 활용)
- 각 팀이 독립적으로 서비스 개발/배포
- 서비스 간 계약(API)만 지키면 됨
5. 점진적 기술 마이그레이션
- 레거시 서비스를 하나씩 새 기술로 교체 가능
❌ MSA 단점:
1. 분산 시스템 복잡성
- 네트워크 장애, 타임아웃, 재시도 로직 필요
- 서비스 디스커버리 (Consul, Eureka)
- 분산 트랜잭션 처리 (Saga Pattern)
2. 운영 오버헤드 증가
- 각 서비스별 모니터링, 로깅, 추적 (Distributed Tracing)
- Kubernetes, Service Mesh (Istio) 등 인프라 복잡성
- 다수의 배포 파이프라인 관리
3. 데이터 일관성 어려움
- 각 서비스가 자체 DB → 조인 불가
- 이벤트 소싱(Event Sourcing), CQRS 패턴 필요
4. 높은 초기 비용
- 인프라 설정 (K8s, 서비스 메시, API 게이트웨이)
- 팀의 분산 시스템 지식 필요
5. 테스트 복잡성
- 서비스 간 계약 테스트 (Consumer-Driven Contract Testing)
- 통합 테스트 환경 구성 어려움
아키텍처 선택 기준
초기 스타트업/소규모 팀 → 모놀리식 추천
- 빠른 개발 속도가 생존에 직결
- MSA 운영 역량 부족
- 비즈니스 도메인이 아직 불분명
성장기/중규모 팀 → 점진적 분리 고려
- 특정 기능의 독립 배포 필요성 증가
- 팀이 여러 개로 분화
- 모놀리식의 병목 명확해짐
대규모 서비스/큰 팀 → MSA 고려
- 트래픽이 특정 기능에 집중
- 팀이 10개 이상
- 배포 주기 단축이 비즈니스 요구사항
모놀리스 퍼스트 (Monolith First)
Martin Fowler의 권장 전략:
1단계: 모놀리식으로 시작
- 빠른 개발, 도메인 이해 집중
- "모듈식 모놀리스"로 내부 경계 명확히
2단계: 병목 식별 후 분리
- 성능 병목 서비스 분리
- 팀이 독립 개발 원하는 서비스 분리
- 도메인 경계가 명확한 서비스부터 분리
3단계: 전체 MSA (필요시)
- 대부분의 서비스가 독립 필요할 때
- 전담 인프라 팀 확보 후
정리
| 항목 | 모놀리식 | MSA |
|---|---|---|
| 배포 단위 | 전체 애플리케이션 | 각 서비스 독립 |
| 트랜잭션 | ACID 보장 용이 | 분산 트랜잭션 복잡 |
| 확장성 | 전체 스케일아웃 | 서비스별 스케일아웃 |
| 장애 영향 | 전체에 영향 | 격리 가능 |
| 기술 스택 | 단일 | 서비스별 자유 |
| 개발 복잡성 | 낮음 (초기) | 높음 (분산 시스템) |
| 운영 복잡성 | 낮음 | 높음 (K8s, 서비스 메시 등) |
| 적합한 규모 | 초기, 소~중규모 | 성장 후, 대규모 |
핵심: "모놀리식으로 시작해서 MSA로 진화하라 (Monolith First)". 처음부터 MSA로 시작하면 불필요한 복잡성을 초기에 감당하게 됩니다. 서비스가 성장하고 도메인이 명확해진 후 점진적으로 분리하는 것이 더 실용적입니다.