Transactional OutBox Pattern 이란 ?
메시지를 DB 트랜잭션과 묶어서 커밋하여 비동기적으로 메시지 브로커에게 전달하는 패턴
무엇을 해결하기 위해 등장?
이벤트를 활용한 분산환경에서는 특정 작업을 완료한 후 이벤트를 발행해야한다.
E-Commerce를 예를 들면 상품을 구매하고 결제를 완료했을때 결제를 완료했다는 이벤트를 발행하여 다음단계인 배송준비 프로세스를 진행하는 것 처럼 다음단계 비즈니스 로직을 진행할 수 있도록 한다.
하지만 이벤트로 분리된 애플리케이션 환경에서는 애플리케이션 로직상 트랜잭션이 완료되기 전에 이벤트가 발행된다.
그러면 이후 비즈니스 로직이나 쿼리에서 예외가 발생하여 트랜잭션이 실패될 경우 롤백이 될 수 있다.
그러면 작업이 완료되지 않았는데 이벤트 메시지만 발행되는 상황이 일어날 수 있어 시스템의 오류를 발생시킬 수 있다.
Transactional Outbox Pattern이런 이벤트 메시지의 문제를 해결하기 위해 사용된다.
Transactional Messaging
Transactional OutBox Pattern은 Outbox 와 Message Relay라는 개념을 활용하여 구현한다.
- Sender - 메시지를 보내는 서비스
- Database - 엔티티 및 메시지 outbox를 저장하는 데이터베이스
- Message outbox - 관계형 데이터베이스인 경우 보낼 메시지를 저장하는 테이블. NoSQL의 경우 각 데이터베이스 record(document or item)의 프로퍼티
- Message relay - outbox에 저장된 메시지를 메시지 브로커로 보내는 서비스
outbox에는 Transaction이 정상적으로 완료되면 이벤트의 정보들이 outbox table에 기록된다.
그러면 MessageRelay는 outbox테이블에 저장된 데이터를 읽어서 비동기적으로 event를 발행하고
정상적으로 발행되었다면 outbox테이블에서 삭제한다.
이러한 방식으로 적어도 한 번 이상(at-least once) 메시지 전송을 보장한다.
Message Relay 패턴
Polling Publisher Pattern
outbox 테이블에 저장된 메시지를 주기적으로 polling하여 미발행된 이벤트를 조회하고
이벤트를 발행한다. 그리고 발행된 이벤트는 물리적으로 삭제하거나 발행 flag를 두어 메시지 발행을 식별할 수 있도록 구성한다.
polling시 서비스 트래픽이 감당할 수 있을만큼만 polling하여 트래픽을 제어할 수 있다.
규모가 작을 경우 사용할 수 있는 쉬운 방법
Log tailing Pattern
DB 트랜잭션 로그(커밋 로그)를 테일링(tailing)하는 방법.
애프릴케이션에서 커밋된 작업의 DB트랜잭션 로그 항목(log entry)로 남게되는데
Transaction log miner가 트랜잭션 로그를 통해 변경을 감지하고 이벤트를 발행하는 방법이다.
구현이 까다로워 DB의 변경을 감지하는 CDC(Change Data Capture e.g. Debezium)플랫폼을 사용한다
CDC 는 애플리케이션이 원하는 형태로 메시지를 발행하지 않는다.
데이터베이스에 적용된 CDC 는 주로 MySQL 의 binlog 와 같은 트랜잭션 로그를 활용하여 변경된 데이터를 확인하고, 이후 타겟으로 정한 메시지 브로커에 메시지를 발행한다.
이러한 방식은 발행되는 메시지의 형태가 테이블의 스키마에 의존적일 수 밖에 없고, 발행하는 메시지에 특정한 값을 추가하거나 의미에 맞게 값을 변경하기에는 어려움이 따른다.
요구사항에 따라서는 메시지를 수신하는 컨슈머 측에서 메시지 자체를 그대로 활용할 수 없기 때문에, 별도의 API 를 추가로 호출해야 할 수도 있다.
CDC 는 테이블 스키마 변경에 취약하다.
CDC 는 테이블의 구조에 의존적이기 때문에 테이블의 스키마가 변경되면 CDC 를 통해 메시지 브로커로 전달되는 메시지의 형태도 바뀌게 된다.
이는 테이블의 스키마를 변경하는 DDL 이 실행될 때마다 CDC 를 통해 생성되는 메시지의 형태가 바뀔 수 있다는 것이고, 그 때마다 메시지를 수신하는 컨슈머 측의 로직 변경을 요구하게 된다.
이는 마이크로서비스가 추구하는 서비스 간의 독립된 구현과 운영을 어렵게 만드는 요인이 된다.
cc.
https://medium.com/@greg.shiny82/트랜잭셔널-아웃박스-패턴의-실제-구현-사례-29cm-0f822fc23edb
문제 3가지
이벤트의 중복
메시지 릴레이 서비스가 Outbox 테이블에서 메시지 데이터를 읽어서 메시지 브로커에 전달하다가 오류가 나서,
다른 메시지 릴레이 서비스의 인스턴스가 Outbox 테이블에서 다시 메시지 브로커에 전달하는 경우 같은 메시지가 두번 전달되는 상황이 벌어진다
서비스가 멱등성을 보장하도록 개발하면 해결할 수 있다.
Outbox 테이블이 메시지 정보를 저장할때 각 메시지마다 ID값을 할당하고, 메시지 릴레이 서비스가 메시지 브로커로 발생하는 이벤트에 해당 아이디 값을 부여하면,
consumer service들은 아이디값을 비교해서 이미 처리된 이벤트, 중복 이벤트라는 것을 알고 무시 할 수 있다.
NoSQL DB 같이 데이터베이스 자체가 트랜잭션을 지원하지 않는 경우
Transaction Outbox 패턴의 핵심은 엔터티 테이블과 Outbox 테이블의 원자성, 단일 트랜잭션을 보장하는 것인데,
주문 서비스가 NoSQL DB를 사용해서 트랜잭션을 지원하지 못하는 경우가 있을 수 있다.
메시지 릴레이 서비스가 주기적으로 데이터베이스내 레코드를 쿼리해서 아웃박스 속성을 찾아 메시지 브로커에 전달하고
마지막에 해당 레코드 내의 Outbox 애트리뷰트를 삭제하는 방식으로 트랜잭션을 지원하지 않는 NoSQL DB를 사용하더라도 트랜잭셔널 아웃박스 패턴을 사용할 수 있습니다.
이벤트 발행 순서가 틀어지는 경우
만약 사용자가 상품을 주문하자 마자 바로 취소하는 경우, 메시리 릴레이 서비스가 주기적으로 아웃박스 테이블을 모니터링하다가 주문 생성, 주문 취소의 두개의 메시지를 찾았는데,
비즈니스의 순서를 이해하지 못하고 주문 취소 이벤트를 주문 생성 이벤트보다 먼저 발행할 수 있다.
이런 경우, 배송서비스는 주문 취소 이벤트를 수신해서 이미 추가된 배송정보를 삭제해야 하는데 정보가 없어서 무시하게 되고 이후 주문 이벤트를 수신해서 배송 정보를 생성하게 되면,
주문은 취소되었지만 배송 목록에는 남아 있는 문제가 발생할 수 있다.
이 문제는 아웃박스 테이블이 메시지 마다 시퀀셜한 아이디를 부여해서, 이벤트를 발행할때 아이디로 정렬해서 순서를 맞춰서 해결할 수 있다.
결론
이러한 설계의 핵심은 “도메인 로직이 실행되었을 때, 이를 표현하는 이벤트도 반드시 발행되게 하는 것” (Transactional Messaging) 이다
데이터베이스 업테이트와 메시지 전송의 원자성(Atomic)을 보장하기 위해서 사용된다.
또한 메시지의 최소 한번 전달을 보장힌다.
CC.
https://microservices.io/patterns/data/transactional-outbox.html
https://devocean.sk.com/blog/techBoardDetail.do?ID=165445&boardType=techBlog
댓글