이 블로그는 마이크로서비스패턴 (길벗) 책 내용을 스터디를 위해 정리 한 내용 입니다.
책구매는 바로가기 에서 구매 가능합니다.
SAGA란?
SAGA는 약자가 아닌, 1987년 처음 지정된 공통 용어이다. LLT (Long Lived Transactions) 을 의미 하는 단어이다.
사실 SAGA 는 사전적인의미로 '말해진것', '말로 전하다' 등으로, 아마 SAGA 패턴도 데이터 적으로 봤을때 '발생한 데이터' 로 그 데이터를 전달 한다로 이해 할수 있다.
마이크로서비스 아키텍처에서의 트랜잭션관리
분산 트랜잭션의 필요성
모놀리식 서비스에서의 트랜잭션 관리는, DB에게 위임하기때문에 어렵지 않다. 그러나 마이크로서비스 패턴에서의 트랜잭션은 DB를 여러개 사용하기도 하고, IPC 를 이용해 개별 DB를 가지고 있는 각 서비스 별로 인터페이스 하기 때문에 어플리케이션 레벨에서 정교한 메커니즘으로 트랜잭션을 설계/개발 해야 한다.
X/Open DTP (Distributed Transaction Processing) 는 분산 시스템 소프트웨어 사용시에 각 분산 시스템의 트랜잭션을 관리 해주는 사실상의 표준이다. XA는 분산 트랜잭션 환경에서 트랜잭션 매니저와 리소스매니저 사이에 통신을 담당하는 표준이다. 이 XA는 2PC (Two Phase Commit) 을 이용해 전체 트랜잭션이 성공하면 각 시스템을 COMMIT 하고 아니면 ROLLBACK 한다.
(참고 : https://rastalion.me/mariadb%EC%9D%98-xa-transactions/)
분산 트랜잭션의 문제점
- 미지원 시스템이 많다.
대부분의 SQL DB는 XA 를 지원하며, 메시지 브로커도 일부 지원하기도 한다. 하지만 MongoDB 나 Cassandra같은 NoSQL DB, 최근 등장한 RabbitMQ 나 Kafka 등은 분산 트랜잭션을 지원하지 않는다. - 참여중인 모든 서비스가 가동중이여야 한다.
하나라도 시스템이 정지 되어 있다면 각 시스템별 서비스 처리를 진행 할 수 없으므로, 해당 서비스를 이용하는 프로세스는 중지 되게 된다.
데이터 일관성 유지 : SAGA Pattern
SAGA 는 분산트랜잭션 없이 데이터 일관성을 유지 하는 매커니즘이다. 각 서비스별로 사가(로컬 트랜잭션)를 하나씩 정의 하여, 어느 한 사가가 수행되고 종료되면, 다음 사가가 전달 받은 메지시 혹은 인터페이스로 개별 서비스 데이터를 업데이트 한다.
사가와 ACID 의 차이점
- 사가는 ACID 중 격리성이 존재하지 않는다
- 사가는 자신이 받은 변경분만 처리해 커밋 하므로, 보상 트랜잭션을 걸어 롤백 해야 한다.
예제 : 주문생성 사가
- 주문 서비스 : 주문을 APPROVAL_PENDING 상태로 생성
- 소비자 서비스 : 주문가능한 소비자인지 확인
- 주방 서비스 : 주문 내역을 확인하고 티켓을 CREATE_PENDING 상태로 생성
- 회계 서비스 : 소비자 신용카드 승인 (결제완료)
- 주방 서비스 : 티켓 상태를 AWAITING_ACCEPTANCE 로 변경
- 주문 서비스 : 주문 상태를 APPROVAED 로 변경 (최종 완료)
각 단계별로 로컬 트랜잭션이 완료되면 메시지를 발행해 다음 로컬 트랜잭션이 수행된다. 각 시스템 별로 느슨하게 결합되어야 하며, 다음 시스템이 중지된 상태면 메시지를 전달 받을때까지 대기 해야 한다.
SAGA 는 보상 트랜잭션으로 변경분을 롤백한다.
SAGA 는 각 단계별로 DB에 커밋 되므로, 다른 서비스의 롤백은 불가능하다. 때문에 명시적으로 롤백 매커니즘을 설계 해야 한다. 이때 롤백을 위해 명시적으로 이벤트를 발생 시키는 작업을 "보상 트랜잭션 (compensating transaction)" 이라고 한다.
n개의 작업이 진행 되는 중이고 해당 작업이 실패 되었다면 직전 수행한 각 서비스에 보상 트랜잭션을 발생 해야 한다. 결국 n-1 회 보상 트랜잭션이 발생한다.
모든 작업이 commit 이 필요한 작업은 아닐것이다. 소비자 서비스는 소비자가 승인 가능한 상태인지만 확인하고 트랜잭션이 별도 존재하지 않기 때문에 이런 경우는 별도로 보상 트랜잭션을 수행 할 필요는 없다.
사가편성
코레오그래피 사가 (Choreography-Based Saga)
코레오그래피 사가 방식은 사가 참여자들이 서로의 이벤트를 구독해 자신의 로컬 트랜잭션을 스스로 수행하는 방식이다. 각각의 이벤트를 수신하기 위해 KAFKA 같은 메시지 브로커가 별도로 필요하다.
주문생성 사가 구현 : 코레오그래피 스타일
주문이 발생하면, 해당 주문건 이벤트를 등록한 각 서비스가 이벤트를 수신하고, 각 개별 서비스별 처리를 진행 한 다음 다시 이벤트를 발생한다.
확실한 이벤트 기반 통신
코레오 그래피 방식으로 사가를 구현하면 두가지 통신 이슈가 있다.
- 각 서비스는 자신의 DB를 업데이트 한 후, DB 트랜잭션의 일부로 이벤트를 발생해야 한다.
- 수신된 이벤트는 자신이 가진 데이터와 연관된 키를 보유 해야 한다.
코레오그래피 사가의 장단점
크레오그래피 사가는 다음 장점을 갖는다
- 단순함 : 단순이 자신의 이벤트를 처리후 후속 이벤트만 발생시킨다.
- 느슨한 결합 : 각 서비스는 이벤트를 구독 할 뿐, 서로의 로직을 알지 못한다.
다음과 같은 단점도 있다
- 이해하기 어렵다 : 전체적으로 프로세스가 어떻게 흘러가는지 한눈에 파악 할 수 없다.
- 서비스간 순환 의존성이 발생 할 수 있다 : 이벤트가 발생한 후, 해당 이벤트를 다른 서비스가 받아 다시 이벤트를 발생 시켰을시, 후속 이벤트를 먼저 이벤트를 발생한 서비스가 재 수신 할 경우 순환 구조가 성립될 수 있다. ( A > B > A > B .... )
- 결합도가 높아진다. : 자신이 처리 해야 할 모든 이벤트를 수신 해야 한다.
오케스트레이션 사가 (Orchestration-Based Saga)
오케스트 레이션 사가는 참여자가 할 일과 순서를 처리하는 오케스트레이터 클래스가 존재 하는 방식이다. 오케스트 레이터 클래스는 하나의 이벤트 흐름을 관장하는 클래스이다. 오케스트 레이션이 모든 참가자를 관장 하는 것이다.
주문 생성 사가 구현 : 오케스트레이션 스타일
오케스트레이터인 Create order saga orchestrator 는 비동기 요청을 주고 받으며, 개별 서비스의 참여자를 호출 하고, 그 처리 결과에 따라 커맨드를 진행 한다.
사가오케스트레이터를 상태 기계로 모델링
이벤트는 상태값에 따라 전이되며, 상태값의 결과에 따라 어떤 액션을 취할지 결정 된다. 상태 기계는 오케스트레이션이 수행할 다양한 시나리오를 모델링 하는데 유용한 방식이다.
오케스트레이션 사가의 장단점
장점
- 의존관계 단순화 : 오케스트레이션이 참여자의 호출을 관리 하고 있으므로 순환 의존성이 생기지 않는다.
- 낮은 결합도 : 오케스크레이터가 API 를 호출 할 뿐, 각 참여자들은 이벤트를 알 필요가 없다.
- 관심사를 분리하고 비지니스 로직을 단순화 : 도메인 객체는 더 단순해지고, 상태기계 모델은 더 단순해진다.
단점
- 비지니스 로직이 오케스트레이터에 작성될 수 있다.
비격리 문제 처리
ACID 중 격리성은 동시 실행중인 여러 트랜잭션의 결과가 어떤 순서대로 실행된 결과와 동일함을 보장하는 속성이다.
SAGA 는 이 격리성이 빠져있다. 사가가 실행중에 다른 사가가 데이터를 바꿔치기 하거나, 사가가 데이터를 업데이트 하기 전에 다른 사가가 데이터를 읽을 수 있다.
SAGA 는 다음 ACD 를 보장한다.
- 원자성 : 각 사가는 트랜잭션을 모두 COMMIT 하거나, ROLLBACK(UNDO) 해야 한다.
- 일관성 : 서비스별 참조 무결성은 각 DB 가 담당하며, 전체 서비스의 참조무결성은 서비스가 처리 한다.
- 지속성 : 로컬 DB로 처리한다.
격리성이 없어지면, 트랜잭션이 순서대로 실행되지 않은 거처럼 데이터를 읽고 쓰게 되는 일이 발생할 수 있다.
비정상개요
- 소실된 업데이트 : 한 사가의 변경분을 다른 사가가 덮어 쓸때 일어 난다.
- 주문 생성 사가 첫번째에서 주문생성
- 사가 실행중, 주문 취소 발생. 취소 사가가 주문을 즉시 취소
- 주문 생성 사가가 아직 수행중으로, 주문을 승인. (주문 취소보다 나중에 첫번째 사가가 완료)
- 더티읽기 : 한 사가가 업데이트가 완료되지 않았는데, 다른 사가가 데이터를 읽었을때
- 퍼지/반복 불가능한 읽기 : 수행중인 사가 내에서, 각 다른 두 단계에서 데이터 조회시 다른 데이터가 조회 되었을 경우
비격리 대책
사가의 구조
- 보상 가능 트랜잭션 : 보상 트랜잭션으로 롤백 가능한 트랜잭션
- 피봇 트랜잭션 : 사가의 진행/중단 지점. 피봇 트랜잭션이 커밋되면 사가는 완료될때까지 수행된다.
- 재시도 가능 트랜잭션 : 피봇 트랜잭션 이후 트랙잭션으로, 피봇이 종료되고 실패 되더라도 재시도 등을 이용해 반듯이 성공 되는 트랜잭션이다.
대책 : 시맨틱 락
어플리케이션 수준의 락이다. 레코드에 플래그를 세팅해, 커밋 완료 전이라고 표시하는 방식이다. 플레그 세팅은 조회나 업데이트를 하지 못하도록 LOCK 을 걸거나 상태를 다른 트랜잭션에게 노출하는 역할을 한다. 해당 플래그는 재시도 가능 트랜잭션, 혹은 보상트랜잭션으로 변경 가능하다.
대책 : 교환적 업데이트
소실된 업데이트 등의 문제로 순서적으로 수행해야 하는 이벤트 (사가) 가 존재 할 경우가 있다. 이때의 해결방법은.. 어떠한 순서로 수행되도 이슈 없도록 설계 하면된다.
대책 : 비관적 관점
더티 읽기로 인한 리스크를 최소화 하기 위해 사가 단계순서를 재 조정 한다.
- 주문 서비스 : 주문을 취소 상태로 변경
- 배달 서비스 : 배달을 취소
- 회계 서비스 : 잔고를 늘림
잔고를 마지막에 늘리므로써, 더티 읽기가 발생 되지 않는다.
대책 : 값 다시 읽기
사가에서 레코드를 업데이트하기 직전 데이터를 다시 읽어 데이터의 변화가 있는지 다시 확인한다.
대책 : 버전 파일
레코드에 수행한 작업을 하나하나 기록하여 순서를 재 조정 할 수 있도록 업데이트를 기록한다.
순서가 맞지 않는 레코드가 도착하면, 기록해두었다가 정확한 순서대로 실행 하는 방식이다.
대책 : 값에 의한
요청의 속성을 보고, SAGA 를 쓸지, 분산 트랙잭션을 쓸지 동적으로 판단하는 방식이다.
주문서비스 및 주문생성 사가 설계
OrderService 클래스
주문생성사가구현
OrderCommandHandlers 클래스
OrderServiceCongifuration 클래스