IT 지식 목록
IT 지식

데이터 일관성을 지키는 아키텍처 패턴: SAGA 패턴을 통한 분산 트랜잭션 해결

분산 시스템의 데이터 정합성을 보장하기 위해 로컬 트랜잭션을 연결하고 보상 로직으로 장애를 해결하는 SAGA 패턴의 핵심 원리를 다룹니다.

00
데이터 일관성을 지키는 아키텍처 패턴: SAGA 패턴을 통한 분산 트랜잭션 해결

마이크로서비스 아키텍처(MSA)가 도입되면서 우리는 더 이상 하나의 데이터베이스 안에서 트랜잭션을 완결 지을 수 없는 환경에 놓였습니다. 단일 데이터베이스 환경에서는 ACID(Atomicity, Consistency, Isolation, Durability)를 보장하기 위해 BEGIN과 COMMIT이라는 강력한 도구를 사용했지만, 여러 서비스에 걸쳐 있는 비즈니스 프로세스에서는 이 방식이 통하지 않습니다. 이때 등장하는 것이 바로 'SAGA 패턴'입니다.

개념 소개: 분산 트랜잭션의 난제와 SAGA의 탄생

개념 소개: 분산 트랜잭션의 난제와 SAGA의 탄생

SAGA 패턴은 분산 시스템에서 여러 서비스에 걸친 데이터 일관성을 유지하기 위해 탄생했습니다. 분산 환경에서는 서비스 A에서 주문을 생성하고, 서비스 B에서 결제를 수행하며, 서비스 C에서 재고를 차감해야 합니다. 만약 서비스 C에서 재고가 없어 실패한다면 어떻게 될까요? 이미 완료된 주문과 결제를 되돌려야(Rollback) 합니다. SAGA는 트랜잭션을 하나의 단위로 묶는 대신, 로컬 트랜잭션들의 연속으로 분해하고, 실패 시 보상 트랜잭션(Compensating Transaction)을 실행하여 결과적인 일관성(Eventual Consistency)을 확보합니다.

핵심 원리: 오케스트레이션과 코레오그래피

SAGA는 이를 구현하는 방식에 따라 두 가지 모델로 나뉩니다.

  1. 코레오그래피(Choreography): 중앙 제어 장치 없이 각 서비스가 이벤트를 발행하고 구독합니다. "주문 생성됨" 이벤트를 듣고 결제 서비스가 일을 처리하는 식입니다.
  2. 오케스트레이션(Orchestration): 중앙 관리자(Orchestrator)가 각 서비스의 호출 흐름을 제어합니다. 중앙에서 상태를 관리하기 때문에 복잡한 프로세스에 유리합니다.

비유하자면, 코레오그래피는 단체 무용에서 서로의 움직임을 보고 반응하는 것이고, 오케스트레이션은 지휘자가 악기들에게 언제 연주할지 신호를 주는 것과 같습니다.

코드 예제: 파이썬으로 보는 SAGA 흐름

아래 예제는 오케스트레이션 방식을 간소화하여 보상 트랜잭션을 구현한 구조입니다.

# 서비스 간 상태 관리의 예시
class OrderSaga:
    def execute(self, order_data):
        # 1. 로컬 트랜잭션: 주문 생성
        order_id = self.create_order(order_data)
        try:
            # 2. 로컬 트랜잭션: 결제 수행
            self.process_payment(order_id)
            # 3. 로컬 트랜잭션: 재고 차감
            self.deduct_inventory(order_id)
        except Exception as e:
            # 4. 실패 시 보상 트랜잭션 실행
            print(f"오류 발생, 보상 로직 실행: {e}")
            self.compensate(order_id)

    def compensate(self, order_id):
        # 데이터 일관성을 맞추기 위해 상태를 원복
        print(f"주문 {order_id} 취소 처리 및 결제 환불 요청")

위의 예시를 비동기 이벤트 기반(코레오그래피)으로 확장하면 다음과 같습니다.

// JavaScript(Node.js) 이벤트 기반 처리
eventEmitter.on('ORDER_CREATED', async (data) => {
    try {
        await paymentService.charge(data);
        eventEmitter.emit('PAYMENT_SUCCESS', data);
    } catch (err) {
        // 결제 실패 시 주문 취소 이벤트 발행
        eventEmitter.emit('ORDER_CANCEL_REQUESTED', data);
    }
});

eventEmitter.on('ORDER_CANCEL_REQUESTED', async (data) => {
    // 실제 데이터베이스 상태를 업데이트하는 보상 로직
    await db.orders.update({ id: data.id, status: 'CANCELLED' });
});

실무 적용 사례: 이커머스 결제 시스템

실무 적용 사례: 이커머스 결제 시스템

가장 흔한 사례는 대규모 이커머스 플랫폼의 '주문-결제-배송' 프로세스입니다. 사용자가 주문 버튼을 누르는 순간 시스템은 주문 생성, 결제 승인, 물류 창고 할당이라는 세 가지 독립적인 서비스를 거칩니다. 이때 서비스 간의 네트워크 타임아웃이나 재고 부족 등의 이유로 실패가 발생할 경우, SAGA 패턴을 통해 '결제 완료 후 재고 실패'와 같은 데이터 불일치 상태를 '결제 환불 후 주문 취소 완료'라는 정합성 있는 상태로 빠르게 복구할 수 있습니다.

자주 하는 실수와 해결법

가장 흔한 실수는 보상 트랜잭션을 작성하지 않는 것입니다. 많은 개발자가 '성공' 케이스에만 집중하여 코드를 작성하지만, 분산 시스템은 반드시 실패한다는 전제를 가져야 합니다.

  • 실수: "보상 트랜잭션이 실패하면 어떻게 하지?"
  • 해결책: 보상 트랜잭션은 **멱등성(Idempotency)**을 보장해야 하며, 무한 재시도(Retry)를 지원하는 메시지 큐(Kafka, RabbitMQ 등)와 연동하여 실패 시 끝까지 성공을 보장해야 합니다. 또한, 보상 처리가 불가능한 극단적인 경우(예: 환불 실패)는 별도의 관리자 알림(Dead Letter Queue)을 통해 사람의 개입을 유도해야 합니다.

더 공부할 리소스 추천

  1. Microservices Patterns (Chris Richardson 저): SAGA 패턴의 바이블로 불리는 도서입니다. 이론부터 실제 코드 구현까지 완벽하게 다룹니다.
  2. Temporal.io 공식 문서: SAGA를 코드로 구현할 때 발생하는 상태 유지 문제를 아주 우아하게 해결해주는 오픈소스 워크플로우 엔진입니다.
  3. Martin Fowler의 마이크로서비스 블로그: SAGA 패턴의 기원과 개념적 정의를 심도 있게 학습할 수 있습니다.