Transactional Outbox Pattern을 설명해 주세요.

이 질문은 Kafka나 메시지 큐를 이력서에 적어둔 분들에게 꽤 자주 이어질 수 있는 질문입니다.

단순히

“이벤트를 발행했습니다”

“Kafka로 비동기 처리했습니다”

라고 말하면 면접관은 보통 이렇게 물어볼 수 있습니다.

“DB 저장은 성공했는데 Kafka 발행이 실패하면 어떻게 하나요?”

“반대로 메시지는 발행됐는데 DB 트랜잭션이 롤백되면요?”

“DB와 Kafka를 하나의 트랜잭션으로 묶을 수 있나요?”

이 질문은 메시지 큐 사용 경험보다,

DB 변경과 이벤트 발행 사이의 정합성 문제를 이해하고 있는지 확인하는 질문에 가깝습니다.

같이 체크해보면 좋은 포인트는 아래와 같습니다.

DB 저장과 메시지 발행은 왜 원자적으로 묶기 어려운가?

Outbox 테이블에는 어떤 데이터를 저장해야 하는가?

이벤트 발행 실패 시 어떻게 재시도할 것인가?

같은 이벤트가 여러 번 발행되면 Consumer는 어떻게 중복 처리할 것인가?

Outbox 테이블이 계속 커질 때 어떻게 관리할 것인가?

꼬리질문으로는 이런 질문이 이어질 수 있습니다.

ㄴ> Outbox Pattern은 어떤 문제를 해결하나요?

ㄴ> Outbox 테이블에는 어떤 컬럼이 필요할까요?

ㄴ> Outbox 이벤트를 Kafka로 발행하는 방식은 Polling 방식인가요, CDC 방식인가요?

ㄴ> 메시지가 중복 발행되면 Consumer는 어떻게 멱등성을 보장하나요?

ㄴ> Outbox 테이블이 커지면 성능 저하는 어떻게 방지하나요?

ㄴ> Saga Pattern과 Outbox Pattern은 각각 어떤 문제를 해결하나요? 

백엔드 면접에서 메시지 큐(Kafka, RabbitMQ 등)를 활용한 아키텍처를 언급했을 때, 꼬리 질문으로 가장 나오기 좋은 핵심 주제입니다. 질문 주신 내용들을 흐름에 따라 5가지 핵심 케이스(개념, 설계, 발행, 소비, 비교)로 나누어 명쾌하게 정리해 드리겠습니다.

1. 원자성 결여의 문제 (핵심 배경)

Q. DB 저장과 메시지 발행은 왜 원자적(Atomic)으로 묶기 어렵나요?

Q. Outbox Pattern은 어떤 문제를 해결하나요?

2. Outbox 테이블 설계 및 대용량 관리

Q. Outbox 테이블에는 어떤 컬럼이 필요할까요?

이벤트의 추적성, 순서 보장, 재시도를 위해 최소한 아래와 같은 스키마 구조가 필요합니다.

컬럼명 타입 설명
id UUID 또는 BigInt 이벤트를 식별하는 고유 PK (Idempotency Key로 활용 가능)
aggregate_type VARCHAR 이벤트가 발생한 도메인 영역 (예: Order, Member)
aggregate_id VARCHAR 해당 도메인의 식별자 (예: order_id)
event_type VARCHAR 이벤트 종류 (예: OrderCreated, OrderCanceled)
payload TEXT 또는 JSONB 메시지에 담을 실제 데이터 (Serialized Data)
status VARCHAR 발행 상태 관리 (PENDING, PROCESSED, FAILED)
created_at TIMESTAMP 이벤트 생성 시간 (순서 보장 및 디버깅용)

Q. Outbox 테이블이 커지면 성능 저하는 어떻게 방지하나요?

데이터가 무한히 쌓이면 PENDING 상태를 조회하거나 인덱스를 유지하는 데 비용이 커집니다.