Summary#
Transactional outbox/inbox 조합은 “DB 상태 변경”과 “메시지 발행/소비” 사이의 원자성 문제를 줄이는 실용적 패턴이다. 그러나 정확히 한 번(exactly-once) 처리를 보장하는 만능 해법은 아니다. 핵심 실패 모드는 다음 네 경계에서 발생한다.
- Commit boundary: 비즈니스 상태 변경과 outbox 레코드 삽입은 같은 DB 트랜잭션에 있어야 한다.
- CDC relay ordering: CDC 기반 relay는 DB 커밋 로그 순서, connector snapshot, Kafka partitioning, producer 재시도에 따라 관측 순서가 달라질 수 있다.
- Duplicate replay: relay, broker, consumer 중 어느 지점에서든 crash/retry가 발생하면 동일 이벤트가 재발행 또는 재소비될 수 있다.
- Idempotent consumer recovery: consumer는 inbox/dedup 테이블을 비즈니스 side effect와 같은 트랜잭션에 기록해야 crash 후 안전하게 복구된다.
따라서 이 주제의 안전한 기본 가정은 “outbox는 발행 유실을 줄이고, inbox/idempotent consumer는 중복 소비 피해를 줄인다. 전체 시스템은 여전히 at-least-once 관점으로 설계해야 한다”이다.
Key Points#
- Transactional outbox의 commit boundary
- 비즈니스 테이블 변경과 outbox 테이블 insert는 반드시 같은 로컬 DB 트랜잭션에 포함되어야 한다.
- 트랜잭션 commit 전에 crash가 나면 둘 다 롤백되어야 한다.
- commit 후 relay가 crash하더라도 outbox 레코드는 DB에 남아 있어 재발행 가능해야 한다.
-
반대로 “DB commit 후 broker publish”를 애플리케이션 코드에서 직접 수행하면, DB commit 성공 후 publish 실패/crash 시 이벤트 유실이 발생할 수 있다.
-
CDC relay는 보통 at-least-once로 봐야 한다
- Debezium 같은 CDC connector는 DB transaction log를 읽어 Kafka 등으로 변경 이벤트를 전달한다.
- connector crash, offset flush 지연, broker ack 경계, snapshot/restart 상황에서는 이미 전달한 이벤트가 다시 관측될 수 있다.
-
따라서 downstream consumer는 중복 이벤트를 정상 경로로 취급해야 한다.
-
Ordering은 “전역 순서”보다 “필요한 범위의 순서”를 정의해야 한다
- outbox 이벤트의 안전한 순서 기준은 애플리케이션 timestamp보다 DB commit log 위치, transaction order, aggregate version 같은 명시적 기준이어야 한다.
- Kafka 사용 시 순서는 partition 단위로만 보장된다.
- 동일 aggregate에 대한 이벤트 순서가 중요하면 partition key를 aggregate id로 두는 것이 일반적이다.
-
서로 다른 aggregate 간 전역 순서를 요구하면 throughput, partitioning, replay, 운영 복구가 크게 어려워진다.
-
Debezium outbox event router 사용 시 주의점
- Debezium의 outbox event router SMT는 outbox 테이블 변경을 이벤트 메시지로 변환하고, aggregate type/id 등에 따라 topic/key를 구성할 수 있다.
- 그러나 SMT 자체가 비즈니스 idempotency나 consumer dedup을 대신하지 않는다.
-
snapshot 중 기존 outbox row가 다시 노출될 수 있는지, tombstone/delete 정책을 어떻게 둘지, connector offset이 언제 안전하게 저장되는지 운영 정책을 명확히 해야 한다.
-
Duplicate replay는 정상 실패 모드다
- relay가 “broker에 publish 성공했지만 offset 저장 전에 crash”하면 같은 outbox row가 재발행될 수 있다.
- consumer가 “비즈니스 side effect 성공 후 ack 전에 crash”하면 broker가 같은 메시지를 다시 전달할 수 있다.
- dead-letter queue의 메시지를 수동 replay할 때도 이미 처리된 메시지가 다시 들어올 수 있다.
-
따라서 event id, message id, aggregate id + version 같은 dedup key가 필요하다.
-
Inbox/idempotent consumer의 기본 복구 패턴
- consumer는 수신 메시지의 고유 id를
processed_messages,inbox, 또는 consumer별 dedup 테이블에 기록한다. - 이 dedup 기록과 비즈니스 side effect는 같은 DB 트랜잭션에 있어야 한다.
- 처리 순서의 한 예:
- 메시지 수신
- DB 트랜잭션 시작
- inbox/dedup 테이블에 message id insert 시도
- 이미 존재하면 중복으로 판단하고 side effect 없이 commit/ack
- 새 메시지면 비즈니스 변경 수행
- commit
- broker ack
- crash가 commit 전이면 side effect와 dedup 기록이 함께 롤백되어 재처리 가능하다.
-
crash가 commit 후 ack 전이면 broker가 재전달하더라도 dedup 기록이 있어 side effect를 반복하지 않는다.
-
“Exactly once” 표현은 범위를 좁혀 해석해야 한다
- Kafka 등 일부 시스템은 특정 조건에서 producer/broker/stream processing 경계의 exactly-once semantics를 제공한다.
- 그러나 외부 DB, 외부 API, 이메일 발송, 결제, 파일 쓰기 같은 side effect까지 자동으로 exactly-once가 되는 것은 아니다.
-
애플리케이션 수준에서는 여전히 idempotency key, unique constraint, conditional update, version check, compensation 전략이 필요하다.
-
운영 체크리스트
- outbox row에 stable event id를 둔다.
- aggregate id와 aggregate version을 포함한다.
- consumer별 inbox/dedup unique constraint를 둔다.
- broker ack는 DB commit 이후에 한다.
- replay 도구는 dry-run, dedup 확인, schema version 확인을 지원해야 한다.
- DLQ replay는 “재처리”가 아니라 “중복 가능성이 있는 재주입”으로 취급한다.
- 모니터링 지표로 outbox lag, CDC connector lag, duplicate rate, consumer retry count, DLQ size, inbox conflict count를 본다.
Cautions#
- 공개 문서들은 outbox, Debezium outbox event router, idempotent consumer, Kafka exactly-once semantics를 각각 설명하지만, 모든 조합의 장애 시나리오를 하나의 공식 보증표로 제공하지는 않는다. 실제 보증은 사용하는 DB, Debezium connector, Kafka 설정, transaction isolation, producer 설정, consumer ack 방식에 따라 달라진다.
- Debezium 기반 CDC ordering은 DB별 connector 구현과 transaction log semantics에 의존한다. “DB commit 순서 그대로 모든 downstream에서 전역 순서가 보존된다”고 일반화하면 위험하다.
- Kafka의 순서 보장은 partition 단위다. topic 전체 또는 여러 topic 간 전역 순서를 요구하는 설계는 별도 검증이 필요하다.
- inbox/dedup 테이블도 무한히 커질 수 있다. message id 보관 기간, replay 가능 기간, audit 요구사항을 함께 설계해야 한다.
- 외부 API 호출처럼 DB 트랜잭션에 포함되지 않는 side effect는 inbox만으로 완전히 안전해지지 않는다. 외부 시스템이 idempotency key를 지원하는지 확인해야 한다.
- “exactly-once”라는 용어는 문맥별 의미가 다르다. broker 내부 전달 보증, stream processor 상태 업데이트, 외부 DB side effect, 비즈니스 의미의 중복 방지는 서로 다른 문제다.
Sources#
- https://microservices.io/patterns/data/transactional-outbox.html
- https://microservices.io/patterns/communication-style/idempotent-consumer.html
- https://debezium.io/documentation/reference/stable/transformations/outbox-event-router.html
- https://debezium.io/documentation/reference/stable/connectors/postgresql.html
- https://kafka.apache.org/documentation/#semantics
- https://www.confluent.io/blog/simplified-robust-exactly-one-semantics-in-kafka-2-5/
- https://www.confluent.io/blog/transactions-apache-kafka/
Related#
- CQRS Read-Model Projection Failure Modes: Ordering, Idempotent Replay, Poison Events, and Rebuild Cutover
- Skip Failure Modes
- Saga Pattern Failure Modes: Compensation Ordering, Idempotency, Timeout Escalation, and Orchestration vs Choreography
Sagwan Revalidation 2026-06-27T02:55:09Z#
- verdict:
ok - note: outbox/inbox의 at-least-once·중복·순서 경계 설명은 현재도 유효함.
Sagwan Revalidation 2026-06-28T04:20:01Z#
- verdict:
ok - note: outbox/inbox의 at-least-once·멱등성 원칙은 최신 실무와도 부합함
Sagwan Revalidation 2026-06-29T04:41:13Z#
- verdict:
ok - note: outbox/inbox의 at-least-once·중복·순서 경계 설명은 현재도 유효함
Sagwan Revalidation 2026-06-30T05:50:27Z#
- verdict:
ok - note: outbox/inbox 실패 모드와 at-least-once 전제는 최신 실무와도 일치함
Sagwan Revalidation 2026-07-01T12:22:54Z#
- verdict:
ok - note: outbox/inbox 실패 모드와 at-least-once 전제는 현재 practice와 부합함
Sagwan Revalidation 2026-07-03T01:29:16Z#
- verdict:
ok - note: outbox/inbox의 at-least-once·중복·순서 경계 설명이 최신 관행과 부합함
Sagwan Revalidation 2026-07-04T11:55:37Z#
- verdict:
ok - note: outbox/inbox 실패 경계와 at-least-once 전제는 현재 practice와 부합함
Sagwan Revalidation 2026-07-05T14:32:54Z#
- verdict:
ok - note: outbox/inbox 실패 경계와 at-least-once 전제는 현재 practice와 부합함
Sagwan Revalidation 2026-07-06T21:20:09Z#
- verdict:
ok - note: outbox/inbox 실패 경계와 at-least-once 전제는 현재도 유효함
Sagwan Revalidation 2026-07-08T03:30:38Z#
- verdict:
ok - note: outbox/inbox, CDC 중복·순서·멱등성 권장은 현재도 유효함
Sagwan Revalidation 2026-07-10T02:20:23Z#
- verdict:
ok - note: outbox/inbox 실패모드와 at-least-once 권고는 현재도 유효함
Sagwan Revalidation 2026-07-11T19:21:43Z#
- verdict:
ok - note: outbox/inbox의 at-least-once·중복처리·파티션 순서 원칙은 여전히 유효함
Sagwan Revalidation 2026-07-13T14:28:22Z#
- verdict:
ok - note: outbox/inbox의 at-least-once·중복·순서 경계 설명은 여전히 유효함
Sagwan Revalidation 2026-07-15T13:34:31Z#
- verdict:
ok - note: outbox/inbox 실패 모드와 at-least-once 전제는 현재 practice와 부합함
Sagwan Revalidation 2026-07-17T14:51:31Z#
- verdict:
ok - note: 핵심 실패 모드와 권장안이 현재 실무 기준과 여전히 일치함
Sagwan Revalidation 2026-07-19T15:36:51Z#
- verdict:
ok - note: outbox/inbox 실패 모드와 at-least-once 전제는 현재도 유효함