보상 트랜잭션으로 분산 환경에서도 안전하게 환전하기!
1. 분산 환경이 만들어진 이유?
- monolithic system : 서비스가 커지며 배포/개발 경험/단일 장애점/확장성에 문제가 발생
- MSA로 분리 -> 기존에 존재하던 로직들을 분리된 서버로 옮겨가는 방식
- 신규 domain에 대해서는 DB까지 분리된 MSA서버에 개발
- 환전이 같은 DB + 단일 서버에 구현되어 있다면, 굉장히 간단하게 구현이 가능함.
e.g) transaction을 열어 각 계좌(원화/외화)에 업데이트 후 transaction commit하면 됨 (쉽게 원자성 보장가능)
- 분산 환경에서 원화 계좌/ 외화 계좌가 분리되고 각각의 DB를 사용할 경우?! 분산 트랜젝션을 통해 원자성을 보장해야함
2. 2PC(2 phase commit) vs SAGA(Saga pattern)
- 분산 트랜젝션을 구현하는 방법임
2PC
: 두 단계로 나누어 commit을 진행하는 것
Phase1) Voting - 투표를 진행함. Coordinator가 각 참여자들(각 서버의 DB)에게 Commit 가능 여부 질의 +
투표의 참여자는 transaction을 열고 commit 가능 여부를 응답함
Phase2) Commit. 모든 참여자들이 commit 가능하다고 응답할 경우 coordinator가 commit 요청을 보내 tranasaction을 성공으로 종료. 하지만 불가능이라는 응답이 있는 경우 rollback요청을 보내 transation을 실패 처리함
장점 - 강한 일관성
단점 - 낮은 가용성/확장성 (모든 서비스들이 트랜젝션을 열고 가장 느린 참여자의 응답을 기다려야함)
SAGA pattern
:각 서비스들이 작은 로컬 트랜젝션을 통해 진행하다가 특정 단계에서 실패 시, 이전 단계에서 실패된 트랜젝션들에게 보상 트랜젝션을 보내 rollback 시킴
장점: 높은 가용성/높은 확장성 (각 서비스들의 로컬 트랜젝션만 진행)
단점: 중간 상태 노출 / 보상 트랜젝션 구현 필요 (일부 트랜젝션만 커밋된 중간 상태가 노출. 보상 트랜젝션 직접 구현해야함)
환전 서비스의 경우에는 높은 트래픽을 견뎌야하고 카드나 회계와 같은 다양한 서비스가 추가될 수 있음으로 SAGA를 선택
3. More about SAGA(Saga pattern)
1. Choreography Saga
: 중앙 제어자 없이 메시지 브로커를 통해 이벤트를 교환하며 진행
: 중앙 제어자가 없음 === 단일 장애점이 없음
: 각 서비스들이 느슨하게 결합
: 현재 진행 중인 transaction을 추적하고 debugging하기 힘듦
2. Orchestration Saga
: Orchestrator가 각 서비스들에게 transaction과 보상 transaction을 명령하며 진행하는 방식
: Orchestrator가 단일 장애점이 됨
: 모든 서비스들이 결합됨
: 현재 진행 중인 transaction을 추적하고 debugging하기 쉬움
환전 서버에서는 Orchestration Saga를 채택 Why?
클라이언트의 요청을 받아 환전을 시작하는 환전서버가 필요했고 현재 환전 진행 중인 상태들을 관리가 필요했음
e.g) 환전 한도를 구현하려면, 현재 진행 중인 환전 금액과 상태의 추적이 필요함
4. 어떻게 구현했을까?!
환전 성공 케이스
1. 환전 서버 -- 1300원 출금요청--> 원화 계좌
2. 성공 시, 환전 --1$ 입금--> 외화 계좌
환전 실패 케이스
정상적인 실패
- 잔액 부족
- 잔액 증명서 출력
- 계좌 해지
- 고객/계좌 거래 제한
비정상적인 실패
- 서버 에러(5xx / 타임아웃)
- 네트워크 에러
- 메시지 produce/consume 실패
4-A) 정상적인 실패에 대한 처리
- 출금에 실패할 경우 -> 추가적인 입금 요청 작업 없이 마무리 가능
- 입금에 실패할 경우 -> 이미 출금이 되어 보상 트랜잭션이 필요함 (출금 취소 입금에 대한 요청을 원화 계좌로 보냄)
왜 출금부터 해야할까?!
- Saga의 특징 중 하나인 중간 상태의 노출 문제때문임. 입금된 돈이 빠져나갈 수 있어 입금 취소는 위험함.
HTTP vs Messaging (대부분의 Saga는 Messaging으로 구현됨)
HTTP
: 동기 / 직관적 구현 간단 / 추가적인 에러핸들링 필요 / Client에서 read timeout 두기 편함
Messaging
: 비동기 / 서비스간 느슨한 결합 / 메시지 브로커 레벨에서 에러 핸들림 지원 및 결과적 정합성 보장 / 응답 및 timeout 구현 복잡
입금과 출금은 HTTP를 사용함 Why?
1. 입출금 결과를 알고 넘어가야함(동기적)
2. 유저는 환전이 즉시 완료되기를 기대함 (타임아웃 구현 필요 - 타임 아웃을 메시징으로 구현하려면 입출금을 다시 messaging으로 받고 polling하는 등의 로직이 필요함)
출금 취소(보상 트랜젝션)는 Messaging을 사용함 Why?
1. 출금 취소는 마지막 과정
2. 유저가 기다릴 필요가 없음 (비동기)
3. 출금 취소에 에러 핸들링을 하기 싫음. 결과적 정합성 보장만 되면 됨
4-B) 비정상적인 실패에 대한 처리
Http error handling
1. 환전 서버 -- 출금 요청(5xx 에러/ 타임아웃)--> 원화 계좌 서버
: 출금 결과를 다시 확인함
: case1) 성공 확인 -> 출금 취소 처리
: case2) 실패 확인 -> 환전 실패 처리
2. 환전 서버 -- 입금 요청(5xx 에러/ 타임아웃)--> 외화 계좌 서버
: 입금 결과를 다시 확인함
: case1) 성공 확인 -> 환전 성공 처리
: case2) 실패 확인 -> 출금 취소 처리
3. 입출금 결과 확인 시 실패할 경우?
: Kafka Message Scheduler(메시지를 지연시켜 발행하는 역할)
: 일반적인 메시지의 경우, producer가 메시지를 발행하면 그 즉시 message broker를 통해 consumer에 메시지가 전달됨
: 하지만 지연 시간을 넣어 발행하면, 별도의 지연 topic으로 kafka message scheduler로 전달됨
: kafka message scheduler는 지연 시간 이후, 원래의 topic에 메시지를 대신 발행하여 consumer가 지연 시간 이후 메시지를 가져감
: 이때 메시지의 주고 받는 주체가 환전 서비스가 된다면, 특정 동작을 지연 시간만큼 뒤로 예약할 수 있게됨(상대 입출금 서버의 회복시간을 기다릴 수 있게됨)
e.g) 출금 결과 확인 실패 시, 출금 결과 재조회 요청을 delay하여 출금 결과를 지연 시간 이후에 재조회함
환전 서버의 문제로 환전 지연 이벤트를 발행하지도 못하고 서버가 죽은 경우(장비 결함/Container OOM)에는?
- Batch 재처리
: Orchestrator가 환전 transaction의 마지막 상태를 저장하고 있음. 현재 상태부터 환전 재시작이 가능.
원화계좌가 출금 취소 메시지를 처리하다 에러가 발생하면?
: Consumer Dead Letter(CDL)을 통해 Kafka 결과적 정합성을 보장함.
: CDL message broker를 통해 DL 서버로 전달함. DL 서버는 정해진 횟수와 간격으로 Service Message Broker에 메시지를 다시 전달하여 원화 계좌의 출금 취소를 재시도함
Transactional Messaging(Local Transaction commit과 Message 발행이 원자적으로 이루어져야함)
Transaction Messaging을 보장하기 위한 패턴은 여러개 있음
그 중 PDL(producer dead letter)을 사용!
만약 Service Message Broker의 장애로 메시지 전달 자체가 되지 않으면?
e.g) 환전 서버가 service message broker의 장애로 출금 취소 메시지 발행 실패하면, PDL Message broker로 메시지를 대신 발행하여 DL 서버로 전달함. DL서버에서 일정 시간 이후 Service Message Broker한테 메시지를 다시 전달하여 consumer에게 전달
5. 모니터링?!
: Orchastration Saga에서는 각 transaction의 상태별 명령이 정해져있어서 State Machine으로 나타낼 수 있음
: Exchange State Log DB Table에 추가 삽입으로 상태에 대한 정보를 추가함(현재 상태 외, 환전이 거쳐간 모든 상태를 저장할 수 있음)
: 일정 시간 이후에도 끝나지 않은 환전들을 모니터링함
: 원화/외화 계좌는 각각 입금/출금 하나만 볼 수 있음. 떄문에 둘을 합쳐 입출금을 모두 볼 수 있는 정보계 DB에 주기적으로 적재하고 Batch 작업을 통하여 쌍이 맞는지 검증함
참고문헌
https://youtu.be/xpwRTu47fqY?si=E5E-13ESHwPac2bJ