inblog logo
|
DailyCoding
    MSA

    3. MSA에서 트랜잭션이 어려운 이유

    박은서's avatar
    박은서
    May 01, 2026
    3. MSA에서 트랜잭션이 어려운 이유
    Contents
    MSA에서 트랜잭션이 어려운 이유1️⃣ 트랜잭션2️⃣ 모놀리식에서 트랜잭션이 쉬운 이유3️⃣ MSA에서 어려운 이유4️⃣ 핵심 문제: 분산 트랜잭션(Distributed Transaction)5️⃣ 대표적인 문제 상황6️⃣ 왜 2PC를 잘 안 쓰는가?7️⃣ MSA에서 해결 방법8️⃣ 상태 설계9️⃣ 한 줄로 이해하기🔟 시험/면접용 핵심 정리

    MSA에서 트랜잭션이 어려운 이유

    1️⃣ 트랜잭션

    1) 트랜잭션이란?

    • 여러 작업을 하나로 묶어서 처리하는 것
    • 전부 성공해야 하고, 중간에 하나라도 실패하면 전부 원래대로 되돌려야 함

    2) 예시

    • 주문 생성
    • 결제 승인
    • 재고 차감
    ➡️ 이 3개가 하나의 작업처럼 움직여야 함
    • 3개 다 성공 → 최종 성공
    • 중간에 하나 실패 → 전부 취소

    2️⃣ 모놀리식에서 트랜잭션이 쉬운 이유

    1) 하나의 애플리케이션 + 하나의 DB인 경우가 많음

    • 같은 DB 안에서 처리 가능
    • DB 트랜잭션으로 묶기 쉬움
    • 예시
      • @Transactional public void order() { orderRepository.save(order); paymentRepository.save(payment); stockRepository.decrease(itemId, qty); }
        ➡️ 중간에 예외 발생하면 DB가 rollback 해줌
        즉, 하나의 DB 트랜잭션으로 끝남

    3️⃣ MSA에서 어려운 이유

    1) 서비스가 나뉘어 있음

    • 따로 존재하는 서비스
      • 주문 서비스
      • 결제 서비스
      • 재고 서비스
    • DB도 따로 가짐
      • 주문 DB
      • 결제 DB
      • 재고 DB
    즉, 더 이상 하나의 @Transactional로 전체를 묶을 수 없음

    2) 네트워크를 거쳐야 함

    • 모놀리식은 메서드 호출이지만, MSA는 서비스끼리 HTTP, gRPC, 메시지 큐 등으로 통신함
      • ➡️ 문제 발생
      • 주문 서비스는 성공
      • 결제 서비스 호출 중 네트워크 오류
      • 재고 서비스는 아직 실행 안 됨
    ➡️ 이렇게 되면 중간 상태가 생김

    3) 각 서비스의 로컬 트랜잭션은 성공했는데 전체는 실패할 수 있음

    1. 주문 서비스 → 주문 생성 성공
    1. 결제 서비스 → 결제 실패
    1. 재고 서비스 → 실행 안 됨
    ➡️ 현재 상태
    • 주문은 생김
    • 결제는 안 됨
    • 재고는 그대로
    즉, 사용자 입장에서는 이상한 데이터가 됨

    4️⃣ 핵심 문제: 분산 트랜잭션(Distributed Transaction)

    1) 분산 트랜잭션이란?

    • 여러 서비스, 여러 DB에 걸친 트랜잭션(주문 DB, 결제 DB, 재고 DB 등)을 하나의 트랜잭션처럼 다루고 싶은 것

    2) 어려운 이유

    • 각 서비스가 서로 다른 서버, DB, 트랜잭션 경계를 가지고 있기 때문
    • DB 하나 안에서는 rollback이 쉽지만,
      • 서비스가 나뉘면 이미 다른 서비스에서 commit된 작업을 한 번에 되돌리기 어려움

    5️⃣ 대표적인 문제 상황

    1) 부분 성공 문제

    • 상황
      • 주문은 저장됐는데 결제는 실패
    • 결과
      • 주문만 남음
      • 사용자는 주문이 된 건지 안 된 건지 헷갈림

    2) 데이터 불일치 문제

    • 상황
      • 결제는 완료됐는데 재고 차감은 실패
    • 결과
      • 돈은 받았음
      • 재고는 그대로
      • 나중에 재고 수량이 꼬일 수 있음

    3) 중복 처리 문제

    • 상황
      • 결제 요청을 보냈는데 응답이 안 옴
      • 그래서 재시도했더니 결제가 두 번 됨
    • 결과
      • 중복 결제
      • 중복 주문
      • 중복 이벤트 처리

    4) 롤백 불가 문제

    • 상황
      • 이미 다른 서비스가 commit한 작업은 내 서비스에서 rollback()처럼 쉽게 되돌릴 수 없음
    • 예시
      • 결제 서비스가 카드 승인 완료
      • 그 뒤 주문 서비스 실패
      • ➡️ 이때 단순 rollback이 아니라 결제 취소라는 별도 작업이 필요함
        즉, MSA에서는 rollback 대신 보상 작업이 자주 필요함

    6️⃣ 왜 2PC를 잘 안 쓰는가?

    1) 2PC(2-Phase Commit)란?

    • 여러 시스템에 “준비됐니?”, “그럼 다 같이 commit 하자”하고 맞추는 방식

    2) 이론적으로는 가능하지만 실무에서는 잘 안 씀

    • 성능 부담 큼
    • coordinator 장애 시 복잡
    • 락이 오래 걸릴 수 있음
    • MSA의 느슨한 결합과 잘 안 맞음
    • 클라우드/NoSQL/메시지 브로커 환경과도 잘 안 맞는 경우 많음
    ➡️ “전통적인 DB식 강한 트랜잭션”을 MSA 전체에 그대로 적용하기 어렵다

    7️⃣ MSA에서 해결 방법

    🎯 해결 방식의 핵심 방향

    ① 강한 일관성보다 최종 일관성(Eventual Consistency)을 많이 사용
    • 지금 당장 모든 서비스 데이터가 완벽히 똑같을 필요는 없고 조금 시간이 지나면 맞춰지도록 설계
    • 순간적으로는 주문 완료 / 결제 대기 상태일 수 있지만 잠시 후 결제 성공 이벤트가 오면 최종 완료
    ② 하나의 큰 트랜잭션 대신 각 서비스는 자기 DB에서만 로컬 트랜잭션 처리
    • 서비스 간에는 이벤트/보상 처리로 연결

    1) 대표 해결 방법 1: Saga 패턴

    ① Saga란?
    • 하나의 큰 트랜잭션 대신 여러 개의 작은 로컬 트랜잭션으로 나누고,
      • 실패하면 이전 작업들을 보상 트랜잭션으로 취소하는 방식
    ② 예시 흐름
    1. 주문 서비스 → 주문 생성
    1. 결제 서비스 → 결제 승인
    1. 재고 서비스 → 재고 차감
    ➡️ 그런데 3번에서 실패하면 2번, 1번을 되돌림
    1. 결제 취소 (2번)
    1. 주문 취소 (1번)
    ③ 중요한 점
    • 여기서 “되돌림”은 DB rollback이 아니라 비즈니스적으로 반대 작업을 수행하는 것
    • 예
      • 주문 생성 ↔ 주문 취소
      • 결제 승인 ↔ 결제 취소
      • 재고 차감 ↔ 재고 복구
    ④ 장단점
    • 장점
      • MSA 구조에 잘 맞음
      • 서비스 독립성 유지 가능
    • 단점
      • 설계가 복잡함
      • 보상 로직을 직접 만들어야 함
      • 모든 작업이 완벽히 되돌릴 수 있는 건 아님
    ⑤ Saga의 두 가지 방식
    1. Choreography 방식
        • 중앙 관리자 없이 서비스들이 이벤트를 보고 각자 다음 행동 수행
        • 예
          • 주문 생성됨 이벤트 발행
          • 결제 서비스가 듣고 결제 수행
          • 결제 완료 이벤트 발행
          • 재고 서비스가 듣고 재고 차감
        • 장점
          • 중앙 제어가 없어 느슨한 결합
        • 단점
          • 흐름 추적 어려움
          • 서비스가 많아지면 복잡해짐
    1. Orchestration 방식
        • 중앙 오케스트레이터가 흐름 제어
        • 예
          • 오케스트레이터가 주문 서비스 호출
          • 성공하면 결제 서비스 호출
          • 성공하면 재고 서비스 호출
          • 실패하면 보상 작업 호출
        • 장점
          • 흐름이 명확함
          • 관리 쉬움
        • 단점
          • 중앙 제어 컴포넌트 필요
          • 오케스트레이터 의존성 생김

    2) 대표 해결 방법 2: Outbox 패턴

    ① 필요한 이유
    • 자주 생기는 문제
      • DB 저장은 성공
      • 그런데 이벤트 발행은 실패
    • 예
      • 주문은 DB에 저장됨
      • 그런데 OrderCreated 이벤트를 메시지 브로커에 못 보냄
      • ➡️ 그러면 다른 서비스는 주문이 생긴 걸 모름
    ② 해결 아이디어
    • 주문 저장과 이벤트 저장을 같은 로컬 DB 트랜잭션으로 처리
    • 예
      • orders 테이블 저장
      • outbox 테이블에 이벤트 저장
      • ➡️ 둘 다 한 트랜잭션으로 commit
        ➡️ 그 다음 별도 프로세스가 outbox를 읽어서 메시지 브로커로 발행
    ③ 장점
    • DB 저장과 이벤트 유실 문제를 줄일 수 있음
    • Saga와 같이 자주 사용됨

    3) 대표 해결 방법 3: Idempotency(멱등성)

    ① 중요한 이유
    • MSA에서는 네트워크 문제 때문에 재시도가 자주 발생함
      • 응답이 안 와서 다시 요청
      • 메시지가 중복 전달
      • 소비자가 같은 이벤트를 두 번 처리
    ② 멱등성이란?
    • 같은 요청을 여러 번 처리해도 결과가 한 번 처리한 것과 같아야 함
    • 예
      • 같은 결제 요청 ID로 두 번 요청해도 결제는 한 번만 처리
      • 같은 주문 취소 이벤트를 두 번 받아도 한 번만 취소
    ③ 방법
    • 요청 ID 저장
    • 이벤트 ID 저장
    • 중복 처리 여부 체크

    8️⃣ 상태 설계

    1) 상태를 설계해야 하는 이유

    ① 성공/실패만 두면 부족함
    • MSA에서는 중간 상태가 필요함
    • 예
      • ORDER_CREATED
      • PAYMENT_PENDING
      • PAYMENT_COMPLETED
      • STOCK_RESERVED
      • COMPLETED
      • FAILED
      • CANCELED
      • ➡️ 이렇게 상태를 세분화해야 함
    • 이유
      • 모든 작업이 즉시 끝나지 않기 때문
      • 지금은 결제 대기 중일 수 있고 잠시 뒤 최종 완료될 수 있음
      • ➡️ 그래서 사용자에게도 “주문 완료”가 아니라 “주문 접수됨 / 결제 확인 중” 같은 상태를 보여줘야 할 때가 많음

    2) Spring 기준으로 이해하기

    ① @Transactional의 범위
    • @Transactional은 내 서비스의 로컬 DB 작업까지만 안전하게 묶어줌
    • 예
      • @Transactional public void createOrder() { orderRepository.save(order); // 여기까지는 로컬 트랜잭션 paymentClient.requestPayment(...); // 다른 서비스 호출 }
      • 여기서 paymentClient.requestPayment(...)는 다른 서비스 호출이므로 같은 DB 트랜잭션처럼 묶이지 않음
      • ➡️ 내 DB 저장, 다른 서비스 처리를 하나의 ACID 트랜잭션으로 보장할 수 없음
    ② 그래서 보통 이렇게 감
    • 내 DB 저장
    • 이벤트 발행 or 다음 서비스 호출
    • 실패 시 보상 처리
    • 상태값 관리

    3) 주문 예시로 전체 흐름 보기

    ⭕ 정상 흐름
    1. 주문 서비스: 주문 생성 (PENDING)
    1. 주문 서비스: OrderCreated 이벤트 발행
    1. 결제 서비스: 결제 성공
    1. 결제 서비스: PaymentCompleted 이벤트 발행
    1. 재고 서비스: 재고 차감 성공
    1. 재고 서비스: StockReserved 이벤트 발행
    1. 주문 서비스: 주문 상태 COMPLETED
    ❌ 실패 흐름
    1. 주문 서비스: 주문 생성 (PENDING)
    1. 결제 서비스: 결제 성공
    1. 재고 서비스: 재고 차감 실패
    1. 결제 서비스: 결제 취소
    1. 주문 서비스: 주문 취소 상태 변경

    9️⃣ 한 줄로 이해하기

    모놀리식은 하나의 DB 안에서 rollback으로 되돌리기 쉽지만,
    MSA는 서비스별로 DB와 트랜잭션이 분리되어 있어서 중간 실패가 생기면 단순 rollback이 안 되고,
    대신 보상 트랜잭션, 상태 관리, 이벤트 기반 처리, 멱등성 보장으로 문제를 해결해야 한다.

    🔟 시험/면접용 핵심 정리

    1) 왜 문제인가?

    • 서비스마다 DB와 트랜잭션 경계가 다름
    • 하나의 ACID 트랜잭션으로 묶기 어려움

    2) 무슨 문제가 생기나?

    • 부분 성공
    • 데이터 불일치
    • 중복 처리
    • 롤백 어려움

    3) 어떻게 해결하나?

    • Saga 패턴
    • 보상 트랜잭션
    • Outbox 패턴
    • 멱등성 보장
    • 최종 일관성 수용
    • 상태값 세분화
    Share article
    Contents
    MSA에서 트랜잭션이 어려운 이유1️⃣ 트랜잭션2️⃣ 모놀리식에서 트랜잭션이 쉬운 이유3️⃣ MSA에서 어려운 이유4️⃣ 핵심 문제: 분산 트랜잭션(Distributed Transaction)5️⃣ 대표적인 문제 상황6️⃣ 왜 2PC를 잘 안 쓰는가?7️⃣ MSA에서 해결 방법8️⃣ 상태 설계9️⃣ 한 줄로 이해하기🔟 시험/면접용 핵심 정리

    DailyCoding

    RSS·Powered by Inblog