노트

트랜잭셔널 아웃박스 패턴

Transactional Outbox Pattern

백엔드#architecture#db · 연결된 개념 11개

쉽게 말하면

트랜잭셔널 아웃박스는 물건을 팔 때 판매 장부에 '배송 보낼 것'까지 같이 적어 두고, 배송 담당이 그 장부를 훑어 내보내는 방식이에요. 팔았는데 배송 지시만 빠지는 일이 생기지 않아요.

비유가 깨지는 곳 배송 담당이 보내 놓고 완료 표시를 하기 전에 쓰러지면 같은 이벤트를 또 보내요. 그래서 소비자는 멱등해야 하고, 시스템 전체는 즉시가 아니라 최종 일관성으로 맞춰져요.

DB 변경과 이벤트 발행을 안전하게 함께 처리하는 패턴이다. 실제 데이터 변경과 "이 이벤트를 내보내라"는 기록(outbox 행)을 같은 DB 트랜잭션에 넣고, 별도 워커가 outbox를 읽어 메시지 브로커로 보낸다.

풀려는 문제: 이중 쓰기

① DB에 주문 저장 (커밋 성공)
② 브로커에 "주문 생성됨" 발행 (네트워크 오류로 실패)
→ 주문은 있는데 아무도 모른다

순서를 바꿔도 마찬가지다. 서로 다른 두 시스템은 한 트랜잭션으로 묶을 수 없으므로, 둘 중 하나만 성공하는 순간이 반드시 생긴다.

해결 구조

[한 트랜잭션]
  INSERT INTO orders ...
  INSERT INTO outbox (aggregate_id, type, payload) ...
        ↓ 둘 다 커밋되거나 둘 다 롤백
[릴레이 워커(message relay)] outbox 폴링(또는 CDC로 읽기) → 브로커 발행 → 처리 완료 표시
  • 원자성 덕분에 데이터 변경과 "할 일"이 함께 남는다. 스프링이라면 같은 @Transactional 안에서 두 행을 저장하면 된다
  • 워커가 발행 후 완료 표시 전에 죽으면 같은 이벤트를 다시 보낸다. 그래서 소비자는 멱등해야 한다
  • outbox 테이블을 CDC (변경 데이터 캡처)로 읽으면 폴링(polling) 없이 거의 실시간으로 내보낼 수 있다

언제 쓰나

주문·결제처럼 "DB에 반영됐다면 반드시 다른 서비스도 알아야 하는" 흐름에 쓴다. 이벤트 형태를 테이블 구조와 분리해 도메인 이벤트로 설계할 수 있다는 점이 CDC 단독 방식과 다르다. 애그리거트 단위로 이벤트를 내는 DDD(Domain-Driven Design) 설계와 잘 맞는다.

결과적으로 시스템은 최종 일관성을 받아들인다. 메시지 전달 쪽은 Kafka를 본다.

출처: microservices.io: Transactional outbox

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 예외 삼키기

    예외를 잡아 놓고 아무 처리 없이 흘려보내는 안티패턴(Anti-pattern). 빈 catch뿐 아니라 debug 레벨로만 찍고 정상인 척 진행하는 것도 마찬가지다.

  • OT (연산 변환)

    여러 사용자가 같은 문서를 동시에 편집할 때, 다른 사람의 연산이 먼저 적용됐다는 사실에 맞춰 내 연산을 변환(transform)해서 모두가 같은 결과에 이르게 하는 방식. 실시간 협업 편집기에서 오래 쓰여 왔다.

  • 옵저버 패턴

    한 객체(발행자, Publisher)의 상태가 바뀌면 그것을 구독한 객체(구독자, Subscriber)들에게 자동으로 알리는 패턴. 발행자는 구독자가 누구인지 구체적으로 몰라도 된다.

  • 데이터베이스 다중화

    같은 데이터를 여러 DB 서버에 복제해 두는 것. 가장 흔한 형태는 원본을 가진 주(Primary) 서버가 쓰기를 받고, 사본을 받는 부(Replica) 서버들이 읽기를 나눠 맡는 구조다.

  • 구조와 동작, 옵션의 가치

    켄트 벡이 정리 시점을 판단하려고 꺼내는 경제학 틀. 소프트웨어는 두 가지 가치를 만든다. 오늘 하는 일(동작)과, 내일 새로 할 수 있게 되는 일(옵션)이다. 구조는 동작을 바꾸지 않지만 옵션을 만든다.

보기 옵션