마이크로서비스 아키텍처(MSA) 핵심 완전 정복: 모놀리스 전환부터 분산 트랜잭션·Saga 패턴까지

현대 백엔드 엔지니어링의 정점, 마이크로서비스(MSA)의 명과 암! 모놀리스와 비교한 구조적 본질, DDD 바운디드 컨텍스트 서비스 분리 기준, API Gateway와 Service Discovery, 서킷 브레이커, 분산 트랜잭션을 해결하는 Saga 및 Transactional Outbox 패턴을 집대성합니다.
IT·과학
저자

Ikmyungterran

공개

2026년 9월 8일

Modified

2026년 9월 8일

0. 환상에서 현실로: MSA는 은총알(Silver Bullet)이 아니다

모든 스타트업과 개발팀이 한때 “우리도 넷플릭스나 쿠팡처럼 MSA로 전면 전환하자!”며 마이크로서비스 열풍에 뛰어들었습니다. 하지만 수많은 팀이 다음과 같은 참혹한 현실을 마주하고 모놀리스로 회귀했습니다:

  • “단순히 회원 이름 하나 바꾸는데 서비스 5개를 거쳐 네트워크 통신을 해야 한다.”
  • “결제는 성공했는데 주문 서버가 다운되어 결제 취소가 안 된다(분산 트랜잭션 파탄).”
  • “에러가 터졌는데 어느 서버에서 시작된 건지 로그를 찾을 수가 없다.”

마이크로서비스 아키텍처(MSA)는 단순히 “프로그램을 여러 개의 작은 서버로 쪼개는 것”이 아닙니다.

그 본질은 단일 프로세스 안에서 누렸던 초고속 인메모리 함수 호출, 강력한 DB SQL JOIN, 완벽한 ACID 트랜잭션을 포기하는 대가로, 수백 명의 개발자가 독립적으로 배포할 수 있는 조직적 확장성을 얻는 거대한 패러다임의 전환입니다.

graph LR
    subgraph Monolith["모놀리스 (단일 프로세스)"]
        M1["주문 로직"] -->|인메모리 함수 호출: 1나노초| M2["결제 로직"]
        M1 --- M_DB[단일 DB: ACID 트랜잭션 100% 보장]
    end
    subgraph MSA["마이크로서비스 (분산 시스템)"]
        S1["주문 서비스"] -->|원격 네트워크 호출: 20밀리초 지연 & 실패 가능!| S2["결제 서비스"]
        S1 --- S1_DB[주문 DB]
        S2 --- S2_DB[결제 DB]
    end
    style Monolith fill:#DCFCE7,stroke:#16A34A,stroke-width:2px;
    style MSA fill:#FEE2E2,stroke:#DC2626,stroke-width:2px;


1. 서비스를 어디서 나눌 것인가? DDD와 콘웨이의 법칙

MSA를 도입할 때 가장 먼저 부딪히는 벽은 “도대체 시스템을 몇 개로, 어떤 기준으로 쪼개야 하는가?”입니다.

1-1. DDD (도메인 주도 설계)의 바운디드 컨텍스트 (Bounded Context)

기술적 레이어(컨트롤러 서비스 DB)가 아니라, 비즈니스 용어와 책임의 경계에 따라 서비스를 나누어야 합니다. * 예를 들어 같은 ’사용자(User)’라도, - 인증 서비스에서는 아이디, 비밀번호, 권한이 핵심입니다. - 배송 서비스에서는 수령인 이름, 도로명 주소, 공동현관 비밀번호가 핵심입니다. - 정산 서비스에서는 사업자등록번호, 정산 계좌번호, 세금계산서 정보가 핵심입니다. * 하나의 거대한 ’사용자 테이블’에 100개 컬럼을 욱여넣지 않고, 각 도메인 영역(Bounded Context)마다 독립된 데이터 모델을 갖도록 쪼개는 것이 정석입니다.

1-2. 콘웨이의 법칙 (Conway’s Law)

“시스템을 설계하는 조직은 그 조직의 소통 구조를 닮은 아키텍처를 만들어낸다.”

10명짜리 스타트업 개발팀이 MSA를 도입하면 통신 비용만 늘어나 망합니다. MSA는 각 서비스를 온전히 책임지고 독립적으로 기획-개발-배포-운영할 수 있는 자율적인 팀 단위(아마존의 ‘피자 2판 팀’)가 갖추어졌을 때 비로소 빛을 발합니다.


2. 분산 서비스의 척추: API Gateway와 Service Discovery

수십 개의 마이크로서비스가 떠 있을 때, 스마트폰 앱 클라이언트가 각 서비스의 IP 주소를 일일이 알고 통신할 수는 없습니다.

flowchart TD
    Client["모바일 앱 / 웹 브라우저"] --> Gateway["API Gateway (인증, 라우팅, Rate Limiting)"]
    Gateway --> Discovery["Service Discovery (Consul / Eureka)"]
    Discovery -.->|실시간 건강한 서버 IP 목록 반환| Gateway
    Gateway --> S_Order["주문 서비스 인스턴스 A, B"]
    Gateway --> S_Pay["결제 서비스 인스턴스 A, B"]
    Gateway --> S_Delivery["배송 서비스 인스턴스 A, B"]
    style Client fill:#E0F2FE,stroke:#0284C7,stroke-width:1px;
    style Gateway fill:#F3E8FF,stroke:#9333EA,stroke-width:2px;
    style Discovery fill:#FEF3C7,stroke:#D97706,stroke-width:2px;
    style S_Order fill:#DCFCE7,stroke:#16A34A,stroke-width:1px;
    style S_Pay fill:#DCFCE7,stroke:#16A34A,stroke-width:1px;
    style S_Delivery fill:#DCFCE7,stroke:#16A34A,stroke-width:1px;

  • API Gateway: 시스템의 단일 진입로(Front Door). 사용자 인증(JWT 토큰 검증), SSL 암호화 해제, DDoS 공격 방어, 트래픽 유량 제어(Rate Limiting)를 도맡아 처리합니다.
  • Service Discovery: 클라우드 환경에서는 컨테이너가 죽고 새로 뜨면서 IP 주소가 시시각각 변합니다. 서비스가 켜질 때 자신의 IP를 등록하고, 게이트웨이는 디스커버리에 물어보고 살아있는 인스턴스로 트래픽을 보냅니다.

3. 연쇄 장애 차단기: 서킷 브레이커 (Circuit Breaker)

가정집 두꺼비집(누전 차단기)을 떠올려 보십시오. 배선에 누전이 발생하면 즉시 전기를 차단해 온 집안이 불타는 것을 막습니다.

소프트웨어에서도 마찬가지입니다. 외부 결제 대행사(PG) 서버가 마비되어 응답이 30초씩 지연되면, 결제 서비스 스레드가 꽉 차고, 이어서 주문 서비스 스레드가 고갈되며, 결국 전체 웹사이트가 다운되는 ‘연쇄 장애(Cascading Failure)’가 발생합니다.

stateDiagram-v2
    [*] --> Closed
    Closed --> Open: 실패율 50% 초과 (회로 차단)
    Open --> HalfOpen: 대기 시간 30초 경과 후 시험 가동
    HalfOpen --> Closed: 시험 호출 성공 (정상 복구)
    HalfOpen --> Open: 시험 호출 실패 (재차단)
    
    note right of Open: 외부 요청을 즉시 거절하고<br>미리 준비된 Fallback 응답 반환!

  • Closed (정상): 모든 트래픽을 통과시킵니다.
  • Open (차단): 최근 100건 중 에러율이 50%를 넘으면 즉시 회로를 엽니다. 이제 서버를 호출하지도 않고 0.001초 만에 “죄송합니다. 잠시 후 다시 시도해 주세요”라는 대체 응답(Fallback)을 돌려줍니다.
  • Half-Open (복구 시험): 일정 시간이 지난 뒤 몇 개의 트래픽만 살짝 흘려보아 서비스가 정상화되었는지 확인합니다.

4. 분산 트랜잭션의 구원투수: Saga 패턴과 Transactional Outbox

MSA에서는 “Database-per-Service(서비스별 독립 DB 보유)”가 철칙입니다. 서로 다른 물리 DB 간에는 RDBMS의 BEGIN ... COMMIT 트랜잭션을 걸 수 없습니다.

4-1. Saga 패턴: 보상 트랜잭션(Compensating Transaction)

각 로컬 트랜잭션이 성공하면 이벤트를 발행해 다음 서비스를 실행하고, 중간에 실패하면 지금까지 실행된 작업들을 거꾸로 취소하는 ‘보상 트랜잭션’을 순차 실행합니다.

sequenceDiagram
    autonumber
    participant Order as 주문 서비스 (DB1)
    participant Pay as 결제 서비스 (DB2)
    participant Stock as 재고 서비스 (DB3)

    Order->>Order: 1. 주문서 생성 (상태: PENDING)
    Order->>Pay: 2. 결제 요청 ($50,000)
    Pay->>Pay: 3. 카드 승인 성공!
    Pay->>Stock: 4. 재고 1개 차감 요청
    alt 재고 부족으로 차감 실패!
        Stock-->>Pay: 5. "재고가 0개입니다! 실패!"
        Note over Pay: [보상 트랜잭션 실행!]
        Pay->>Pay: 6. 앞서 승인했던 카드 결제 자동 취소(Refund)!
        Pay-->>Order: 7. 주문 취소 알림
        Order->>Order: 8. 주문서 상태를 CANCELLED로 변경
    end

4-2. 트랜잭셔널 아웃박스 패턴 (Transactional Outbox Pattern)

“DB에 데이터를 INSERT하는 작업과 카프카(Kafka)에 이벤트를 쏘는 작업”을 어떻게 한 몸으로 묶을 수 있을까요? 네트워크 장애로 DB 저장은 성공했는데 카프카 전송이 실패하면 데이터 정합성이 깨집니다.

  • 해법: 서비스 DB 안에 outbox라는 임시 메시지 테이블을 만듭니다. 비즈니스 데이터와 아웃박스 이벤트를 동일한 로컬 DB 트랜잭션 안에서 원자적(ACID)으로 저장합니다. 이후 백그라운드 프로세스(CDC: Debezium)가 아웃박스 테이블을 읽어 카프카로 안전하게 발행합니다.

5. MSA 도입 결정 자가 진단 체크리스트

다음 5가지 질문 중 4개 이상 “YES”가 아니라면, 지금은 모놀리스를 유지하는 것이 훨씬 현명한 선택입니다:


📚 참고 문헌 및 공식 출처

구분 자료명 주요 내용 링크
MSA 바이블 Building Microservices 2nd Ed. (Sam Newman 저) 서비스 모델링, 통합 방식, 분산 트랜잭션, 점진적 전환 가이드 O’Reilly Media
패턴 집대성 Microservices Patterns (Chris Richardson 저) Saga 패턴, Transactional Outbox, CQRS 실전 구현 총정리 microservices.io
회복탄력성 라이브러리 Resilience4j Official Docs Java/Spring 생태계 서킷 브레이커, Rate Limiter 구현체 Resilience4j

함께 읽으면 좋은 글