MSA Transaction
MSA 구성을 할 계획이시라면 아마도 가장 고민되는 사항은 MSA 서비스간의 트랜잭션
일 것입니다.
기존 monolithic
시스템에서는 동일 DB를 사용하면서 매우 간단하게 트랜잭션
이 보장이 되었겠지만, MSA 시스템에서는 DB가 분리되었을 뿐만 아니라 다중 데이터소스
를 통해 데이터를 직접 접근하는것이 아닌 서비스간 통신을 사용하기때문에 트랜잭션
을 구성하기가 더욱 어려운것이 사실입니다.
물론 서비스간의 트랜잭션이 필요없도록 설계를 잘 하셨을 수도 있지만, 서비스가 확장되고 규모가 커질수록 어떠한형태로든 트랜잭션이 필요하게 될 가능성이 높습니다.
XA 분산 트랜잭션
XA
는 분산 트랜잭션 처리를 위해 X-Open에서 명시한 표준 기술로써, 2-PC(2 Phase Commit)
을 이용해 여러 서비스의 일관성을 유지해줍니다.
XA
는 대부분의 SQL DB
및 메시지 API
, 메시지 브로커
등에서 호환하여 사용 할 수있도록 지원이되며 JAVA 개발을 한다면 JTA
기술을 통해 손쉽게 트랜잭션처리를 할 수 있다는 장점을 가집니다.
하지만 XA
가 호환되지 않는 환경들도 있습니다.
NoSQL(몽고, 카산드라...)
, 현대 메시지 브로커(RabbitMQ
, Kafka
) 등에서는 XA
를 지원하지 않습니다.
또한 큰 단점으로 트랜잭션이 걸린 모든 서비스들에 동기 IPC를 유지해야 하기때문에 MSA에서의 가용성이 떨어 질 수 밖에 없다는 단점이 있습니다.
정리해보자면, XA
는 여러 자원들의 Transaction을 하나로 묶어주기에 ACID
를 보장할수 있는 가장 안전한 방법이지만 MSA 환경에서 가용성측면에서는 떨어지는 편이기 때문에, 이번 포스팅에서는 XA
보다는 유연하게 더 넓은 상황에서의 데이터 일관성
을 유지시켜줄수있는 SAGA 패턴
을 통한 MSA 서비스간 트랜잭션 유지방법에 대해서 집중해보고자 합니다.
SAGA 패턴
SAGA
는 위 제목에서도 알수 있다시피 트랜잭션
을 보장하기위한 기술이 아닌 데이터 일관성을 유지하기 위한 하나의 패턴
입니다.
SAGA
를 구현하기위한 다양한 패턴들이 있지만, 보상 트랜잭션
을 통해 롤백
할 수 있도록 구현하는것이 핵심입니다.
보상 트랜잭션
이란 정상적으로 수행된 서비스 트랜잭션을 취소
혹은 되돌리기
할 수 있는 트랜잭션
으로, Query로 따져보자면 Insert <-> Delete
, Update <-> Update
, Create <-> Drop
의 관계가 될 것입니다.
그렇다고 모든 트랜잭션에 대해 보상트랜잭션을 만들어줄 필요는 없고, 읽기전용 혹은 항상 성공하는 단계의 트랜잭션의 경우 보상트랜잭션을 만들어 줄 필요가 없습니다.
DB
의 트랜잭션
기능을 사용하지 않기때문에 자동 롤백
이 되지 않는다는 점을 생각하면, 다중 DB 환경에서 데이터 일관성유지 측면에서 필수적으로 만들어주어야하는 기능입니다.
위에서 알아본것같이 SAGA
는 개별적인 트랜잭션
들을 개발자가 컨트롤 함으로써 데이터의 일관성을 유지해주는 방식이라 트랜잭션
의 주요 원칙인 ACID
의 Isolation (격리성)
을 보장해 줄 수는 없다는 단점을 기억해야 합니다.
Choreography SAGA
SAGA
를 구성하는 첫번째 스타일으로 코레오그래피
스타일이 있습니다. SAGA
에 참여하는 서비스들이 각각의 이벤트를 구독함으로써 작업을 수행하고 rejection 여부를 확인해 보상트랜잭션
을 수행 합니다.
장점
- 이벤트를 발행함으로써 단순하게 서비스간 연결이 가능하다
- 이벤트 구독을 통한 느슨한 연결이 가능하다
단점
- 서비스마다 각각의 구현로직이 흩어져있어서 이해하기 어렵다
- 순환 의존이 생길 가능성이 있다
- 보상트랜잭션 수행을 위해 영향이 미치는 모든 이벤트를 구독함으로써 이벤트에 의거한 단단한 연결이 될 가능성이 있다
참여 서비스가 많지않은 간단한 MSA
시스템의 경우는 코레오그래피
스타일로 구현하기 편하지만 아무래도 서비스 갯수가 많고 복잡할수록 코레오그래피
보다는 오케스트레이션
스타일이 더 유리 할 수 있습니다.
Ochestration SAGA
오케스트레이션
스타일은 SAGA
참여 서비스들이 할 일들을 지휘해주는 클래스를 두어 모든 서비스의 흐름을 관장하는 방식입니다.
장점
- 연결 및 통신을
오케스트레이터
가 관장함으로써 의존관계를 단순화 할 수 있다 - 각 서비스들은 간편하게
오케스트레이터
가 호출하는 API만 구현하면된다 SAGA
편성 로직이오케스트레이터
에 모두 있어 각각의 서비스들은 비즈니스로직에만 집중 할 수 있다
단점
- 과다한 중앙화로 인한 서비스 구현의 불편함이 생길 수 있음
SAGA
편성 로직의 중앙화해 오케스트레이터
는 SAGA
패턴 구현을 위한 순서와 흐름
을 담당하고, 각 서비스들은 비즈니스 로직을 담당하여 관심사를 분리 할 수 있습니다.
위와같은 이유로인해 일반적으로 SAGA
패턴을 구현할때는 오케스트레이션
방식의 구현을 권장합니다.
SAGA Isolation
위에서 SAGA
패턴을 통해서는 ACID
의 Isolation (격리성)
를 보장 할 수 없다고 하였는데, Isolation
이 보장이 되지 않는다면 전체적으로 완료되지 않은 트랜잭션의 중간 데이터를 다른서비스들에서 읽어 갈 수 있는 문제가 생기게 됩니다.
일반적인 DB 에서 생각해본다면, commit
되지 않은 데이터 변경건이 다른 트랜잭션에서 read
된다면 트랜잭션
에 있어서 치명적인 결함이라고 말 할 수 있을텐데요 이로인해 발생 할 수 있는 문제점은 다음과 같습니다.
- dirty read (read uncommited)
- lost update (이중 update)
- non-repeatable read
위와같은 문제점이 발생하더라도 안전한, 멱등한 로직의 서비스라면 큰 문제는 없겠지만 그것이 아니라면 Isolation
을 보장해 줄 수 있는 대책을 세워줘야 할 것입니다.
아래에서 몇가지 대책들을 정리해보겠습니다.
- semantic lock : 해당 트랜잭션의 영향을 받는 레코드에 플래그를 설정하여 락을 구현합니다.
- commutative update : 어떤 순서로 실행되더라도 문제없도록 구현합니다. [ex : (+200 -100) == (-100 +200)]
- pessimistic view : dirty read를 최소화 하기위해 리스크가 있는 서비스의경우 반드시 성공하는 트랜잭션(재시도 가능 트랜잭션)의 위치로 순서를 변경해줍니다.
- reread value : lost update를 방지하기위한 방법으로, update전에 한번더 read하여 값이 변하지 않았는지를 체크합니다. (낙관적 오프라인 락 패턴)
- version file : 모든 레코드 수행작업들을 기록하여 반드시 순서에 맞는 동작만 처리되도록 해 줍니다. (ex : 주문(1) -> 승인(2)/거절(2)이 동시에 접근시 ‘주문(1) -> 승인(2) -> 거절(2)’ 혹은 ‘주문(1) -> 거절(2) -> 승인(2) ‘은 일어날 수 없음)
- by value : 데이터에따라 isolation이 매우 민감하다면 XA를 통해 완전한 Transaction을 별도로 구현해 줍니다.
카탈로그 서비스에 SAGA 적용해보기
우리의 카탈로그 서비스에 SAGA
를 적용해보도록 하겠습니다.
상품서비스에서 상품들을 조회하여 몇몇 상품들을 선택해 카탈로그에 매칭시키는 로직입니다. 이 매칭 정보는 카탈로그 DB와 상품 DB에도 같이 저장되어야 하는 데이터 일관성이 유지되어야 합니다.
순서 | 서비스 | 로직 |
---|---|---|
0 | 일반상품 서비스 | getProducts() |
1-1 | 일반상품 서비스 | verifyProduct() |
1-2 | 카탈로그 서비스 | matchProduct() |
1-3 | 일반상품 서비스 | updateCatalogMatchingInfo() |