반응형
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
'Architecture' 카테고리의 다른 글
대기열(Waiting Room) 시스템, 직접 만들까? 외부 솔루션 쓸까? (0) | 2025.04.29 |
---|---|
서버 스펙 산정 (0) | 2025.04.28 |
MVC vs CQRS: 무엇이 다르고 언제 써야 할까? (0) | 2025.04.16 |
도메인 중심 설계의 정석: Hexagonal Architecture + DDD + CQRS 아키텍처 통합 정리 (0) | 2025.04.09 |
쿠버네티스 클러스터 프로비저닝 도구 알아보기 (0) | 2024.10.19 |