티스토리 뷰
https://microservices.io/patterns/data/transactional-outbox.html
마이크로서비스 패턴중에 transactional outbox 패턴으로 불리는 유명한 패턴입니다
여기서 outbox는 보낼편지함을 의미합니다
그래서 transactional outbox를 굳이 해석하자면 "트랜잭션처리가 가능한 보낼편지함" 이라고 할까요
위 그림을 살펴보면
Order Service로 어떠한 요청이 왔을때 Order Service는 트랜잭션을 통해 ORDER 테이블과 OUTBOX 테이블에 모두 작업을 수행합니다
그리고 Message Relay가 OUTBOX 테이블을 읽고 Message Broker로 발행하는 모습을 볼 수 있습니다
이 패턴은 왜 필요할까요?
트랜잭션 아웃박스 패턴은 단일 작업에 데이터베이스 쓰기 작업과 메시지 또는 이벤트 알림이 모두 포함된 경우에 분산 시스템에서 발생하는 이중 쓰기 작업 문제를 해결합니다
예를 들어 마이크로서비스가 데이터베이스에 데이터를 저장하고 메시지를 전송하여 다른 시스템에 알려야하는 경우가 이에 해당합니다
데이터베이스 업데이트 후 마이크로서비스가 이벤트 알림을 보내는 경우 데이터 일관성과 신뢰성을 보장하기 위해 이 두 작업이 원자적으로 실행되어야 합니다.
일관성을 유지하기 위해 데이터베이스를 업데이트하고, 메시지를 보내는 순서를 유지해야합니다
트랜잭션의 중간에서 메시지를 보내는 것은 신뢰를 떨어뜨립니다
function createFlight() {
// 트랜잭션 시작
// 데이터베이스에 항공권예약정보 저장
// 예악완료 메시지 발행 🟢
// 트랜잭션 커밋실패 ❌ 실패하였음에도 예약완료 메시지가 이미 발행되었음
}
트랜잭션이 반드시 커밋성공하리라는 보장이 없기 때문입니다
function createFlight() {
// 트랜잭션 시작
// 데이터베이스에 항공권예약정보 저장
// 트랜잭션 커밋 🟢
// 예악완료 메시지 전송 실패 ❌ 데이터는 저장되었으나 메시지가 발행되지 않음
}
비슷하게 커밋이후에 메시지를 보내는것 역시 메시지 발송 전에 에러가 발생하지 않으리라는 보장이 없습니다
데이터베이스 업데이트는 성공했지만, 이벤트 알림이 실패할 경우 시스템이 일관되지 않은 상태가 될 수 있습니다
데이터베이스 업데이트에 실패했는데, 이벤트 알림이 전송되면 이 또한 시스템의 신뢰성에 영향을 미칠수 있습니다
aws 문서에도 나와있는 유즈케이스를 살펴보겠습니다
1. 항공편 서비스가 데이터베이스에 데이터를 쓰고 결제 서비스에 이벤트 알림을 보냅니다.
2. 메시지 브로커가 메시지와 이벤트를 결제 서비스에 전달하여 결제를 요청합니다. 만약 메시지 브로커에 장애가 발생하면 결제 서비스가 업데이트를 수신할 수 없습니다.
여기서 데이터베이스 업데이트에 성공하였는데, 메시지 발행이 되지 않아 결제서비스에 요청을 할수 없게되는 상황과
데이터베이스에 업데이트의 커밋이 실패하였음에도 불구하고 메시지 발행이 되어 결제서비스에 예약되지 않은 결제 요청을 하게되는 경우
모두 발생할수 있습니다
데이터베이스의 업데이트와 메시지브로커에세 메시지를 전송하는것을 원자적으로 처리할수 있을까요?
데이터베이스 트랜잭션이 커밋된다면 메시지는 반드시 전송되어야하며,
반대로 데이터베이스가 롤백되었다면 메시지는 전송되면 안됩니다
그래서 이러한 경우에 두가지 작업의 원자성을 보장하기 위해 트랜잭션 아웃박스 패턴을 사용합니다
다시보면 이 패턴은
- sender : 메시지를 보내는 서비스
- database : 비즈니스 엔터티와 message outbox를 저장하는 데이터베이스
- message outbox : RDB의 경우 보내져야할 메시지를 저장하는 테이블, nosql 데이터베이스라면, outbox는 데이터베이스 레코드의 속성
- message relay : outbox에 저장된 메시지를 메시지 브로커로 보냄
이렇게 구성됩니다
이러한 구성에서는 트랜잭션이 커밋되어야지만 OUTBOX 테이블에 데이터가 저장이 되었을것이기 때문에 원자성을 보장하게 됩니다
OUTBOX 테이블에는 다음과 같은 필드들이 필요할 수 있습니다
- id : 메시지id
- payload : json 형태 메시지
- messageType : 메시지종류
- isProcessed : 처리여부
- isProcessing 처리중여부
- isSequential : 순서보장필요여부
- metadata : traceId 등
- reservedAt
- expiredAt
- createdAt
트랜잭션 아웃박스 패턴에서도 다음과 같은 문제를 추가로 생각해볼 수 있습니다
중복 메시지
message relay는 아마도 같은 메시지를 한번 이상 발생할 가능성이 있습니다
메시지를 발행하기는 하였으나 그 결과를 저장하기 전에 크래시가 발생한다던가 같은일이 생길 수 있습니다
결과적으로 message consumer는 멱등성을 보장해야 합니다
이미 처리된 message의 id를 트래킹하는 방법으로 멱등성을 보장할 수 있습니다
알림 순서
서비스에서 데이터베이스를 업데이트 하는 순서와 동일한 순서로 메시지나 이벤트를 전송해야합니다
만약 순서가 올바르지 않은 경우에, 주문이 접수되지도 않았는데 취소 요청을 먼저 처리하려고 한다거나 하는 문제가 발생할수 있습니다
예를 들어 isSequential 같은 플래그를 통해 반드시 특정 노드에서만 실행되어야 하는 메시지임을 표시해 줄 수 있습니다
이 경우 특정 노드는 해당 플래그가 활성화된 레코드만 처리하는 방식처럼 구현이 가능합니다
중복 메시지, 알림 순서 문제 모두 수평 확장시 문제가 더욱 드러납니다
여러 노드가 outbox 테이블을 배타적으로 읽을 필요가 있는데
mysql에서는 SELECT FOR UPDATE를 통해 해당 row에 lock을 통해 다른 트랜잭션에 의해 접근되는 것을 막을수 있습니다
또는 분산락을 사용하는 방법도 있겠습니다
트랜잭션이 없는 nosql의 저장소를 사용하는 경우
만약 트랜잭션이 없는 nosql의 경우는 레코드의 속성을 outbox 처럼 활용합니다.
이렇게 json 형태의 document가 있다면 outbox 필드를 사용하여 유사하게 구현할수 있습니다
{
id: "1254234",
departureAirport: "ICN",
arrivalAirport: "NRT",
departureDateTime: "2024-12-01T12:00:00Z"
arrivalDateTime:: "2024-12-04T12:00:00Z"
...
outbox: {
...
}
}
aws의 공식 문서에도 트랜잭션 아웃박스 패턴이 설명되어있습니다
그 내용으로 message relay의 구현으로는 두가지 패턴이 있습니다
polling publisher 패턴 : outbox 테이블을 폴링(RDS를 사용한 예)
transaction log tailing 패턴 : outbox 테이블의 변경 데이터를 캡쳐(DynamoDB 스트림을 활용한 예)
다른 분들도 분산환경에서 상황에 맞게 요긴하게 시도해보셨으면 합니다
'BACKEND' 카테고리의 다른 글
mongodb의 변경스트림(changeStream) (0) | 2024.12.13 |
---|---|
querystring을 통해 배열도 받을수 있나요? (0) | 2024.08.13 |
cloudfront에 일괄 cache-control 적용하기 (0) | 2024.05.03 |
nestjs swagger가 아니라 redoc 적용하기 (0) | 2024.03.11 |
POST 메서드 apache bench(ab) 사용하기 (0) | 2024.02.07 |
- Total
- Today
- Yesterday
- Apple
- 게임
- AWS
- 앱
- 구글
- CSS
- 경진대회
- php
- 어플리케이션
- 네이버
- 소프트웨어
- iPhone
- 앱스토어
- 트위터
- 창업
- android
- 웹표준
- 아이폰
- 자바스크립트
- 아이디어
- 벤처
- 공모전
- 스마트폰
- JavaScript
- 모바일
- 대학생
- 안드로이드
- 애플
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |