Transactional Outbox 패턴

saewoohan·2025년 3월 11일

MSA

목록 보기
4/5
  • Transactional Outbox 패턴은 실제 테이블을 업데이트하는 행위와 이벤트를 발생하는 행위 사이에 일관성을 보장하기 위한 패턴이다.
    • 데이터베이스와 메시지 브로커가 일관성이 유지되지 않는다면, 실제 데이터의 정합성이 깨질 것이기 때문이다.
  • 그렇다면 서비스가 데이터베이스를 업데이트하면서 이벤트 메시지를 전송할 때, 두 작업을 원자적으로 처리하는 방법은 무엇일까?
    • 그렇다면 이벤트 자체를 데이터베이스에 저장하는 방식이다. → Outbox Table
    • DBMS에서 실제 엔터티의 저장과 Outbox의 저장을 원자적으로 처리함으로써, 이벤트와 엔터티의 일관성이 보장될 수 있다.
    • 후에 이 이벤트를 별도의 프로세스 (이벤트 릴레이)가 메시지 브로커로 전송하여 처리하는 방법으로 처리하는 것이다.
    • 이로 인한 얻게되는 장점 중 하나는 메시지 발행 로직을 트랜잭션에서 분리하기에 메시지 브로커와의 의존성을 제거할 수 있다는 점이다.
  • 이때, Outbox테이블을 읽는 방식에는 폴링과 log를 읽는 방식으로 크게 존재하는데, 이전에 근무했던 회사에서는 MongoDB의 Change Stream을 사용하였다.
    • Change Stream은 log를 통해 전달하는 방식
  • 추가적인 저장공간이 필요하다는 점말고는 구현도 간단하며, 일관성 보장 측면에서는 굉장히 뛰어난 패턴이라고 생각이 든다.
    • 물론, 추가적인 프로세스가 Outbox를 주기적으로 읽고 메시지를 보내는 방식이기에 최종적 일관성이 보장은 되지만 그 텀이 조금 길 수도 있을 것 같다.


예시

  • 간단한 예시를 들어보자면 다음과 같다.
  • 예를 들어, 주문을 하는 시스템에서 주문을 생성할 때를 생각해 본다면
    1. 주문 생성 트랜잭션
      • 주문 정보를 데이터베이스에 저장하면서, Outbox 테이블에 주문 생성 이벤트를 기록한다. 이 두 작업은 하나의 트랜잭션으로 처리.
    2. 이벤트 전달
      • Outbox 테이블을 주기적으로 읽는 프로세스가 주문 생성 이벤트를 메시지 브로커에 전달한다.
    3. 전송 확인
      • 메시지가 전송되면 Outbox 테이블의 해당 이벤트를 삭제하거나 처리 상태를 업데이트.
  • 이와 같은 방식을 통해 주문 정보와 주문 생성 이벤트 간의 일관성이 보장되며, 메시지가 전송되지 않는 문제가 생기더라도 Outbox테이블에서 데이터를 보고 이벤트를 재전송 할 수도 있다.

남아있는 문제

이벤트의 중복

  • Outbox 테이블을 읽는 프로세스를 메시지 릴레이라고 부르는데, 해당 서비스가 메시지 브로커에 전달하다가 오류가 나서
  • 다른 메시지 릴레이 서비스의 인스턴스가 Outbox테이블에서 다시 메시지 브로커에 전달하는 경우 같은 메시지가 두번 전달되는 상황이 벌어진다.
  • 이 문제는 Outbox 테이블에 메시지 정보를 저장할 때, 각 메시지마다 ID값을 할당하고, 메시지 브로커로 전달하는 이벤트에 해당 아이디 값을 부여하면 쉽게 해결할 수 있다.

데이터베이스가 트랜잭션을 미지원

  • NoSQL DB같은 DBMS는 트랜잭션을 지원하지 않는다.
    • 정확하게는 최근에는 대부분 지원하지만 성능이 좋지 않다.
  • Transaction Outbox 패턴의 핵심 개념은 엔터티와 Outbox의 원자성을 보장하여 이벤트 발행과 데이터의 변화 사이의 트랜잭션을 보장하는 것인데, 이 부분이 불가능하다.
  • 그렇다면 어떻게 해결할 수 있을까?
    • 해결 방법은 간단하다. 단순히 데이터 상태를 직접 업데이트 하지 않고, 변경할 상태를 이벤트로 기록하는 것이다.
    • 해당 변경사항을 이벤트로 전달하여 간접적으로 데이터를 업데이트 하는 것이다.

메시지 순서

  • 만약 사용자가 상품을 주문하자마자 바로 취소하는 경우에는 메시지 릴레이가 생성과 취소 두개의 메시지를 발견하였고,
  • 비즈니스 순서를 이해하지 못하고 주문 취소 이벤트를 주문 생성 이벤트보다 먼저 발행할 수도 있다.
  • 이러한 경우에는, Outbox테이블에서 각 메시지마다 연속적인 아이디 혹은 타임스탬프를 부여해서, 이벤트를 발행할때 정렬하는 방식으로 순서를 맞출 수 있다.

0개의 댓글