- [Backend] Transactional Outbox Pattern2026년 01월 19일
- 아몬드맛빼빼로
- 작성자
- 2026.01.19.:52

Transactional Outbox Pattern
트랜젝셔널 아웃박스 패턴(Transactional Outbox Pattern)이란 분산 시스템에서 트랜젝션과 이벤트 발행이 원자적으로 진행되지 않아 발생하는 문제를 해결하기 위한 디자인 패턴이다. 예를 하나 들어보자,
@Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; private final KafkaTemplate<String, Object> kafkaTemplate; @Transactional public void createOrder(OrderRequest request) { Order order = request.toEntity(); orderRepository.save(order); kafkaTemplate.send("order-topic", new OrderCreatedEvent(order)); } }이런 코드가 있을 때 만약 이벤트 발행 후 네트워크 타임아웃이 발생한다면 DB에는 트랜젝션이 커밋되지만 이벤트는 발행되지 않게 된다. 반대의 경우도 마찬가지로 이벤트는 발행되어 성공했지만 트랜젝션이 실패할 수도 있는 것이다. 이로 인하여 데이터 정합성에 문제가 발생하고 결국 서비스에 장애를 일으키거나 사용자 데이터에 이상이 생길 수 있다.
설명
트랜젝션이 포함된 비즈니스 로직과 이벤트 발행을 원자적으로 함께 수행하도록 디자인하는 것이 해당 패턴이며 이렇게 디자인한 것을 랜잭셔널 메시징(Transactional Messaing)이라고 한다.
먼저 아웃박스 테이블을 정의한다. 이는 이벤트의 내용을 저장하고 발행 여부를 추적할 테이블이다.
@Entity @Getter @NoArgsConstructor @Table(name = "outbox_events") public class OutboxEvent { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String aggregateType; // 예: "ORDER" private Long aggregateId; // 예: orderId private String type; // 예: "ORDER_CREATED" @Lob private String payload; // 이벤트 내용 (JSON 문자열 등) private boolean published; private LocalDateTime createdAt; public void markAsPublished() { this.published = true; } }이렇게 구성한다면 더 이상 서비스는 직접 Kafka 호출을 시도하지 않는다. 대신 아웃박스 테이블에 이벤트를 저장하며 @Transactional 덕분에 주문과 이벤트는 "둘 다 성공하거나, 둘 다 실패"한다.
@Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; private final OutboxEventRepository outboxEventRepository; private final ObjectMapper objectMapper; @Transactional public void createOrder(OrderRequest request) throws JsonProcessingException { Order order = request.toEntity(); orderRepository.save(order); String payload = objectMapper.writeValueAsString(new OrderCreatedEvent(order)); OutboxEvent event = new OutboxEvent( "ORDER", order.getId(), "ORDER_CREATED", payload ); outboxEventRepository.save(event); } }이후 별도의 스케쥴러와 같은 기능을 이용해 메세징을 릴레잉 시킨다. 이것들이 아웃박스 테이블을 테이블을 주기적으로 검사하여 카프카로 메시지를 보낸다.
@Component @RequiredArgsConstructor @Slf4j public class OutboxMessageRelay { private final OutboxEventRepository outboxEventRepository; private final KafkaTemplate<String, Object> kafkaTemplate; @Scheduled(fixedDelay = 5000) public void publishEvents() { List<OutboxEvent> events = outboxEventRepository.findByPublishedFalse(); for (OutboxEvent event : events) { try { kafkaTemplate.send("order-topic", event.getPayload()).get(); event.markAsPublished(); outboxEventRepository.save(event); log.info("Event published: {}", event.getId()); } catch (Exception e) { log.error("Failed to publish event: {}", event.getId(), e); } } } }마무리
트랜잭셔널 아웃박스 패턴은 비즈니스 로직 처리와 이벤트 발행을 하나의 데이터베이스 트랜잭션으로 묶어, 분산 시스템에서 흔히 발생하는 데이터 불일치 문제를 근본적으로 해결해 준다. 별도의 폴링 프로세스를 통해 일시적인 중개인 장애가 발생하더라도 끊임없는 재시도를 통해 결과적 일관성(Eventual Consistency)을 보장한다는 점이 이 패턴의 핵심 가치이다. 다만, 서비스 규모가 커질수록 DB 폴링 부하를 줄이기 위한 CDC(Change Data Capture) 도입이나 중복 발송에 대비한 컨슈머 측의 멱등성 설계까지 고려해야 진정으로 견고한 이벤트 기반 아키텍처를 완성할 수 있을 것이다.
반응형'Backend' 카테고리의 다른 글
[Backend] 캐시 스탬피드 (0) 2026.01.22 [Backend] 일급 컬렉션 (0) 2026.01.21 [Backend] MSA에서의 트랜젝션 (0) 2026.01.13 [Backend] SameSite 쿠키 (0) 2025.12.22 [Backend] 다중 서버 환경에서 세션 기반 인증 방식 (0) 2025.12.19 다음글이전글이전 글이 없습니다.댓글