
Outbox Pattern아웃박스 패턴
아웃박스 패턴(Outbox Pattern)은 데이터베이스 트랜잭션과 메시지 발행의 원자성을 보장하는 패턴이다. DB 업데이트와 메시지 브로커 발행을 별도 트랜잭션으로 수행할 때 생기는 불일치를 해결한다.
문제 정의
문제: DB 업데이트 성공 + 메시지 발행 실패 = 불일치
// 잘못된 코드
await db.updateOrder(order); // DB 성공
await kafka.publish(orderEvent); // 실패하면?
// → 주문은 처리됐는데 이벤트 없음해결책: 아웃박스
sql
-- 아웃박스 테이블
CREATE TABLE outbox (
id UUID PRIMARY KEY,
topic VARCHAR(255),
payload JSONB,
created_at TIMESTAMP,
published BOOLEAN DEFAULT FALSE
);
-- 비즈니스 로직과 같은 트랜잭션에서 삽입
BEGIN;
UPDATE orders SET status = 'PAID' WHERE id = :orderId;
INSERT INTO outbox (id, topic, payload)
VALUES (gen_random_uuid(), 'order.paid',
'{"orderId": 123, "amount": 5000}');
COMMIT;
-- 둘 다 성공하거나 둘 다 실패 (원자성 보장)릴레이 프로세스
┌─────────────┐ 트랜잭션 ┌────────────┐
│ 비즈니스 로직│ ─────────────▶│ DB (주문) │
└─────────────┘ │ DB (아웃박스)│
└──────┬─────┘
│ 폴링/CDC
┌──────▼─────┐
│ 릴레이 서비스│
└──────┬─────┘
│
┌──────▼─────┐
│ Kafka/MQ │
└────────────┘
Debezium CDC 활용:
MySQL binlog / PostgreSQL WAL 감지 →
outbox 테이블 변경 → 자동 Kafka 발행Spring Boot 구현
java
@Service
@Transactional
public class OrderService {
public void processOrder(Order order) {
// 1. 비즈니스 로직
orderRepository.save(order);
// 2. 아웃박스에 이벤트 삽입 (같은 트랜잭션)
OutboxEvent event = OutboxEvent.builder()
.topic("order.paid")
.payload(toJson(new OrderPaidEvent(order.getId())))
.build();
outboxRepository.save(event);
// 트랜잭션 커밋 시 둘 다 성공 또는 실패
}
}
// 릴레이 스케줄러
@Scheduled(fixedDelay = 1000)
public void relayOutboxEvents() {
List<OutboxEvent> events = outboxRepository
.findByPublishedFalse();
for (OutboxEvent event : events) {
kafkaTemplate.send(event.getTopic(), event.getPayload());
event.setPublished(true);
}
}아웃박스 vs 사가(Saga)
| 비교 | 아웃박스 | 사가 |
|---|---|---|
| 보장 | At-least-once 발행 | 분산 트랜잭션 |
| 범위 | 단일 서비스 | 여러 서비스 |
| 복잡도 | 낮음 | 높음 |