IT개발

분산 트랜잭션과 Saga 패턴 실전 예제

우리모두 개발자되기 2025. 4. 20. 23:22

 

분산 트랜잭션과 Saga 패턴 실전 예제: 마이크로서비스에서 데이터 일관성을 유지하는 방법

마이크로서비스 아키텍처에서는 하나의 서비스가 여러 DB나 시스템과 독립적으로 동작합니다.

이는 서비스 간 결합도를 낮추고 확장성을 높여주지만, 동시에 트랜잭션 관리라는 새로운 과제를 안겨줍니다.

예전처럼 하나의 RDBMS 안에서 BEGIN – COMMIT으로 처리되는 ACID 트랜잭션은 마이크로서비스 환경에서는 적용하기 어렵기 때문입니다.

 

이 문제를 해결하기 위한 대표적인 전략 중 하나가 바로 Saga 패턴입니다.

이 글에서는 분산 트랜잭션의 개념과 문제점, 그리고 Saga 패턴의 적용 방식과 실전 예제까지 자세히 설명드리겠습니다.

 

1. 분산 트랜잭션이란?

분산 트랜잭션은 **둘 이상의 독립된 자원(DB, 메시지 브로커 등)**에 걸쳐 하나의 논리적 작업 단위를 구성하는 트랜잭션을 의미합니다. 예를 들어:

  • 주문 서비스가 주문 데이터를 MySQL에 저장
  • 결제 서비스가 결제 데이터를 PostgreSQL에 저장
  • 재고 서비스가 Redis 캐시에서 재고를 차감

위의 세 동작을 하나의 트랜잭션처럼 처리하려면, 이들 모두 성공해야 하며, 하나라도 실패하면 전체를 롤백해야 합니다. 이를 2-Phase Commit(2PC) 같은 방식으로 처리할 수는 있지만, 이는 복잡하고 성능 저하가 심각하며, 마이크로서비스 철학과도 어긋납니다.

 

2. Saga 패턴이란?

Saga 패턴은 복잡한 분산 트랜잭션을 단일 서비스 트랜잭션의 시퀀스로 나누고, 각 트랜잭션이 실패했을 경우 **보상 트랜잭션(Compensating Transaction)**을 통해 이전 상태로 되돌리는 방식입니다.

Saga 패턴의 특징

  • 각 서비스는 자체 DB 트랜잭션만 수행
  • 실패 시 롤백 대신 보상 처리
  • Event-driven 방식 또는 Command/Orchestration 방식으로 구현 가능

 

3. Saga 패턴의 두 가지 구현 방식

(1) Choreography 기반 Saga

서비스 간 **이벤트(Event)**를 주고받으며 트랜잭션을 이어갑니다. 중앙 오케스트레이터가 없고, 각 서비스가 다음 단계에 필요한 이벤트를 발행합니다.

장점:

  • 단순하고 유연함
  • 서비스 간 결합도 낮음

단점:

  • 흐름이 복잡해짐
  • 이벤트 폭발 가능성

(2) Orchestration 기반 Saga

중앙 관리 서비스(Orchestrator)가 모든 트랜잭션을 명령(Command) 형태로 지시하고, 성공/실패 여부에 따라 보상 트랜잭션을 호출합니다.

장점:

  • 트랜잭션 흐름이 명확함
  • 실패 처리 로직 통합 가능

단점:

  • Orchestrator가 단일 장애점(SPOF)이 될 수 있음
  • 구현 복잡성 증가

 

4. 실전 예제: 주문 처리 Saga

시나리오:

  1. 고객이 주문을 생성
  2. 결제 요청
  3. 재고 차감
  4. 배송 생성

만약 결제 실패 시, 이미 생성된 주문을 보상 취소해야 하며, 재고가 이미 차감되었다면 재가산이 필요합니다.

1) 이벤트 기반 흐름도

[Order Service]
    |
    |-- OrderCreatedEvent -->
    |
[Payment Service]
    |
    |-- PaymentSuccessEvent / PaymentFailedEvent -->
    |
[Inventory Service]
    |
    |-- InventoryReservedEvent / InventoryFailedEvent -->
    |
[Shipping Service]

2) 보상 트랜잭션 흐름

  • 결제 실패 → 주문 취소 이벤트 발행
  • 재고 부족 → 결제 취소 → 주문 취소

 

5. 예제 코드 (간단한 Orchestration 구현)

주문 서비스 – 주문 생성

@app.post("/order")
def create_order():
    order = save_order_to_db()
    publish_event("order_created", order.id)

오케스트레이터 – 이벤트 수신 및 커맨드 발행

@event_listener("order_created")
def on_order_created(order_id):
    send_command("process_payment", order_id)

@event_listener("payment_failed")
def on_payment_failed(order_id):
    send_command("cancel_order", order_id)

결제 서비스

@command_listener("process_payment")
def process_payment(order_id):
    try:
        charge_credit_card(order_id)
        publish_event("payment_success", order_id)
    except Exception:
        publish_event("payment_failed", order_id)

주문 보상 처리

@command_listener("cancel_order")
def cancel_order(order_id):
    update_order_status(order_id, "CANCELLED")

이러한 로직은 KafkaRabbitMQ 같은 메시지 브로커를 통해 비동기 이벤트로 처리됩니다.

 

6. Saga의 도전 과제 및 보완 방안

  • 데이터 일관성 문제: 보상 트랜잭션이 실제로 완벽한 롤백을 보장하지는 않음
  • 순서 보장: 이벤트 처리 순서에 따라 시스템 상태가 달라질 수 있음
  • 오케스트레이터 복잡도 증가: 트랜잭션 단계가 많아질수록 관리가 어려워짐

보완 전략

  • 이벤트 Idempotency 보장
  • Retry 메커니즘 설계
  • 데이터 변경 로그를 기반으로 재처리 가능성 확보
  • 데드 레터 큐(DLQ) 운영

 

7. 아키텍처 다이어그램 (Orchestration 기반)

[Client]
   |
   V
[Order Service] --> [Event Broker] --> [Orchestrator]
                                           |
                                           |---> [Payment Service]
                                           |---> [Inventory Service]
                                           |---> [Shipping Service]

오케스트레이터는 상태 머신처럼 모든 단계의 성공/실패를 추적하며 흐름을 제어합니다.

 


8. 마무리: 마이크로서비스에서 데이터 일관성 확보의 핵심

Saga 패턴은 마이크로서비스 환경에서 **트랜잭션의 원자성(Atomicity)**을 구현하기 위한 현실적인 해법입니다. 하지만 완벽한 트랜잭션을 보장하기는 어렵기 때문에, 보상 처리 설계, 이벤트 순서 관리, 오류 복구 메커니즘까지 신중하게 고려해야 합니다.

현대적인 클라우드 시스템에서는 Kafka 기반의 Choreography 방식과, Spring Boot + Camunda 같은 Orchestration 방식이 혼합되어 사용되는 경우도 많습니다.