[Pattern] Microservices vs Monolith: 언제 분리, 언제 통합
Microservices, Monolith, Modular monolith, Service boundary, Conway's Law, Distributed monolith
정의
| - | Monolith | Modular Monolith | Microservices |
|---|---|---|---|
| 배포 단위 | 1개 | 1개 (모듈 명확) | N개 |
| 데이터 | 단일 DB | 단일 DB (스키마 분리) | DB per service |
| 통신 | 함수 호출 | 함수 호출 | 네트워크 (HTTP/gRPC) |
| 운영 복잡도 | 낮음 | 낮음 | 높음 |
| 스케일링 | 전체 함께 | 전체 함께 | service 별 독립 |
| 장애 격리 | 약함 | 약함 | 강함 (이상적) |
| 적합 팀 | < 30 | < 100 | 100+ |
결정 트리
flowchart TD
Q1{팀 크기}
Q1 -->|"< 10"| Mono[Monolith]
Q1 -->|"10-50"| Q2{성장 속도?}
Q2 -->|느림| Mono
Q2 -->|빠름| MM[Modular Monolith]
Q1 -->|"50-200"| MM
Q1 -->|"200+"| MS[Microservices]
MM -->|"진짜 필요해지면"| MS
시작은 Monolith (Fowler 의 “Monolith First”)
flowchart LR
Start[새 프로젝트] --> Mono["Monolith 로 시작"]
Mono --> Grow["성장 + 도메인 명확"]
Grow --> MM[Modular Monolith]
MM --> Carve["필요시 service 분리"]
Carve --> MS["Microservices 일부"]
IMPORTANT
대부분의 새 프로젝트는 monolith 로 시작 이 정답. 마이크로서비스로 시작 한 팀이 2년 후 distributed monolith 가 되는 사례가 압도적.
Microservices 가 진짜 도움될 때
flowchart TD
Q1{독립 배포 절실?}
Q1 -->|예| C1[조건 1 ✓]
Q2{팀 별 자율성 절실?}
Q2 -->|예| C2[조건 2 ✓]
Q3{스케일 차이 큼?}
Q3 -->|예| C3[조건 3 ✓]
C1 & C2 & C3 -->|모두 ✓| Go[Microservices 도입]
Distributed Monolith (안티 패턴)
flowchart LR
A[Service A] -->|동기 호출| B[Service B]
B -->|동기 호출| C[Service C]
C -->|동기 호출| D[Service D]
Note1[하나 다운 = 전체 다운]
증상:
- 여러 service 가 함께 배포 되어야 함 (calling contract 변경 시)
- 한 service 다운 = 전체 다운
- 분산의 비용 + monolith 의 결합 동시
- 코드 변경 영향이 service 경계 넘김
CAUTION
마이크로서비스의 최악의 결과. monolith 보다 모든 면에서 나쁨.
Service Boundary 가르는 기준
flowchart TD
Best[좋은 boundary]
Best --> B1["Domain (DDD bounded context)"]
Best --> B2["Team ownership (Conway's Law)"]
Best --> B3[데이터 모델 자연 분리]
Bad[나쁜 boundary]
Bad --> X1["기술 계층 (API / DB / UI)"]
Bad --> X2["유사 데이터 기준 (DB 테이블 1:1)"]
Bad --> X3[CRUD 단위 분리]
DDD Bounded Context
Domain-Driven Design 에서 Bounded Context = 독립적인 도메인 모델이 유효한 경계.
flowchart TB
subgraph Order["Order Context"]
O_Order[Order]
O_Item[OrderItem]
O_Price[Price]
end
subgraph Inventory["Inventory Context"]
I_Product[Product]
I_Stock[Stock]
end
subgraph Payment["Payment Context"]
P_Payment[Payment]
P_Invoice[Invoice]
end
Order -.->|"ProductId (ref only)"| Inventory
Order -.->|"PaymentId (ref only)"| Payment
- 각 context 안에서 같은 이름이라도 의미가 다름 (Order 의
Productvs Inventory 의Product) - Bounded Context 경계 = Microservice 분리 경계의 자연스러운 후보
- Context Map 으로 context 간 관계 (ACL, Shared Kernel, Partnership) 명시
Conway’s Law
“시스템 구조는 그것을 만든 조직의 의사소통 구조를 닮는다”
flowchart TB
Team1["Team A<br/>(독립 의사소통)"] --> Service1[Service A]
Team2["Team B<br/>(독립 의사소통)"] --> Service2[Service B]
Team3["Team C<br/>(독립 의사소통)"] --> Service3[Service C]
Team1 -.협업.-> Team2
Service1 -.API.-> Service2
팀 경계와 service 경계의 일치 가 Conway’s Law 의 실용적 활용. 한 service 가 두 팀에 걸치면 운영 사고.
Inverse Conway Maneuver
반대로 service 구조부터 정해 → 팀 구조 맞추기. 큰 조직 재편 에서 사용.
API 통신 패턴: 동기 vs 비동기
flowchart TB
Sync["동기 (HTTP / gRPC)"]
Sync --> S1[즉각 응답 필요]
Sync --> S2[결과 바로 사용]
Sync --> S3["단점: 결합, 장애 전파"]
Async["비동기 (Kafka / NATS / RabbitMQ)"]
Async --> A1[결과 나중에 처리]
Async --> A2["단점: eventual consistency"]
Async --> A3["장점: 결합 없음, 장애 격리"]
| 패턴 | 사용처 | 예시 |
|---|---|---|
| 동기 REST/gRPC | 즉각 응답, 읽기 | 상품 조회, 인증 |
| 비동기 이벤트 | 후속 처리, 팬아웃 | 결제 완료 후 알림 발송 |
| Saga | 분산 트랜잭션 | 주문 생성 + 재고 감소 + 결제 |
데이터 일관성 전략
분산 트랜잭션 (2PC) 은 마이크로서비스에서 피해야 할 패턴. 대안:
sequenceDiagram
participant O as Order Service
participant E as Event Bus
participant I as Inventory Service
participant P as Payment Service
O->>E: OrderCreated (event)
E->>I: OrderCreated
I->>I: 재고 감소
I->>E: StockReserved
E->>P: StockReserved
P->>P: 결제 처리
P->>E: PaymentCompleted
E->>O: PaymentCompleted
O->>O: 주문 확정
Choreography-based Saga: 각 서비스가 이벤트를 듣고 다음 단계를 직접 실행.
분리 비용 카탈로그
flowchart TB
Cost[Microservices 의 추가 비용]
Cost --> Net[네트워크 호출]
Cost --> Obs["관측성 (Tracing 필수)"]
Cost --> Auth["mTLS / Service Mesh"]
Cost --> Data["데이터 일관성 (Saga)"]
Cost --> Deploy["K8s, CI/CD 복잡도"]
Cost --> Test["Contract / E2E 테스트"]
Cost --> Skill[엔지니어 학습 비용]
Service Mesh 개요
마이크로서비스 간 네트워크 복잡성 을 인프라 레벨에서 해결:
| 기능 | 설명 |
|---|---|
| mTLS | service 간 자동 암호화 + 인증 |
| Load balancing | L7 수준, 헬스체크 기반 |
| Circuit breaker | 연쇄 장애 방지 |
| Observability | 자동 trace / metric 수집 |
| Traffic management | canary, retry, timeout |
대표 구현: Istio (풍부한 기능), Linkerd (경량), Cilium (eBPF 기반).
IMPORTANT
Service Mesh 는 마이크로서비스의 운영 복잡도 비용 의 일부를 낮춰주지만, 도입 자체도 학습 비용이 크다. 팀 규모와 트래픽 수준에 맞게 도입.
모듈 모놀리스 (Modular Monolith)
flowchart TB
subgraph App[Single deployable]
U[User module]
O[Order module]
P[Payment module]
end
U -.interface only.-> O
O -.interface only.-> P
App --> DB[("단일 DB<br/>스키마 분리")]
- 모듈 간 직접 호출 금지. interface만.
- 같은 DB, 다른 스키마.
- 나중에 service 분리 쉬움.
- 대부분의 회사가 결국 여기서 멈춤 (충분).
흔한 함정
WARNING
- 너무 일찍 분리 = 도메인 잘못 잡고 분리 후 재경계. 가장 큰 후회.
- DB 공유 = “Service 단위 분리” 가 DB 결합 으로 무의미. 각 service 자기 DB.
- 분산 트랜잭션 (2PC) = 마이크로서비스의 영혼. Saga 패턴으로 대체.
- shared library 의 영원한 결합 = service A 가 라이브러리 v2 로 못 올라가면 모든 service 정체.
- Bounded Context 무시 = 잘못된 경계로 분리 후 service 간 결합이 더 강해짐.
관련 위키
이 글의 용어 (6개)
- [Auth] mTLS (Mutual TLS): 양방향 인증서 인증auth-security
- 정의 mTLS (Mutual TLS) 는 서버뿐 아니라 클라이언트도 TLS 인증서로 자기 신분 증명. 일반 TLS = 서버 인증만, mTLS = 양방향. 활용: 서비스 메시 (I…
- [Distributed Systems] 분산 트랜잭션: 2PC, Saga, Outboxdistributed-systems
- 정의 분산 트랜잭션 은 여러 service / DB / 메시지 큐에 걸친 작업을 ACID 처럼 묶는 문제. 마이크로서비스 / 이벤트 기반 아키텍처의 핵심 도전. 3가지 접근: 1…
- [Pattern] API Gateway: BFF, 라우팅, auth, rate limitdistributed-systems
- 정의 API Gateway = 클라이언트와 마이크로서비스 사이의 단일 진입점. 라우팅, auth, rate limit, transformation, monitoring 의 cro…
- [Pattern] CQRS: Command / Query 분리, read modeldistributed-systems
- 정의 CQRS (Command Query Responsibility Segregation) = 쓰기 모델 과 읽기 모델 을 분리. 같은 데이터를 다른 형태 로 저장 / 조회. […
- [Pattern] Event Sourcing: 상태 대신 이벤트 누적distributed-systems
- 정의 Event Sourcing = 현재 상태 대신 과거 이벤트 시퀀스 를 저장. 현재 상태 = event 모두 replay 한 결과. 핵심 약속 - 과거 모든 변화 기록. 감사…
- [Pattern] Saga: 분산 트랜잭션의 보상 흐름distributed-systems
- 정의 Saga = 여러 service 의 로컬 트랜잭션 을 연결한 긴 흐름. 한 단계 실패 시 이전 단계의 보상 (compensating) 트랜잭션 으로 논리적 롤백. [!IMP…
💬 댓글