Post

05 데이터 정합성 - Saga Pattern

05 데이터 정합성 - Saga Pattern

1. Note

  • 결론은 정합성을 맞추다가 문제가 생기면 보상트랜잭션으로 해결함.
    • 근데 보상트랜잭션은 개발자가 직접 만든거라 검토가 많이 필요
    • 예외는 어떤 방식으로든 전달해야하는데,
    • 로직들이 흘러가는 구조에 따라서 방법이 다름.

2. Saga Pattern

1. Saga 패턴 (★MSA에서 주로 사용★)

  • Saga Pattern은 MSA 환경에서 서비스별 로컬 트랜잭션을 사용하면서 데이터 정합성을 유지하는 패턴
    • 각 서비스가 자신의 트랜잭션을 처리한 뒤 다음 서비스로 작업을 전달
    • 중간에 실패가 발생하면 이전 작업을 보상 트랜잭션(Compensation Transaction)으로 취소
  • 2PC처럼 전역 트랜잭션을 사용하지 않아 확장성과 서비스 독립성에 유리
  • MSA 환경에서 가장 많이 사용되는 데이터 정합성 패턴 중 하나

2. saga패턴의 중요성

  • MSA는 Database-per-Service 구조를 사용하며, 각 서비스가 독립적인 데이터베이스를 가짐
  • 기존처럼 단일 트랜잭션(ACID)으로 처리할 수 없음
    1
    2
    3
    
    Order Service → 주문 생성      # 트랜잭션1
    Payment Service → 결제 처리    # 트랜잭션2
    Inventory Service → 재고 차감  # 트랜잭션3
    
  • 비교하면
    • 기존 모놀리식 환경에서는 rollback으로 해결할 수 있었지만,
    • MSA에서는 서비스별 DB가 분리되어 있어 전체 rollback이 불가함.
  • 따라서
    • 각 서비스를 독립적인 Local Transaction으로 처리하고,
    • 실패 시 보상 트랜잭션(Compensation Transaction)을 통해 정합성을 유지

3. 보상 트랜잭션 (Compensation Transaction)

  • 이미 완료된 작업을 비즈니스 로직을 통해 취소하는 방식
  • 보상 트랜잭션은 “완벽한 원상복구”가 아닐 수 있음.
  • 소스 로직상에서 try/catch로 실패할 경우 필요한 작업들을 명시하는 방법
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    try {
      ...
      // 외부 API와 연동되는 도메인 작업
      paymentService.pay(orderId);
      inventoryService.decrease(); 
      
    } catch (Exception e) {
      // 보상 트랜잭션 역할
      paymentService.refund(orderId); // 결제를 취소, <별도 구현> 
      orderService.cancel(orderId); // 주문도 취소, <별도 구현>
    }  
    

3. 동작흐름

1. Saga Pattern 동작 흐름

  • 하나의 큰 트랜잭션을 여러 개의 Local Transaction으로 분리하여 순차적으로 처리
  • 각 서비스는 자신의 작업을 독립적으로 수행하고 commit 하며, 모든 과정이 성공하면 트랜잭션이 완료
  • 중간 서비스에서 실패한 경우 Compensation Transaction 작동

    1
    2
    3
    4
    
    1. Order Service # 주문 생성 -> 트랜잭션1 -> 커밋(local Transaction)
    2. Payment Service # 결제 처리 -> 트랜잭션2 -> 커밋(local Transaction)
    3. Inventory Service # 재고 차감 -> 트랜잭션3 -> 커밋(local Transaction)
    4. 주문 완료
    

2. Rollback / 보상트랜잭션

구분Rollback(Local Transaction)Compensation Transaction
처리 방식DB가 자동 복구비즈니스 로직으로 취소
범위하나의 트랜잭션여러 서비스
사용 환경모놀리식MSA
처리 시점즉시 복구이미 commit 후 취소

3. 실행 구조

  • Local Transaction 실행
    • 각 서비스가 자신의 작업을 독립적으로 수행
    • 여기서 실패할 경우 내부에서는 롤백, 외부에서는 보상 트랜잭션
    1
    2
    3
    
    주문 생성
    → 결제 처리
    → 재고 차감
    
  • Compensation Transaction 실행
    • 실패 시 이전 작업을 취소
    • 혼자서 발생하게 되면 내부 롤백, 다른 곳에서 발생하면 보상트랜잭션이 작동

      1
      2
      3
      4
      5
      
      재고 실패
      ↓
      결제 취소
      ↓
      주문 취소
      

4. Saga 구현 방식

1. Choreography

1. Choreography 방식

  • 중앙 제어자 없이 각 서비스가 이벤트를 기반으로 작업을 수행하는 방식
  • 각 서비스는 특정 이벤트를 구독하고, 이벤트 발생 시 자신의 작업을 수행한 후 다음 이벤트를 발행

2. 흐름

  • 성공

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    1. Order Service
       주문 생성
       → OrderCreated 이벤트 발행
      
    2. Payment Service
       OrderCreated 수신
       → 결제 처리
       → PaymentCompleted 이벤트 발행
      
    3. Inventory Service
       PaymentCompleted 수신
       → 재고 차감
    
  • 실패

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    Order Service(주문 생성)
    ↓
    OrderCreated Event (성공이벤트 발행)
      
    Payment Service(결제 성공)
    ↓
    PaymentCompleted Event (성공이벤트 발행)
      
    Inventory Service(재고 차감 실패 - 롤백)
    ↓
    InventoryFailed Event 발행
      
    # InventoryFailed Event를 보고 Pament Service가 보상 트랜잭션 + PaymentCanceled Event 발행
    # Order Service는 PaymentCanceled Event보고 보상 트랜잭션 작동
    # 각 서비스들은 여러 이벤트를 바라보고 있을 수 있음!
    

3. 장단점

구분내용
장점서비스 간 결합도가 낮음
확장성이 높음
Event-Driven Architecture와 자연스럽게 연결됨
Kafka 같은 메시지 브로커 활용에 적합
단점서비스가 많아질수록 이벤트 흐름 파악이 어려움
이벤트 의존성 증가로 복잡도 상승
장애 추적 및 디버깅이 어려움

2. Orchestration

1. Orchestration 방식

  • 중앙 조정자(Orchestrator)가 전체 트랜잭션 흐름을 제어하는 방식
  • 독립적으로 작업을 수행하지만, 다음 작업을 누가 실행할지 Orchestrator가 결정
  • 이벤트를 사용하지 않으면 거의 Orchestrator 방식으로 잡히게됨.(☆)

2. 흐름

  • 성공

    1
    2
    3
    4
    5
    
    Saga Orchestrator
    │
    ├─ Order Service → 주문 생성
    ├─ Payment Service → 결제 요청
    └─ Inventory Service → 재고 차감 요청
    
  • 실패

    • 흐름

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
      Client
      ↓
      Order API (진입점)
      ↓
      Order 생성
      ↓
      Saga 시작
      ↓
      Orchestrator가 흐름 관리 (★)
       ├─ Payment Service 호출
       ├─ Inventory Service 호출
       └─ ...
      
    • 소스

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      
      ...
      try {
         orderId = orderService.createOrder(request);
         paymentId = paymentService.pay(orderId);
         inventoryService.decreaseStock(orderId);
      } catch (Exception e) {
               
         // 보상 트랜잭션 실행
         compensate(orderId, paymentId); 
         
         throw new RuntimeException("Saga Failed", e);
      }
          
          
      private void compensate(Long orderId, Long paymentId) {
      
        // 결제 취소
        if (paymentId != null) {
           paymentService.cancel(paymentId);
        }
      
        // 주문 취소
        if (orderId != null) {
          orderService.cancel(orderId);
        }
      }    
      

3. 장단점

구분내용
장점트랜잭션 흐름이 명확함
모니터링 및 디버깅이 쉬움
복잡한 비즈니스 로직 관리에 유리
단점중앙 Orchestrator 의존성 증가
Coordinator 장애 시 영향 범위가 커질 수 있음
상대적으로 서비스 간 결합도가 높아질 수 있음

5. Saga Pattern 한계와 고려사항

1. Eventually Consistency (최종 일관성)

  • Saga Pattern은 즉시 일관성(Strong Consistency)을 보장하지 않음

    1
    2
    3
    4
    5
    6
    
    주문 생성 완료 Order -> commit
    → 결제 진행 중 Payment -> 진행중
    → 재고 처리 중 Inventory -> 진행전
      
    # 이 타이밍에는 DB 3개의 데이터가 다른 상황.
    # 중간에 조회하게 되면 달라 질 수 있음.
    

2. 보상 트랜잭션의 한계

  • Compensation Transaction은 DB rollback이 X
    • 롤백과 같은 기능을 하도록 구현한 방법
    • 모든 작업이 완벽하게 복구되지는 않을 수 있음
  • 특정 작업에 따라 보상 트랜잭션이 불가할 수 있음

    1
    2
    3
    4
    5
    
    결제 성공
    ↓
    SMS 발송 완료 (복구 불가)
    ↓
    재고 실패
    

3. 멱등성 (Idempotency)

  • 분산 환경에서는 네트워크 오류나 재시도로 인해 동일 요청이 여러 번 실행될 수 있음
  • 동일 요청이 여러 번 실행되어도 결과가 동일해야 함
  • 중복 실행 방지 로직이 필요

    1
    2
    3
    4
    
    결제 취소 API 호출
      
    # 결제 취소 APi 실행후 응답을 받던 중 네트워크 오류 발생
    # 첫 번째 취소는 성공했지만 오류 발생으로 재요청하는 상황 발생  
    

4. 재시도(Retry) 처리

  • 일시적인 장애는 재시도로 복구 가능한 경우가 많음
  • 바로 실패 처리보다는 시간을 두고 몇회 시도후 보상트랜잭션을 실행필요

    1
    2
    3
    4
    5
    6
    7
    
    # 일시적인 타임 아웃 발생
    Inventory Service Timeout 
      
    Retry 재요청1 -> 실패 
    Retry 재요청2 -> 실패 
    Retry 재요청3 -> 실패 
    보상 트랜잭션 요청
    

5.장애 추적 및 디버깅 어려움

  • Saga는 여러 서비스에 걸쳐 동작하여 문제의 원인을 찾기 어려움
  • 어디서 실패했는지, 누가 어떤 상태인지, 어떤 보상 작업이 실행됐는지 추적 필요함.

    1
    2
    3
    
    Correlation ID       # 하나의 요청 흐름을 식별하기 위한 고유 ID
    Distributed Tracing  # 서비스 호출 흐름 + 실행 시간까지 추적
    Centralized Logging  # 여러 서버 로그를 한곳에 모으는 것
    
This post is licensed under CC BY 4.0 by the author.