Skip to Content
Backend ArchitectureTransactional Outbox의 전달 보장

Transactional Outbox의 전달 보장

정정: 이전의 “완전한 신뢰성”, “단일 스케줄러면 중복 처리 없음”이라는 표현은 전달 성공과 중복 처리의 조건을 생략했다. 아래에서 저장 원자성과 외부 전달을 분리한다.

원자적으로 저장하는 것은 전송 의도다

도메인 데이터와 메시지 전송 요청을 같은 DB 트랜잭션에 저장한다. 릴레이는 확정된 요청을 읽어 외부로 전송한다. 한쪽만 저장되는 이중 쓰기를 줄이는 패턴이다. DB 커밋과 브로커 전송을 하나의 원자적 커밋으로 바꾸는 것은 아니다. Chris Richardson의 패턴 설명 

@Transactional public void complete(long jobId) { jobRepository.complete(jobId); outboxRepository.append(new OutgoingEvent(jobId)); }

두 저장이 같은 트랜잭션 관리자·DB에 참여하는 일반화한 예제다. 서로 다른 DB나 별도 트랜잭션에 저장하면 이 원자성이 성립하지 않는다.

왜 커밋 뒤에 Outbox를 저장하지 않는가

데이터를 먼저 확정한 뒤 Outbox를 쓰면 그 사이에 종료될 때 전송 요청이 남지 않는다. 반대로 같은 트랜잭션의 Outbox 저장 실패는 도메인 커밋에도 영향을 준다. 이 비용을 허용하는 업무인지 결정해야 한다.

전송 성공과 완료 기록 사이에 틈이 있다

종료·실패 구간다음 시도
전송 전미전송 요청을 다시 읽음
전송 실패재시도 정책에 따라 다시 전송
전송 성공 후 완료 기록 전이미 전달한 이벤트를 다시 보낼 수 있음
완료 기록 후보존·정리 정책에 따라 처리

단일 릴레이여도 세 번째 구간의 중복은 가능하다. 여러 릴레이를 사용하면 작업 선점·락·리스 만료도 필요하다. 어떤 방식을 쓰든 소비자의 멱등 처리와 이벤트 식별자가 중요하다.

즉시 전송과 릴레이를 함께 사용할 때

커밋 후 즉시 전송을 시도하면 지연을 줄일 수 있다. 릴레이는 남은 요청을 처리한다. 두 전송자가 같은 행을 동시에 처리할 수 있으므로 선점·상태 전이와 중복 허용 정책을 함께 정한다.

AFTER_COMMIT의 완료 상태 저장에는 새 트랜잭션이 필요할 수 있다. 릴레이는 자신의 트랜잭션 경계를 가진다. 두 경로가 같은 저장 방식이라고 가정하지 않는다.

적용 전에 정할 정책

  • 전송할 이벤트 ID와 소비자의 멱등 키
  • 재시도 간격·상한·만료·수동 복구
  • 여러 릴레이의 선점과 정지 후 복구
  • 같은 대상 이벤트의 순서 보장 필요성
  • 요청 보존 기간과 상태 관측 지표

만료된 요청을 늦게 전달해도 의미가 있는지는 업무에 따라 다르다. Outbox 존재만으로 모든 요청의 최종 성공을 보장한다고 말하지 않는다.

검증할 실패 구간

실제 DB에서 데이터와 요청의 커밋·롤백 원자성을 확인한다. 전송 직후 완료 기록 전에 프로세스를 중단하고 중복 수신이 안전한지도 확인한다. 등록 메서드를 Mock으로 대체한 테스트는 이 DB 원자성을 입증하지 않는다.

Last updated on