본문 바로가기
  • Where there is a will there is a way.
개발/기타개발

마이크로 서비스 트랜잭션

by 소확행개발자 2020. 5. 24.
내가 정리한 내용은
마이크로 서비스 패턴
(공)저: 크리스 리처드슨
에서 참조한 내용이다.

기존 앤터프라이즈 애플리케이션을 개발할 때

 

createOrder() 를 만든다고 해보자

1. 주문 가능한 소비자인지 확인

2. 주문 내역 확인

3. 소비자의 신용카드를 승인하고

4. DB 에 주문을 생성

 

4가지의 작업이 하나의 DB 에서 이루어 진다.

 

스프링 프레임워크를 사용할 경우에 우리는 흔히

 

@Transactional
public void createOrder() {

1. 주문 가능한 소비자인지 확인

2. 주문 내역 확인

3. 소비자의 신용카드를 승인하고

4. DB 에 주문을 생성

}

로 해결할 수 있다. 이러면 ACID 가 보장된다.

 

하지만 ,

마이크로 서비스 아키텍처는 

1-> 소비자 서비스

2-> 주문 서비스

3-> 회계 서비스 

 

등 서비스가 분산되어 있고 해당 서비스마다 DB 가 따로 있을 수 있기 때문에 데이터의 일관성을 유지할 수 있는 수단을 강구해야 한다.

 

분산 트랜잭션

예전에는 분산 트랜잭션을 이용해서 여러 서비스 , DB, 메시지 브로커에 걸쳐 데이터의 일관성을 유지했다고 한다.

 

분산 트랜잭션 처리 XA 는 대부분 SQL DB 와 호환되고 자바 애플리케이션은 JTA ( java transacation api ) 기술을 이요하여 분산 트랜잭션을 수행할 수 있다. 

 

분산 트랜잭션은 NoSQL, 메시지 브로커 ( 아파치 카프카 ) 의 경우엔 지원하지 않기 때문에 해당 관련 DB 를 트랜잭션으로 관리해야하는 경우라면 상당부분 포기해야 하는 부분이 생긴다.

 

동기 IPC ( Inter-Process Communication ) 형태라서 가용성 ( HA ) 이 떨어지는 문제점도 있다. 

-> 프로세스간의 데이터 통신이 동기로 이루어 진다. ( 즉 주문 서비스가 폭주해서 여러개 만들어지는 경우가 생긴다면 ? 그런 경우에 분산 트랜잭션을 보장해야 한다면? 분산 트랜잭션이라 주문 서비스에서 동기 IPC 형태라서 ( 프로세스끼리 데이터 공유가 안되기 때문에 늘릴 수 없다 ) 시간이 오래 걸리는 문제점이 생기지 않을까 ?

 

 개발자 관점에서 분산 트랜잭션은 로컬 트랜잭션과 프로그래밍 모델이 동일하므로 매력적이지만, 지금까지 설명한 문제점 때문에 요즘 애플리케이션에 잘 맞지 않는다.

 

마이크로 서비스 아키텍처에서 데이터 일관성을 유지하려면, 느슨하게 결합된 비동기 서비스 개념을 토대로 뭔가 대른 메커니즘이 필요하지 않을까?? 

 

마이크로 서비스 사가 패턴

사가는 비동기 메시징을 이용하여 편성한 일련의 로컬 트랜잭션이다.

 

사가는 마이크로서비스 아키텍처에서 분산 트랜잭션 없이 데이터 일관성을 유지하는 메커니즘 이다. 

 

여러 서비스의 데이터를 업데이트하는 시스템 커맨드마다 사가를 하나씩 정의한다. -> 사가는 일련의 로컬 트랜잭션이다. 

로컬 트랜잭션 개념이기 때문에 각각의 서비스별 데이터를 업데이트 한다. 

 

시스템은 우선 사가의 첫 번째 단계를 시작한다. 

어느 로컬 트랜잭션이 완료되면 이어서 다음 로컬 트랜잭션이 실행된다. 

-> 통신을 해서 호출을 하든 비동기식으로 섭스크라이버가 호출을 하든

 

비동기 메시징이기 때문에 하나 이상의 사가 참여자가 일시 불능 상태인 경우에도 사가 전체 단계를 실행시킬 수 있다. 

-> 장점이자 장점이 아닐까? 모든 트랜잭션이 보장되어야 하는 상황에서 참여자가 일시 불능일 상태를 만든다면

-> 해당 참여자가 일시 불능일 때의 처리상황을 정확하게 만들어 놔야한다.

 

사가와 ACID 트랜잭션에는 2가지의 차이가 있다. 

1. ACID 트랜잭션에 있는 isolation 이 사가에는 없다.

2. 사가는 로컬 트랜잭션만다 커밋을 함으로 보상 트랜잭션을 걸어 롤백해야 한다.

 

 

주문 생성 사가

이 사가의 첫 번째 로컬 트랜잭션은 주문 생성이라는 외부 요청에 의해 시작된다. Text2 트랜잭션 부터는 자신의 선행 트랜잭션이 완료되면 트리거 되게 된다. 

 

메시지를 발행하여 다음 사가 단계를 트리거 한다.

 

로컬 트랜잭션이 실패하면 사가는 주문과 티켓을 무효화하는 보상 트랜잭션을 가동한다.

-> 물론 모든 단계가 보상 트랜잭션이 필요한건 아니다 ex ) 조회하는 로직은 필요 없다.

 

다음은 트랜잭션 서비스의 보상 트랜잭션을 나타낸 표이다.

 

사가 편성

그렇다면 사가를 어떻게 만들 것인가?

사가는 단계를 편성하는 로직으로 구성된다.

 

첫번째 사가 참여자를 지시하고 트랜잭션을 시행한다. 

트랜잭션이 완료되면 그다음 사가 참여자를 호출하는 시행을 반복한다.

도중에 하나라도 로컬 트랜잭션이 실패하면 사가는 보상 트랜잭션을 역순으로 실행하면 된다.

-> 여기서 더 깊게 이해하려면 과연 사가를 결정하는건 누구인가? 에 대한 질문을 던질 수 있다.

  • 코레오 그래피 방식
    • 사가의 의사결정과 순서화를 사가 참여자에게 맡긴다 사가 참여자는 이벤트 교환 방식으로 통신한다.

 

 

  • 오케스트레이션 방식
    • 사가 편성 로직을 사가 오케스트레이션 중앙화한다. 사가 오케스트레이션 서버는 사가 참여자에게 커맨드 메세지를 보내 작업을 수행한다.

 

아주 단순한 방식이 아니라면 오케스트레이션 방식을 권장한다. 

 

트랜잭션 isolation 

트랜잭션은 AICD 가 보장되어야 하는데

마이크로 서비스 사가 패턴은 isolation 이 보장되지 않는다. 

-> 한 사가가 실행도중 접근하는 데이터를 도중에 누가 가로챌 수 있다.

 

격리가 안되면 anomaly ( 이상 ) 이 발생될 가능성이 있다.

  • lost update : 한 사가의 변경분을 다른 사가가 미처 못읽고 덮어쓸 수 있다.
  • dirty read : 사가 업데이트를 하지 않은 데이터를 다른 트랜잭션의 사가가 읽을 수 있다.
  • non reapeatable read : 한 사가에서 데이터를 읽는데, 같은 데이터 조회의 결과가 달라질 수 있다.

대책은 시멘틱 락 -> 애플리케이션 단의 락을 건다. ( 버전 관리 )

댓글