TIL 4일차

HanEol~·어제
post-thumbnail

개요

RabbitMQ를 사용해 Choreography 기반 Saga 패턴의 보상 트랜잭션 동작 방식을 학습하고 구현한 기록입니다.


1. Saga 패턴이란?

  • 최종 일관성(Eventual Consistency) 확보: MSA 환경에서는 서비스마다 DB가 분리되어 있어 단일 @Transactional로 묶을 수 없습니다.
  • 보상 트랜잭션(Compensating Transaction): 각 서비스가 로컬 트랜잭션을 수행하며, 중간에 실패가 발생하면 이전 단계들을 역순으로 되돌리는 보상 이벤트를 발행합니다.
  • 이번 실습에서는 중앙 제어자 없이 각 서비스가 이벤트를 구독하여 자율적으로 동작하는 Choreography 방식을 적용했습니다.

2. 시스템 구조 및 큐 설계

order 서비스에서 product-q로 상품 수량 차감을 요청하고, product 서비스 성공 시 payment-q로 결제를 진행합니다.

실패 시 역방향 보상 메시지(product-err-q, order-err-q)를 발행해 트랜잭션을 취소합니다.

큐 설계 (Queue Architecture)

큐 이름발행 (Producer)소비 (Consumer)역할
product-qorderproduct재고 차감 요청
payment-qproductpayment결제 요청
product-err-qpaymentproduct재고 복구 (보상)
order-err-qproductorder주문 취소 (보상)

3. 핵심 구현 (Product 서비스)

product 서비스는 재고 차감 성공 시 다음 단계(payment-q)로 이벤트를 넘기고, 예외 발생 시 보상 이벤트(order-err-q)를 발행합니다.

@Slf4j
@Component
@RequiredArgsConstructor
public class ProductConsumer {

    private final ProductService productService;
    private final RabbitTemplate rabbitTemplate;

    @RabbitListener(queues = "product-q")
    public void handleOrder(OrderMessage message) {
        try {
            // 로컬 트랜잭션: 재고 차감
            productService.decreaseStock(message.getProductId(), message.getQuantity());
            
            // 다음 단계 진행 (결제 요청)
            rabbitTemplate.convertAndSend("saga-exchange", "payment", message);
        } catch (Exception e) {
            log.error("재고 차감 실패 - orderId: {}", message.getOrderId(), e);
            
            // 보상 트랜잭션 발행 (주문 취소 요청)
            rabbitTemplate.convertAndSend("saga-exchange", "order.err", message);
        }
    }
}

4. 보상 트랜잭션 실패 시 대응 전략

보상 트랜잭션조차 실패하는 상황에 대비하기 위해 다음과 같은 설계 전략을 적용합니다.

  1. 자동 재시도 (Retry) & DLQ (Dead Letter Queue)
  • 보상 로직 실패 시 일정 횟수 자동 재시도를 수행합니다.
  • 지속적인 실패 시 DLQ로 이관하고, 담당자 알림(Slack, Email 등)을 발송하여 수동 조치(Admin 개입)합니다.
  1. 사용자 경험(UX) 관점의 'PENDING' 상태 활용
  • 보상이 진행되는 동안 주문 상태를 PENDING(처리 중)으로 유지합니다.
  • 내부적인 복구 과정(Error)을 사용자에게 직접 노출하지 않고 일관된 UX를 제공합니다.
  1. 피벗 단계(Pivot Step) 배치 전략
  • 결제와 같이 되돌리기 어렵거나 리스크가 큰 작업은 Saga의 가장 마지막 단계(Pivot Step)에 배치합니다.
  • 앞선 단계의 재고 차감 등이 보상/재시도되더라도, 결제 취소가 늦게 발생하거나 꼬이는 문제를 구조적으로 방지합니다.

5. 학습 소감 및 회고

  • RabbitMQ 컴포넌트 이해: Exchange, Binding, Queue, Producer/Consumer의 역할을 직접 구성해보고 대시보드 메세지 흐름을 추적하며 모니터링 감각을 익혔습니다.
  • 보상 패턴의 핵심: 정상 흐름에서는 예외를 catch하여 보상 메시지를 발행하고, 보상 흐름 자체의 실패는 예외를 던져 RabbitMQ의 Retry/DLQ로 넘기는 메커니즘을 명확히 구분하게 되었습니다.
  • 한계점 및 개선 방향:
  • 보상 트랜잭션의 멱등성(Idempotency) 보장, DLQ 수동 처리, 중간 상태 관리 등 복잡성이 높았습니다.
  • 서비스 도메인이 늘어날수록 Choreography 방식은 이벤트 흐름 파악이 어렵고 순환 의존성이 발생할 수 있어, 추후에는 Centralized Control이 가능한 Orchestration 방식 도입을 고려해야겠다고 느꼈습니다.
profile
기록을 통해 앞으로 나아가자!

0개의 댓글