분산 시스템에서 메시지 데이터 정합성 유지하기 - Transactional Outbox Pattern

KwonKusang·2023년 10월 31일
2
post-thumbnail

개요

분산 시스템에서 각각의 서비스를 분리할수록 트랜잭션을 보장하기 어려워지는 문제점이 있다. 하지만 데이터 정합성의 중요도는 누구라도 공감하는 부분일 것이다. 이번 포스트에서는 서비스간의 소통을 위해 메시징 방식을 사용하고 데이터의 정합성을 최대한 유지할 수 있는 방법을 알아보고자 한다.

메시징(Messaging) 방식

일반적인 기능의 데이터 변경은 트랜잭션 내에서 이루어진다. 문제는 이벤트 메시지의 발행으로 전달되는 데이터는 트랜잭션을 보장 받지 못한다는 점이다. 발행되어야 할 메시지가 발행되지 않기도 하고 발행되지 말아야 할 메시지가 발행됨으로서 데이터의 정합성을 해치는 주된 원인이 된다.

메시지 발행 이슈 발생

  1. 트랜잭션 내에서 메시지를 발행한다. 트랜잭션이 롤백된다면?
    • 롤백 시, 메시지가 왜 발행되었는지 트래킹하기 어려움
  2. 트랜잭션 커밋 후 메시지를 발행한다. 메시지 발행에 실패한다면?

기존에는 메시지 발행 시 트랜잭션의 마지막에서 발행하여 롤백 위험을 최소화하는 방법을 선택했었다. (메시지 발행 실패보다 서비스 로직의 롤백 확률이 높다고 판단)

보완점

트랜잭션이 커밋된다면 메시지가 반드시 발행되어야 하고, 트랜잭션이 롤백된다면 메시지가 발행되지 않아야 한다.

Transactional Outbox Pattern

Outbox
보낼 편지함 (새로 작성한 이메일을 보내기 전에 그 메시지가 저장되는 곳)

Outbox Pattern은 트랜잭션 내에서 메시지를 즉시 발행하는 것이 아닌 보낼 메시지를 담는 공간을 마련한다.

Outbox Table (DB)

Outbox 테이블을 생성하여 서비스 로직과 함께 보낼 메시지 정보가 트랜잭션 내에서 원자성을 보장받기 때문에 모두 저장되거나 모두 저장되지 않게 된다.

create table outbox
(
    outbox_id         bigint auto_increment primary key,
    created_date_time datetime default current_timestamp not null,
    topic             varchar(255)                       not null,
    message           varchar(255)                       not null,
    send_yn           tinyint  default 0                 not null
);

Message Relay

이제 Outbox 테이블에서 보낼 메시지를 꺼내 발행해야 한다. Message Relay을 구현하는 방법에는 Polling publisher, Transaction log tailing 두 가지 방식이 있지만 Polling publisher 방식을 예시로 살펴보고자 한다.

@Scheduled(fixedRate = 1000)
public void publish() {
    Outbox message = outboxRepository.findByNotSendOrderById();
    publisher.publish(message);
    message.updateSendYn(true);
}

스케줄링을 통해 Outbox의 메시지를 가져와 발행하고 발송 완료 처리를 합니다.

Q. 처리 속도가 늦으면 메시지가 중복 발송될 수 있지 않나요?

맞다. 메시지 발행에 성공할 때까지 반복하게 된다. 어떤 메시지는 중복으로 발행될 수 있지만 최소 한 번 이상의 발행을 가져간다. 이 방식을 At-Least-Once Delivery 전략이라고 한다.

Message Delivery Semantics

멱등성(Consumer)

메시지의 중복 발행을 허용하는 전략을 사용하기 때문에 컨슈머에서는 데이터가 중복으로 쌓이지 않도록 유의해야 하고 멱등 처리가 반드시 필요하다. 즉, 전송할 때와는 다르게 Exactly-Once Processing 전략을 사용해야 한다.

장단점

  1. Two-Phase Commit(2PC) 방식을 사용하지 않는다.
    2PC의 다이어그램을 보면 DB별로 최소 세 번의 통신이 발생한다. 또한 잠금이 걸리는 구간 길기 때문에 성능 저하가 발생한다.
  2. 데이터베이스 트랜잭션이 커밋하는 경우에만 메시지 전송이 보장된다.
  3. 메시지는 응용프로그램이 보낸 순서대로 메시지 브로커에게 전달된다.

참고자료
https://microservices.io/patterns/data/transactional-outbox.html
https://blog.gangnamunni.com/post/transactional-outbox

profile
안녕하세요! 백엔드 개발자 권구상입니다.

2개의 댓글

comment-user-thumbnail
2023년 11월 1일

아주 유용했어요. 고마워요 구상님

1개의 답글