MSA 트랜잭션 (MSA-3)

MSA Transaction

MSA 구성을 할 계획이시라면 아마도 가장 고민되는 사항은 MSA 서비스간의 트랜잭션일 것입니다.

기존 monolithic 시스템에서는 동일 DB를 사용하면서 매우 간단하게 트랜잭션이 보장이 되었겠지만, MSA 시스템에서는 DB가 분리되었을 뿐만 아니라 다중 데이터소스를 통해 데이터를 직접 접근하는것이 아닌 서비스간 통신을 사용하기때문에 트랜잭션을 구성하기가 더욱 어려운것이 사실입니다.

물론 서비스간의 트랜잭션이 필요없도록 설계를 잘 하셨을 수도 있지만, 서비스가 확장되고 규모가 커질수록 어떠한형태로든 트랜잭션이 필요하게 될 가능성이 높습니다.


XA 분산 트랜잭션

XA는 분산 트랜잭션 처리를 위해 X-Open에서 명시한 표준 기술로써, 2-PC(2 Phase Commit)을 이용해 여러 서비스의 일관성을 유지해줍니다.

1

XA는 대부분의 SQL DB메시지 API, 메시지 브로커 등에서 호환하여 사용 할 수있도록 지원이되며 JAVA 개발을 한다면 JTA기술을 통해 손쉽게 트랜잭션처리를 할 수 있다는 장점을 가집니다.

하지만 XA가 호환되지 않는 환경들도 있습니다.
NoSQL(몽고, 카산드라...), 현대 메시지 브로커(RabbitMQ, Kafka) 등에서는 XA를 지원하지 않습니다.

또한 큰 단점으로 트랜잭션이 걸린 모든 서비스들에 동기 IPC를 유지해야 하기때문에 MSA에서의 가용성이 떨어 질 수 밖에 없다는 단점이 있습니다.

정리해보자면, XA는 여러 자원들의 Transaction을 하나로 묶어주기에 ACID를 보장할수 있는 가장 안전한 방법이지만 MSA 환경에서 가용성측면에서는 떨어지는 편이기 때문에, 이번 포스팅에서는 XA보다는 유연하게 더 넓은 상황에서의 데이터 일관성을 유지시켜줄수있는 SAGA 패턴을 통한 MSA 서비스간 트랜잭션 유지방법에 대해서 집중해보고자 합니다.


SAGA 패턴

SAGA는 위 제목에서도 알수 있다시피 트랜잭션을 보장하기위한 기술이 아닌 데이터 일관성을 유지하기 위한 하나의 패턴 입니다.

SAGA를 구현하기위한 다양한 패턴들이 있지만, 보상 트랜잭션을 통해 롤백할 수 있도록 구현하는것이 핵심입니다.

2

보상 트랜잭션이란 정상적으로 수행된 서비스 트랜잭션을 취소혹은 되돌리기 할 수 있는 트랜잭션으로, Query로 따져보자면 Insert <-> Delete, Update <-> Update, Create <-> Drop의 관계가 될 것입니다.
그렇다고 모든 트랜잭션에 대해 보상트랜잭션을 만들어줄 필요는 없고, 읽기전용 혹은 항상 성공하는 단계의 트랜잭션의 경우 보상트랜잭션을 만들어 줄 필요가 없습니다.

DB트랜잭션기능을 사용하지 않기때문에 자동 롤백이 되지 않는다는 점을 생각하면, 다중 DB 환경에서 데이터 일관성유지 측면에서 필수적으로 만들어주어야하는 기능입니다.

위에서 알아본것같이 SAGA개별적인 트랜잭션들을 개발자가 컨트롤 함으로써 데이터의 일관성을 유지해주는 방식이라 트랜잭션의 주요 원칙인 ACIDIsolation (격리성)을 보장해 줄 수는 없다는 단점을 기억해야 합니다.

Choreography SAGA

SAGA를 구성하는 첫번째 스타일으로 코레오그래피 스타일이 있습니다. SAGA에 참여하는 서비스들이 각각의 이벤트를 구독함으로써 작업을 수행하고 rejection 여부를 확인해 보상트랜잭션을 수행 합니다.

3

4

장점

  • 이벤트를 발행함으로써 단순하게 서비스간 연결이 가능하다
  • 이벤트 구독을 통한 느슨한 연결이 가능하다

단점

  • 서비스마다 각각의 구현로직이 흩어져있어서 이해하기 어렵다
  • 순환 의존이 생길 가능성이 있다
  • 보상트랜잭션 수행을 위해 영향이 미치는 모든 이벤트를 구독함으로써 이벤트에 의거한 단단한 연결이 될 가능성이 있다

참여 서비스가 많지않은 간단한 MSA 시스템의 경우는 코레오그래피 스타일로 구현하기 편하지만 아무래도 서비스 갯수가 많고 복잡할수록 코레오그래피 보다는 오케스트레이션 스타일이 더 유리 할 수 있습니다.

Ochestration SAGA

오케스트레이션 스타일은 SAGA참여 서비스들이 할 일들을 지휘해주는 클래스를 두어 모든 서비스의 흐름을 관장하는 방식입니다.

5

6

장점

  • 연결 및 통신을 오케스트레이터가 관장함으로써 의존관계를 단순화 할 수 있다
  • 각 서비스들은 간편하게 오케스트레이터가 호출하는 API만 구현하면된다
  • SAGA편성 로직이 오케스트레이터에 모두 있어 각각의 서비스들은 비즈니스로직에만 집중 할 수 있다

단점

  • 과다한 중앙화로 인한 서비스 구현의 불편함이 생길 수 있음

SAGA편성 로직의 중앙화해 오케스트레이터SAGA패턴 구현을 위한 순서와 흐름을 담당하고, 각 서비스들은 비즈니스 로직을 담당하여 관심사를 분리 할 수 있습니다.

위와같은 이유로인해 일반적으로 SAGA패턴을 구현할때는 오케스트레이션 방식의 구현을 권장합니다.


SAGA Isolation

위에서 SAGA패턴을 통해서는 ACIDIsolation (격리성)를 보장 할 수 없다고 하였는데, Isolation이 보장이 되지 않는다면 전체적으로 완료되지 않은 트랜잭션의 중간 데이터를 다른서비스들에서 읽어 갈 수 있는 문제가 생기게 됩니다.

일반적인 DB 에서 생각해본다면, commit 되지 않은 데이터 변경건이 다른 트랜잭션에서 read된다면 트랜잭션에 있어서 치명적인 결함이라고 말 할 수 있을텐데요 이로인해 발생 할 수 있는 문제점은 다음과 같습니다.

  • dirty read (read uncommited)
  • lost update (이중 update)
  • non-repeatable read

7

위와같은 문제점이 발생하더라도 안전한, 멱등한 로직의 서비스라면 큰 문제는 없겠지만 그것이 아니라면 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()

8