마이크로 서비스 아키텍처에서 가장 어려운 해결 과제인 트랜잭션 처리에 대해서 학습한 것을 공유하는 글입니다.
AS-IS
기존 모놀리식 아키텍처에서 트랜잭션이란, 하나의 DB를 사용하기 때문에 구현하는 것은 어렵지 않았습니다.
하나의 서비스를 기능 위한 여러 쿼리들의 발생을 하나의 논리적인 단위로 묶는 것을 트랜잭션이라고 하는데요.
그렇기 때문에 하나의 비즈니스 로직에서 트랜잭션으로 묶을 경우, 해당 로직을 실행중인 여러 로직 중 하나만 잘못되어도 전체 롤백 처리를 하면 됩니다.
이는 하나의 거대한 프로세스로 실행중인 모놀리식 아키텍처이기 때문에 가능한 방법입니다.
MSA에서는 여러 서비스들이 프로세스가 나뉘어서 실행중이기때문에 그만큼 DB를 서비스마다 나누어 쓰는 경우가 많기 때문에 그에 따라서 하나의 비즈니스 로직을 처리하는데 여러 서비스의 기능이 수행되어야되는 경우 생각해야할 것이 많아집니다.
그래서 트랜잭션 패턴이라는게 존재하는데요. 그에 대해서 알아보겠습니다.
트랜잭션 패턴
MSA 설계를 통해 도출된 서비스를 사용하여 트랜잭션을 해결해주기 위한 패턴
여러 서비스가 각 역할을 처리하여 하나의 일련된 비즈니스 로직을 실행하기 위해 기능하기 때문에 트랜잭션 처리는 중요해집니다.
송금이라는 케이스의 경우, 회원 서비스에서 송금인의 계좌번호를 체크하고, 계좌 확인 서비스에서 보내는 사람의 계좌를 확인하고, 최종적으로는 송금 서비스에서 송금이 이루어지게 됩니다.
그러나 3개의 서비스 중 어느 하나라도 응답에 실패할 경우 각 서비스마다 DB에 저장된 데이터에 대해 어떻게 롤백을 시킬 것인가를 고려해야합니다.
이에 대해 등장한 것이 트랜잭션 패턴입니다.
2PC(2Phase Commit)
트랜잭션의 완료를 2단계에 거쳐서 결정합니다.
Commit Request(1페이즈)하는 작업이 추가되어, 이에 대한 모든 작업이 성공 유무를 판단(2페이즈)하는 과정이 추가됩니다.
실패할 경우 1페이즈에서 했던 행동들이 무시됩니다.
그러나 이러한 점은 2페이즈를 판단하는 코디네이터에 대한 관리가 필요합니다.
Compensating Transactions(보상 트랜잭션)
특정 요청과 그 요청에 대해 정상적이고 완전히 종료된 “행동”(트랜잭션)을 그 이전 상태로 되돌리기 위한 “행동”(트랜잭션)을 위한 것입니다.
송금 서비스에서 보상 트랜잭션을 나타내면,
송금 요청이 들어왔을때, 송금 서비스 -> 계정 서비스 -> 계좌 확인 서비스에 거쳐서 송금이 이루어진다고 가정해봅시다.
만약 계좌 확인서비스에서 실패를 할 경우, 앞서 진행되었던 송금 서비스에 대해 보상 트랜잭션을 발생시킵니다.
이 때 미리 정의해놓은 서비스에 한해서만 보상 트랜잭션을 실행시킬 수 있습니다.
그러나, 이러한 점은 한가지 큰 문제가 있습니다.
보상 트랜잭션 자체도 서로 다른 서비스간의 통신이기 때문에, 송금 서비스에서 이러한 보상 트랜잭션을 받지 못할 가능성이 있습니다.
그렇기 때문에 비동기 처리나, 최대한 보상 트랜잭션을 구현하지 않는 방법을 고려해보는 것이 요구됩니다.
Saga Pattern(사가 패턴)
트랜잭션의 선, 후 관계를 사전에 정의하고, 필요와 경우에 따라 코디네이터가 보상 트랜잭션을 이용, 관리하여 분산 시스템 환경에서 트랜잭션을 구현하기 위한 패턴입니다.
신뢰성 패턴
말 그대로 어플리케이션을 얼마나 신뢰할 수 있는가에 대한 패턴
마이크로 서비스 아키텍처로 설계됨에 따라 서비스가 분리/분해로 인해 떨어진 신뢰성을 해결하기 위한 패턴
즉, 장애 복구, 자가 치유, 무중단 배포 등을 구현하기 위한 패턴
서킷 브레이커 패턴
신뢰성을 높이기 위한 패턴으로 분산 시스템에 의한 장애 전파를 막고 피해를 최소화하기 위한 패턴
A -> B -> C의 과정으로 서비스를 호출할 때,
C가 오류가 발생하게 되면 B에서는 C를 요청할 때 500 에러를 받는다.
이때, B -> C를 요청하기 때문에 다양한 A의 서비스가 문제가 생기게 되는 것
이럴때 장애 전파를 막고, 이떄의 피해에 대한 데이터 손실을 최소화하기 위해 존재한다.
장애 복구
위 사례에서 서킷 브레이커 패턴을 적용해서, B에 서킷 브레이커를 설정할 경우,
예를 들어 10초동안 100번 이상이 500 에러가 발생하는 조건을 정의해두었다고 하자.
이 조건이 충족되면 B -> C로의 모든 요청을 차단한다.
그리고 B가 받는 요청(예를 들면 A로 부터)에게 반대로 200을 주면서 서킷 브레이커가 발생하는 것을 알린다.
200을 주는 이유는 A 서비스로의 장애 전파를 막기 위해서이다.
200을 받은 A는 이떄의 요청(B로 보내는)을 따로 저장했다가, 문제가 해결되고 나서 처리를 할 수 있다.
자가 치유
이때 A라는 서비스는 B의 신뢰성 패턴이 구현됨에 따라 A라는 서비스 자체를 보장할 수 있게 되었다.
물론 B의 서킷 브레이커 조건이 발동하면 지속적으로 C에게 서비스 생존 유무를 묻는다.
그리고 C가 재생하면 다시 정상적으로 A -> B -> C의 비즈니스 기능이 재개된다.
무중단 배포
Graceful ShutDown을 의미하며, 기존의 서비스가 배포로 인해 프로그램이 버전업 된다고 했을 때, 서버는 동일하기 때문에 기존의 서비스는 죽어야하고, 새로운 서비스로 교체되어야한다.
쿠버네티스의 경우, 서비스에서 파드로 기존의 서비스로 보내는 요청을 막게 된다.
그리고 기존 서비스는 자원을 해제하고, 트랜잭션을 종료시키고 서버를 내리게 된다.
그리고 새로운 서버가 자리를 잡게 된다.
이 때 서비스쪽에서 새로운 서버로 헬스체크를 한 뒤 정상적으로 서버 배포가 완료된다.