트랜잭션 아웃박스 패턴과 Saga 패턴의 역할과 범위
- 아웃박스 패턴으로 이벤트 발행의 성공을 보장
- Saga 패턴과 보상 트랜잭션
"메시지 발행의 성공" 과 "분산된 비즈니스 프로세스의 성공" 간의 차이
트랜잭션 아웃박스 패턴의 보장 범위
보장 내용
- Service A가 자신의 로컬 DB에 데이터를 저장하고, 동시에 OutboxEvent를 저장하는 과정의 원자성(Atomicity)을 보장
- Outbox Relayer에 의해 이 OutboxEvent가 메시지 브로커(Kafka)에 최소 한 번(At-Least-Once) 발행될 것을 보장
보장하지 않는 것
- 메시지 브로커에 발행된 이벤트가 컨슈머 서비스(Service B, C 등)에서 성공적으로 처리될지 여부는 보장하지 않음
Saga 패턴과 보상 트랜잭션이 필요한 이유
- Saga 패턴은 여러 서비스에 걸쳐 있는 하나의 큰 비즈니스 프로세스(분산 트랜잭션)의 "결과적 일관성"을 보장하는 것이 목표
- 트랜잭션 아웃박스 패턴은 이 Saga의 각 단계에서 이벤트를 "안전하게 발행"하는 것을 돕는 보조적인 패턴
어떤 상황에서 보상 트랜잭션이 필요한가
가장 흔한 예시는 "주문 생성 및 결제" Saga
시나리오: 주문 생성 및 결제 (Order Creation & Payment Saga)
-
Saga 단계
- Order Service: 주문을 생성하고 OrderCreatedEvent를 발행 (트랜잭션 아웃박스 사용)
- Payment Service: OrderCreatedEvent를 구독하여 결제를 시도하고, 결제 성공 시 PaymentProcessedEvent를, 실패 시 PaymentFailedEvent를 발행 (트랜잭션 아웃박스 사용)
- Inventory Service: PaymentProcessedEvent를 구독하여 상품 재고를 차감하고, 재고 차감 성공 시 InventoryDeductedEvent를 발행 (트랜잭션 아웃박스 사용)
-
문제 발생 상황과 보상 트랜잭션의 필요성
- 상황 1: Payment Service에서 결제 실패
- 발생: Order Service가 OrderCreatedEvent를 성공적으로 발행했고, Payment Service가 이를 받음.
- 하지만 실제 결제 승인 과정(외부 PG사 연동 등)에서 문제가 발생하여 결제가 실패
- 필요성: Order Service는 이미 주문을 생성한 상태
- 결제가 실패했으니, 이 주문은 "취소 상태" 가 되어야 함
- 보상 트랜잭션: Payment Service는 PaymentFailedEvent를 발행
- Order Service는 이 PaymentFailedEvent를 구독하여 생성했던 주문을 취소 상태로 변경(보상) 하는 로직을 실행
- 상황 2: Inventory Service에서 재고 부족으로 차감 실패
- 발생: Order Service가 주문 생성, Payment Service가 결제 승인까지 성공하여 PaymentProcessedEvent를 발행
- Inventory Service가 이를 받음
- 하지만 재고 부족으로 인해 해당 상품의 재고를 차감 실패
- 필요성: 재고가 없으니 주문을 완료할 수 없음
- 이미 결제된 금액은 환불되어야 하고, 주문은 취소
- 보상 트랜잭션: Inventory Service는 InventoryDeductionFailedEvent를 발행
- Payment Service는 이 이벤트를 구독하여 결제된 금액을 환불(보상) 하는 로직을 실행
- Order Service는 이 이벤트를 구독하여 주문을 취소 상태로 변경(보상) 하는 로직을 실행
- 상황 3: 컨슈머 서비스 자체의 비즈니스 로직 오류 (DB 제약 조건 위반 등)
- 발생: Order Service가 이벤트를 성공적으로 발행
- Payment Service가 이벤트를 받았지만, Payment Service의 내부 DB에 결제 정보를 저장하려는데 유효성 검증 실패나 비즈니스 규칙 위반(예: 사용자에게 이미 미납된 결제가 있음)으로 저장이 실패 (Kafka 메시지 발행은 문제가 없었지만, 비즈니스 로직 실패).
- 필요성: 주문은 취소되어야 함
- 보상 트랜잭션: Payment Service는 이 비즈니스 로직 실패를 감지
- PaymentFailedEvent를 발행하여 Order Service가 주문을 보상(취소)하도록 트리거
트랜잭션 아웃박스 + Saga의 관계
- 트랜잭션 아웃박스 패턴
- 각 서비스(Order, Payment, Inventory)가 자신의 로컬 DB 변경(주문 생성, 결제 기록, 재고 차감)과 해당 이벤트를 메시지 브로커에 "확실하게 발행"하는 것을 보장 + 이는 이중 쓰기 문제를 해결하여 메시지 유실을 방지
- Saga 패턴
- 아웃박스 패턴으로 안전하게 발행된 이벤트들을 사용하여 분산된 비즈니스 프로세스(주문 -> 결제 -> 재고 차감)를 단계별로 진행
- 만약 어떤 단계에서 비즈니스적인 실패가 발생하면, 이전 단계들을 보상 트랜잭션으로 되돌려 전체 프로세스의 일관성을 유지
트랜잭션 아웃박스 패턴은 "메시지가 브로커에 도착하는 것" 을 보장
Saga 패턴은 "비즈니스 프로세스가 분산된 환경에서 최종적으로 일관된 상태가 되는 것" 을 보장
이를 위해 보상 트랜잭션이 필수적인 역할