사가 패턴(Saga Pattern)과 2단계 커밋(2PC)을 활용한 분산 트랜잭션 설계

분산 트랜잭션이란?

일반적인 데이터베이스 트랜잭션은 하나의 데이터베이스 안에서 여러 작업을 하나의 논리적 단위로 처리한다. 작업이 실패하면 롤백하여 데이터의 일관성을 유지할 수 있다.

하지만 마이크로서비스 아키텍처에서는 주문, 결제, 재고, 배송 등의 기능이 서로 다른 서비스와 데이터베이스로 분리될 수 있다. 이때 하나의 비즈니스 작업을 완료하려면 여러 서비스의 트랜잭션을 함께 처리해야 한다.

예를 들어 사용자가 온라인 게임 플랫폼에서 유료 아이템을 구매한다고 가정하자.

  • 주문 서비스는 구매 주문을 생성한다.
  • 결제 서비스는 사용자의 결제를 처리한다.
  • 인벤토리 서비스는 아이템을 지급한다.
  • 알림 서비스는 구매 완료 메시지를 전송한다.

여기서 결제는 성공했지만 아이템 지급에 실패한다면 어떻게 해야 할까?

단일 데이터베이스에서는 트랜잭션 롤백으로 해결할 수 있지만, 서로 다른 서비스의 데이터베이스를 사용하는 환경에서는 하나의 로컬 트랜잭션으로 모든 작업을 되돌릴 수 없다.

이러한 문제를 해결하기 위해 사용하는 대표적인 접근 방식이 2단계 커밋(Two-Phase Commit, 2PC) 과 사가 패턴(Saga Pattern) 이다.

2단계 커밋(Two-Phase Commit, 2PC)

2단계 커밋은 여러 트랜잭션 참여자가 하나의 트랜잭션 결과에 합의하도록 조정하는 분산 트랜잭션 프로토콜이다.

핵심은 모든 참여자가 커밋할 준비가 되었는지 먼저 확인한 후, 전체 트랜잭션의 커밋 또는 롤백을 결정하는 것이다.

Coordinator (트랜잭션 조정자)

전체 트랜잭션의 결과를 결정한다.

Prepare 단계

모든 참여자에게 커밋 준비 요청

주문 서비스

준비 완료

결제 서비스

준비 완료

Commit 단계

모두 준비되었다면 커밋, 하나라도 불가능하다면 롤백 결정

Prepare 단계

조정자는 각 참여자에게 트랜잭션을 커밋할 준비가 되었는지 확인한다.

참여자는 필요한 데이터 변경을 준비하고, 최종 결정이 내려질 때까지 트랜잭션을 유지할 수 있어야 한다. 구현 방식에 따라 데이터베이스 잠금이나 관련 자원이 유지될 수 있다.

이 단계에서는 일반적으로 최종 커밋이 완료된 것이 아니다.

Commit 단계

모든 참여자가 준비 완료를 응답하면 조정자는 커밋을 지시한다.

반대로 참여자 중 하나라도 준비할 수 없다고 응답하면 조정자는 롤백을 결정한다.

목표는 모든 참여자가 동일한 최종 결과를 따르도록 하는 것이다.

2PC의 장점

  • 모든 참여자가 하나의 트랜잭션 결과에 합의하도록 조정할 수 있다.
  • 강한 일관성이 중요한 작업에 적합할 수 있다.
  • 보상 트랜잭션을 직접 설계하는 방식보다 원자적 커밋을 명시적으로 다룰 수 있다.

2PC의 단점

  • 참여자들이 최종 결정까지 잠금이나 자원을 유지할 수 있어 처리량이 저하될 수 있다.
  • 조정자나 네트워크에 장애가 발생하면 참여자가 결정을 기다리며 멈출 수 있다.
  • 참여 데이터베이스와 통신 계층이 분산 트랜잭션 프로토콜을 지원해야 한다.
  • 서비스 간 결합도와 운영 복잡도가 증가한다.

따라서 2PC는 모든 마이크로서비스 환경에서 기본적으로 선택할 수 있는 만능 해결책이 아니다. 특히 각 서비스가 독립적인 데이터베이스를 소유하는 구조에서는 적용 가능성과 장애 복구 방식을 먼저 검토해야 한다.

Oracle Documentation

+1

사가 패턴(Saga Pattern)

사가 패턴은 여러 서비스에서 발생하는 로컬 트랜잭션을 하나의 비즈니스 흐름으로 연결하고, 중간 작업이 실패하면 보상 트랜잭션(Compensating Transaction) 을 실행하여 이전 작업의 영향을 상쇄하는 설계 패턴이다.

2PC가 모든 참여자의 커밋을 조정하는 방식이라면, Saga는 각 서비스가 자신의 트랜잭션을 완료하고 다음 작업으로 진행하는 방식이다.

예를 들어 아이템 구매 과정에서 결제까지 성공했지만 아이템 지급이 실패했다면, 결제 취소와 주문 취소를 실행하여 시스템을 일관된 상태로 복구할 수 있다.

Saga의 처리 흐름

주문 생성

OrderCreated

결제 처리

PaymentCompleted

아이템 지급

InventoryGranted

구매 완료

SagaCompleted

아이템 지급 실패

실패한 작업을 재시도하거나 보상 흐름을 실행한다.

결제 취소

주문 취소 또는 실패 확정

여기서 중요한 점은 보상 트랜잭션이 데이터베이스의 실제 롤백과 동일하지 않다는 것이다.

이미 외부 결제 시스템에서 결제가 승인되었다면 해당 데이터베이스 트랜잭션을 되돌리는 것이 아니라 별도의 환불 요청을 수행해야 한다. 따라서 보상 작업 자체도 실패할 수 있다는 점을 고려해야 한다.

보상 트랜잭션(Compensating Transaction)

보상 트랜잭션은 이전 작업의 결과를 비즈니스 관점에서 상쇄하는 작업이다.

실행한 작업보상 작업
주문 생성주문 취소
재고 예약재고 예약 해제
결제 승인결제 취소 또는 환불
포인트 차감포인트 복구
아이템 지급지급 회수 또는 별도 보정

모든 작업을 완벽하게 원상 복구할 수 있는 것은 아니다. 이미 발송된 이메일은 회수할 수 없고, 외부 시스템에 전달된 정보 역시 되돌릴 수 없을 수 있다.

따라서 Saga를 설계할 때는 단순한 데이터 복구가 아니라 실패 후 비즈니스적으로 허용 가능한 상태를 만드는 것이 목표가 된다.

Saga 구현 방식

Saga는 크게 코레오그래피(Choreography)와 오케스트레이션(Orchestration) 방식으로 구현할 수 있다.

코레오그래피(Choreography)

코레오그래피 방식은 중앙 조정자 없이 각 서비스가 이벤트를 발행하고 다른 서비스가 해당 이벤트를 구독하여 다음 작업을 실행하는 방식이다.

Order Service

OrderCreated 이벤트 발행

Payment Service

결제 처리 후 PaymentCompleted 발행

Inventory Service

아이템 지급 후 InventoryGranted 발행

장점은 중앙 조정자에 대한 의존성을 줄이고 서비스 간 결합도를 낮출 수 있다는 점이다.

반면 참여 서비스가 늘어날수록 이벤트의 발행과 구독 관계가 복잡해지고, 전체 트랜잭션이 현재 어느 단계에 있는지 추적하기 어려워질 수 있다.

따라서 참여 서비스가 적고 이벤트 흐름이 단순한 경우에 적합하다.

오케스트레이션(Orchestration)

오케스트레이션 방식은 Saga Orchestrator라는 중앙 조정자가 전체 트랜잭션의 진행 상태와 실행 순서를 관리한다.

Saga Orchestrator

현재 단계, 실행 결과, 재시도 및 보상 관리

주문

결제

인벤토리

조정자가 각 서비스에 명령을 보내고 결과를 받아 다음 단계를 결정한다.

장점은 전체 비즈니스 흐름이 명시적이고, 실패 시 어떤 보상 작업을 실행해야 하는지 관리하기 쉽다는 점이다.

단점은 조정자의 구현과 운영이 추가되고, 조정자가 장애 지점이 될 수 있다는 점이다. 이를 해결하려면 실행 상태의 영속화, 재시작 복구, 중복 명령 처리 등이 필요하다.

참여 서비스가 많거나 주문·결제·재고처럼 복잡한 비즈니스 흐름을 관리해야 한다면 오케스트레이션 방식이 유리하다.

2PC와 Saga 비교

비교 항목2PCSaga
핵심 원리커밋 결과를 공동으로 결정로컬 트랜잭션과 보상 작업 연결
처리 방식Prepare 후 Commit순차적 작업 및 실패 복구
일관성원자적 커밋을 목표로 함최종적 일관성을 주로 활용
잠금준비 단계에서 자원이 유지될 수 있음로컬 트랜잭션 범위로 제한 가능
실패 복구커밋 또는 롤백 결정재시도 또는 보상 트랜잭션
주요 부담조정 프로토콜과 대기 시간보상 로직과 상태 추적
적합한 환경강한 원자성이 필요한 지원 가능 시스템독립 서비스와 장시간 비즈니스 흐름

이 비교는 일반적인 설계 특성을 나타낸다. 실제 일관성과 장애 복구 수준은 데이터베이스, 메시징 시스템, 프로토콜 구현에 따라 달라진다.

실시간 멀티플레이 게임 서버에 적용하기

온라인 게임 플랫폼에서 회원 포인트로 아이템을 구매한다고 가정하자.

게임 서버는 포인트 데이터베이스, 구매 내역 데이터베이스, 인벤토리 서비스 등 여러 구성 요소를 사용할 수 있다. 이때 모든 작업을 하나의 분산 트랜잭션으로 묶는 대신 Saga를 활용할 수 있다.

권장 처리 흐름

  • 주문 생성: 주문을 PENDING 상태로 저장한다.
  • 포인트 예약: 구매 가능한 포인트를 확인하고 차감 또는 예약한다.
  • 아이템 지급: 인벤토리에 아이템을 지급한다.
  • 구매 확정: 모든 필수 작업이 성공하면 주문을 COMPLETED로 변경한다.
  • 실패 처리: 복구 가능한 오류는 재시도하고, 최종 실패 시 포인트를 복구하고 주문을 FAILED 또는 CANCELLED로 변경한다.

여기서 중요한 것은 게임 내 포인트와 아이템의 소유권이 중복 지급되거나 유실되지 않도록 만드는 것이다.

반드시 고려해야 할 안정성 문제

멱등성(Idempotency)

메시지가 중복 전달되더라도 같은 구매가 두 번 처리되지 않도록 해야 한다. 주문 ID나 작업 ID를 기준으로 중복 실행을 방지한다.

상태 영속화

Saga의 현재 단계, 각 작업의 결과, 재시도 횟수, 보상 진행 상태를 데이터베이스에 기록해야 한다. 서버가 재시작되어도 진행 중이던 작업을 복구할 수 있어야 한다.

Transactional Outbox

데이터베이스 변경과 이벤트 발행 사이의 불일치를 방지하기 위해 활용할 수 있다. 같은 로컬 트랜잭션에서 비즈니스 데이터와 발행할 이벤트를 Outbox 테이블에 기록하고, 별도 발행기가 메시지 브로커로 전달한다.

재시도와 보상 분리

일시적인 네트워크 오류라면 재시도할 수 있지만, 잔액 부족과 같은 비즈니스 오류는 무조건 재시도해서는 안 된다. 오류 유형에 따라 재시도, 보상, 수동 복구를 구분해야 한다.

동시성 제어

같은 사용자가 동시에 여러 구매 요청을 보내면 포인트가 중복 차감되거나 재고가 초과 지급될 수 있다. 조건부 업데이트, 낙관적 락, 비관적 락, 고유 제약 조건 등의 기법을 함께 고려해야 한다.

Saga는 이러한 문제를 자동으로 해결해 주는 도구가 아니다. 일관성을 유지하기 위한 흐름과 실패 처리 정책을 명시적으로 설계하는 패턴이다.

참고 자료

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다