금융 결제 시스템이나 재고 관리 시스템을 모놀리식에서 마이크로서비스(MSA)로 전환할 때, 엔지니어가 마주하는 가장 치명적인 시나리오는 다음과 같습니다: PaymentService가 결제 완료 이벤트를 발행했지만, 네트워크 타임아웃으로 인해 브로커(Broker)로부터 ACK를 받지 못했습니다. 프로듀서는 재시도(Retry) 정책에 따라 동일한 이벤트를 다시 발행합니다. 결과적으로 InventoryService는 동일한 주문에 대해 재고를 두 번 차감하게 됩니다.
이러한 "At-Least-Once(최소 한 번 전송)" 전송 보장 방식이 지배적인 카프카(Apache Kafka)나 RabbitMQ 환경에서, 데이터 정합성을 유지하기 위한 엔지니어링은 선택이 아닌 필수입니다.
메시지 중복과 브로커의 전송 의미론
분산 시스템에서의 통신은 본질적으로 신뢰할 수 없습니다. TCP 레벨에서의 핸드셰이크가 성공했더라도, 애플리케이션 레벨의 커밋(Offset Commit) 단계에서 장애가 발생하면 컨슈머는 메시지를 다시 읽어들입니다.
REPEATABLE READ 이상이라 하더라도 Lost Update가 발생할 수 있습니다.
멱등성(Idempotency) 구현 전략
멱등성은 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질을 의미합니다. REST API나 이벤트 핸들러에서 멱등성을 보장하는 가장 확실한 방법은 Idempotency Key를 활용한 중복 제거입니다.
1. 고유 제약 조건(Unique Constraint) 활용
RDBMS의 ACID 트랜잭션을 이용하는 방법입니다. 별도의 processed_events 테이블을 생성하고, message_id나 비즈니스 키(예: order_id)에 Unique Index를 겁니다. 이벤트 처리 로직과 이벤트 기록 로직을 하나의 트랜잭션으로 묶습니다.
// Spring Boot와 JPA를 가정한 멱등성 컨슈머 예제
@Transactional(isolation = Isolation.READ_COMMITTED)
public void handleOrderEvent(OrderEvent event) {
// 1. 멱등성 키 확인 (DB단에서 중복 방어)
if (processedEventRepository.existsByMessageId(event.getMessageId())) {
log.warn("Duplicate event detected: {}", event.getMessageId());
return;
}
// 2. 비즈니스 로직 수행
inventoryService.deduct(event.getItemId(), event.getQuantity());
// 3. 처리된 이벤트 기록 (Unique Constraint로 인해 동시성 제어)
// 만약 이 시점에 중복 키 예외가 발생하면 트랜잭션은 롤백됨
processedEventRepository.save(new ProcessedEvent(event.getMessageId()));
}
2. Redis와 Lua Script를 이용한 원자적 처리
데이터베이스 부하를 줄이기 위해 Redis를 활용할 수 있습니다. SETNX(Set if Not Exists) 명령어를 사용하거나, 복합적인 로직이 필요한 경우 Lua Script를 실행하여 원자성을 보장합니다.
분산 트랜잭션과 사가(Saga) 패턴
단일 서비스 내에서의 정합성은 DB 트랜잭션으로 해결되지만, 여러 마이크로서비스에 걸친 비즈니스 로직(예: 주문 -> 결제 -> 재고 -> 배송)은 2PC(Two-Phase Commit)의 성능 저하 문제로 인해 사가(Saga) 패턴을 주로 사용합니다.
Transactional Outbox 패턴
이벤트 발행과 DB 업데이트의 원자성을 보장하기 위해 사용합니다. "Dual Write" 문제를 해결하는 표준적인 패턴입니다.
- 비즈니스 데이터를 DB에
INSERT/UPDATE합니다. - 동일한 트랜잭션 내에서 이벤트를
outbox테이블에INSERT합니다. - Debezium과 같은 CDC(Change Data Capture) 도구나 별도의 폴링 프로세스가
outbox테이블을 읽어 카프카로 메시지를 발행합니다.
코레오그래피 vs 오케스트레이션 비교
사가 패턴을 구현하는 두 가지 주요 접근 방식의 장단점 비교입니다.
| 구분 | 코레오그래피 (Choreography) | 오케스트레이션 (Orchestration) |
|---|---|---|
| 제어 흐름 | 각 서비스가 이벤트를 구독하고 반응 (Decentralized) | 중앙의 Orchestrator가 모든 프로세스를 통제 (Centralized) |
| 결합도 | 낮음 (Loose Coupling) | 높음 (서비스가 Orchestrator에 의존) |
| 복잡도 | 참여자 수가 늘어나면 흐름 파악이 난해함 (Cyclic Dependency 위험) | 전체 비즈니스 로직을 한곳에서 파악 가능 |
| 책임 소재 | 분산되어 있어 모니터링이 어려움 | 오케스트레이터가 트랜잭션 상태 관리 및 보상 처리 담당 |
결론: 결과적 일관성(Eventual Consistency)의 수용
이벤트 기반 아키텍처에서 강한 일관성(Strong Consistency)을 고집하는 것은 시스템의 가용성과 성능을 심각하게 저하시킬 수 있습니다. 멱등성 설계를 통해 중복 처리를 방어하고, 사가 패턴과 Outbox 패턴을 통해 데이터의 신뢰성을 확보함으로써, 시스템은 "결과적 일관성" 상태에 도달하게 됩니다. 비동기 통신의 에러 처리를 위한 Dead Letter Queue(DLQ) 전략까지 함께 수립되어야 견고한 분산 시스템이 완성됩니다.
Post a Comment