Architecture

마이크로서비스에서 SAGA 패턴 완전 정복: NestJS 실전 예제와 함께

구루싸 2025. 5. 7. 15:43
반응형
SMALL

마이크로서비스 아키텍처(MSA)에서는 여러 서비스가 각각의 데이터베이스를 가지고 독립적으로 동작합니다.

이런 환경에서 “여러 서비스가 연관된 하나의 비즈니스 트랜잭션”을 어떻게 안전하게 처리할 수 있을까요?

바로 SAGA 패턴이 그 해답입니다.


1. SAGA 패턴이란?

SAGA 패턴은 분산 트랜잭션을 관리하는 대표적인 방법입니다.

각 서비스는 자신의 로컬 트랜잭션만 보장하고,전체 트랜잭션은 여러 단계로 나누어 “성공/실패 이벤트”를 주고받으며 처리합니다.

  • Orchestration(오케스트레이션): 중앙 조정자가 각 서비스에 명령을 내림
  • Choreography(코레오그래피): 서비스들이 이벤트를 발행/구독하며 트랜잭션을 이어감

※ SAGA의 의미

  • SAGA는 “긴 이야기”, “연속된 사건”이라는 뜻의 영어 단어입니다.
  • 분산 트랜잭션에서 여러 단계의 작업이 순차적으로 이어지고,

각 단계가 성공/실패에 따라 다음 단계로 넘어가거나 보상 작업이 실행되는“서사적인 흐름”을 비유적으로 표현한 것입니다.


2. SAGA 패턴이 필요한 이유

  • 단일 DB 트랜잭션(ACID)만으로는 여러 서비스/DB에 걸친 원자성을 보장할 수 없음
  • 2PC(2-Phase Commit)는 복잡하고 성능 저하, 장애에 취약
  • SAGA는 이벤트 기반으로 유연하고, 장애 복구/보상 트랜잭션 설계가 용이

3. 실전 시나리오: 주문-결제-재고

  • OrderService: 주문 생성
  • PaymentService: 결제 승인
  • InventoryService: 재고 차감
  • SAGA Orchestrator가 각 서비스에 명령을 내림

(실패 시 보상 트랜잭션 실행)


4. NestJS에서 SAGA 패턴 적용하기

4-1. 메시지 브로커 연동 (예: Kafka)

NestJS에서는 @nestjs/microservices 패키지로 Kafka, RabbitMQ 등 다양한 브로커를 쉽게 연동할 수 있습니다.

// app.module.ts
import { ClientsModule, Transport } from '@nestjs/microservices';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'KAFKA_SERVICE',
        transport: Transport.KAFKA,
        options: {
          client: { brokers: ['localhost:9092'] },
          consumer: { groupId: 'saga-consumer' },
        },
      },
    ]),
  ],
})
export class AppModule {}

4-2. SAGA Orchestrator 서비스 구현

@Injectable()
export class OrderSagaService {
  constructor(
    @Inject('KAFKA_SERVICE') private readonly kafkaClient: ClientKafka,
    private readonly orderRepository: OrderRepository,
    private readonly sagaStateRepository: SagaStateRepository,
  ) {}

  async createOrderSaga(orderData: CreateOrderDto) {
    // 1. 주문 생성 (로컬 트랜잭션)
    const order = await this.orderRepository.create(orderData);
    await this.sagaStateRepository.save({ orderId: order.id, state: 'ORDER_CREATED' });

    // 2. 결제 요청
    await this.kafkaClient.emit('payment.request', { orderId: order.id, amount: order.total });

    // 이후 단계는 이벤트 리스너에서 처리
  }

  // 보상 트랜잭션
  async compensateOrder(orderId: string) {
    await this.kafkaClient.emit('payment.cancel', { orderId });
    await this.kafkaClient.emit('inventory.release', { orderId });
    await this.orderRepository.cancel(orderId);
    await this.sagaStateRepository.save({ orderId, state: 'COMPENSATED' });
  }
}

4-3. 상태머신(State Machine)으로 SAGA 상태 관리

// saga-state.repository.ts
@Entity('saga_state')
export class SagaState {
  @PrimaryColumn()
  orderId: string;

  @Column()
  state: string; // 예: ORDER_CREATED, PAYMENT_DONE, INVENTORY_DONE, FAILED, COMPENSATED

  @Column({ nullable: true })
  lastEvent: string;
}

 


4-4. 이벤트 리스너 구현

@MessagePattern('payment.success')
async handlePaymentSuccess(@Payload() data: { orderId: string }) {
  // 상태 업데이트
  await this.sagaStateRepository.save({ orderId: data.orderId, state: 'PAYMENT_DONE' });
  // 다음 단계(재고 차감)로 진행
  await this.kafkaClient.emit('inventory.reserve', { orderId: data.orderId, items: ... });
}

@MessagePattern('inventory.success')
async handleInventorySuccess(@Payload() data: { orderId: string }) {
  await this.sagaStateRepository.save({ orderId: data.orderId, state: 'INVENTORY_DONE' });
  // 주문 완료 처리
  await this.orderRepository.complete(data.orderId);
}

@MessagePattern('payment.failed')
async handlePaymentFailed(@Payload() data: { orderId: string }) {
  await this.sagaStateRepository.save({ orderId: data.orderId, state: 'FAILED', lastEvent: 'payment.failed' });
  // 보상 트랜잭션 실행
  await this.compensateOrder(data.orderId);
}

 


5. 보상 트랜잭션(Compensation Transaction) 설계

  • SAGA의 각 단계는 성공/실패에 따라 다음 단계로 진행하거나,

실패 시 이전 단계의 작업을 취소(보상)해야 합니다.

  • 예시:
  • 결제 실패 → 주문 취소
  • 재고 차감 실패 → 결제 취소, 주문 취소
async compensateOrder(orderId: string) {
  await this.kafkaClient.emit('payment.cancel', { orderId });
  await this.kafkaClient.emit('inventory.release', { orderId });
  await this.orderRepository.cancel(orderId);
}

 


6. 마무리: SAGA 패턴의 장점과 주의점

  • 장점
  • 서비스 간 느슨한 결합, 장애 복구 용이, 확장성
  • 이벤트 기반으로 유연한 트랜잭션 관리
  • 주의점
  • 보상 트랜잭션 설계가 반드시 필요
  • 상태 동기화, 중복 처리, 장애 복구 등 추가 설계 필요
  • 메시지 브로커 장애/중복 이벤트 처리에 대한 대비 필요

7. 결론

SAGA 패턴은 MSA 환경에서 분산 트랜잭션을 안전하게 처리하는 가장 현실적인 방법입니다.NestJS와 메시지 브로커, 상태머신, 보상 트랜잭션을 조합하면실제 현업에서도 충분히 견고한 시스템을 만들 수 있습니다.

반응형
LIST