백엔드 면접 용어 사전
메시지 큐Outbox 패턴

Transactional Outbox

이벤트를 업무 트랜잭션과 같은 DB에 먼저 기록하고 별도 프로세스가 발행하는 패턴.

이벤트를 업무 데이터와 같은 DB, 같은 트랜잭션으로 기록한 뒤, 별도 프로세스가 읽어서 발행하는 패턴. dual-write 문제의 표준 해법이다.

구조

다이어그램 로딩 중…

outbox 테이블

CREATE TABLE outbox (
  id           BIGINT PRIMARY KEY,
  aggregate_id VARCHAR(64),     -- 어떤 주문에 대한 것인지 (파티션 키로 사용)
  event_type   VARCHAR(64),
  payload      JSON,
  created_at   TIMESTAMP,
  published_at TIMESTAMP NULL   -- NULL이면 아직 안 보냄
);

저장 시점

@Transactional
public void createOrder(Order order) {
    orderRepository.save(order);              // 업무 데이터
    outboxRepository.save(toEvent(order));    // 이벤트 — 같은 트랜잭션
}   // 커밋 → 둘 다 남거나, 둘 다 없거나

하나의 트랜잭션이므로 "주문은 있는데 이벤트는 없다"가 원리적으로 불가능하다.

릴레이 — 두 가지 방식

① 폴링

SELECT * FROM outbox WHERE published_at IS NULL ORDER BY id LIMIT 100;

간단하지만 주기적 쿼리 부담이 있고, 여러 인스턴스가 돌면 중복 발행을 막아야 한다 (FOR UPDATE SKIP LOCKED 활용).

② CDC (Change Data Capture) Debezium 같은 도구가 DB의 트랜잭션 로그(MySQL binlog, PostgreSQL WAL)를 읽어 outbox 삽입을 감지해 발행한다. 폴링 부담이 없고 지연이 짧다.

남는 특성 — At-Least-Once

발행에 성공하고 published_at을 기록하기 직전에 죽으면 다시 발행된다. 그래서 소비자는 여전히 멱등해야 한다. Outbox가 해결한 것은 유실이지 중복이 아니다.

운영에서 챙길 것

  • 발행 지연 모니터링created_at과 현재 시각의 차이가 벌어지면 릴레이 장애
  • 테이블 정리 — 발행된 행이 무한히 쌓이므로 주기적으로 삭제하거나 파티셔닝
  • 순서 — 같은 aggregate_id는 순서대로 발행해야 한다. id 순서대로 읽고, 파티션 키로 aggregate_id를 쓰면 소비 측 순서까지 보장된다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 메시지 큐·비동기 — Kafka/RabbitMQ·EOS·순서·outbox