--- title: concept / Transactional Outbox Pattern source_type: llm-generated status: reviewed confidence: high tags: [concept, ca-tmpl, messaging, kafka, outbox-pattern] related_projects: [ca-tmpl] last_reviewed: 2026-06-15 --- # concept / Transactional Outbox Pattern ## Summary 로컬 트랜잭션의 일부로 비즈니스 상태 변경과 이벤트를 동일한 데이터베이스(Outbox 테이블)에 저장한 후, 독립적인 프로세스(Outbox Relay)가 이 이벤트를 비동기적으로 메시지 브로커(Kafka 등)로 발행하는 디자인 패턴. - 이를 통해 분산 환경에서 비즈니스 로직 성공과 메시지 발행 간의 원자성(Atomicity)을 보장하고, 이중 쓰기(Dual-Write) 안티패턴을 방지한다. ## Standard (공식 정의) - **Dual-Write Anti-Pattern**: 하나의 비즈니스 유스케이스 내에서 데이터베이스 업데이트와 외부 메시지 발행을 동시에 시도하는 방식. 데이터베이스 트랜잭션은 커밋되었으나 브로커 연결 실패로 메시지가 유실되거나, 반대로 메시지는 발행되었으나 데이터베이스 커밋이 롤백되는 불일치 문제가 상존한다. - **Transactional Outbox**: 1. 비즈니스 원장 데이터 수정과 함께, 발행할 메시지를 동일 트랜잭션 하에서 `Outbox` 테이블에 인서트한다. (DB 로컬 트랜잭션의 원자성으로 인해 메시지 저장도 100% 보장된다.) 2. 별도의 백그라운드 워커(Outbox Relay)가 Outbox 테이블을 주기적으로 폴링(또는 CDC를 활용)하여 `PENDING` 상태의 이벤트를 읽어온다. 3. 릴레이 워커가 메시지를 브로커로 발행(Publish)한 뒤, 데이터베이스에 해당 Outbox 레코드를 `COMPLETED` 등으로 상태를 업데이트하거나 삭제한다. ## 한계 / 주의점 - **중복 메시지 발행 (At-Least-Once Delivery)**: 릴레이가 브로커에 메시지를 정상적으로 보냈으나, DB에 상태를 `COMPLETED`로 업데이트하기 직전에 시스템이 다운되면 동일한 메시지가 재전송될 수 있다. 따라서 소비처(Consumer)는 반드시 **멱등적 메시지 처리(Idempotent Consumer)** 구조를 갖춰야 한다. - **순서 보장 (Ordering)**: 멀티 스레드로 릴레이를 돌릴 때 동일 Aggregate의 이벤트가 뒤집혀서 발행되지 않도록 Aggregate ID 기반의 분산 락이나 시퀀스 제어가 필요할 수 있다. ## Project Application - [[wiki/explainer/adapter-outbound.md]] - 우리 프로젝트에서는 메시지 발행 시 직접 발행과 아웃복스 릴레이 발행의 결합을 지원함. - **직접 발행 (`KafkaMessagePublisher`)**: 비즈니스 트랜잭션 흐름 중 메시지를 즉시 발행함. 이미 로컬 DB 트랜잭션에 아웃복스가 커밋되므로, 실시간 발행은 **Fail-Open** 계약을 맺어 예외가 발생하더라도 사용자 API를 중단시키지 않고 백그라운드 릴레이에 유실 복구를 위임함. - **릴레이 발행 (`KafkaOutboxMessagePublishAdapter`)**: 백그라운드에서 Outbox 레코드를 전달받아 브로커에 실제 전달하는 역할. 브로커가 장애를 내면 반드시 예외를 다시 던지는 **Fail-Closed** 계약을 가짐. 예외가 전파되어야 릴레이 트랜잭션이 롤백되어 해당 레코드가 `IN_FLIGHT`에 고립되지 않고 재시도(Retry) 루프를 타거나 운영 경보(Runbook)가 정상 작동하기 때문임. ## Claim-backed Knowledge | Knowledge Point | Supporting Claims | Confidence | Notes | |---|---|---|---| | 이중 쓰기(Dual-write)의 근본적 문제점과 일관성 결여 | `raw/official-docs/dual-write-antipattern-microservices-io.md` | `high` | 마이크로서비스 데이터 패턴 | | 트랜잭셔널 아웃복스 패턴의 기본 구성 요소 | `raw/official-docs/transactional-outbox-aws-prescriptive-guidance.md` | `high` | AWS 마이크로서비스 설계 패턴 | | Outbox 데이터 상태 변경 및 중복 처리 주의점 | `raw/official-docs/microservices-io-transactional-outbox.md` | `high` | Microservices.io 패턴 정의 | ## 내가 설명할 수 있어야 하는 것 - 이중 쓰기(Dual-Write)의 위험성과 이를 아웃복스 패턴이 어떻게 해결하는지 메커니즘을 상세히 설명할 수 있어야 함. - 실시간 API 단의 메시지 발행기와 백그라운드 릴레이 단의 메시지 발행기가 예외 처리 정책(Fail-Open vs Fail-Closed)을 다르게 맺는 이유는 무엇인가? - 카프카 외에 다른 메시징 시스템(RabbitMQ, AWS SQS)으로 아웃복스 발행기를 대체하려면 어떻게 설계해야 하는가? (Port-Adapter 인터페이스 구현을 통해 어댑터만 교체) ## Interview Questions - 메시지 큐와 RDB를 동시에 업데이트할 때 발생할 수 있는 데이터 정합성 문제와 이를 해결하기 위한 Transactional Outbox Pattern에 대해 설명해 주세요. - 아웃복스 릴레이 컴포넌트의 실패 상황 시 가용성과 정합성 설계 관점에서 어떻게 실패 복구를 처리해야 하는지 설명하십시오. ## Do Not Overclaim - "아웃복스 패턴을 도입했으므로 분산 트레이싱 환경에서 완벽한 1회성 전송(Exactly-Once)을 달성할 수 있다"고 장담하면 안 된다. 분산 네트워크 상에서 릴레이 DB 업데이트 실패 시 중복 메시지가 무조건 나갈 수 있으므로, 최종 소비자의 멱등 수신 설계가 반드시 동반되어야 보장된다. ## Sources - [Microservices.io - Transactional Outbox](https://microservices.io/patterns/data/transactional-outbox.html) - [[raw/official-docs/dual-write-antipattern-microservices-io.md]] - [[raw/official-docs/transactional-outbox-aws-prescriptive-guidance.md]]