3 minute read

Saga 패턴

마이크로서비스에서 SAGA는 분산트랜잭션 문제를 해결하기 위한 패턴으로 얘기된다. 독립적으로 동작하는 마이크로서비스를 만들고 서비스하면 사실 분산트랜잭션은 고려할 필요가 없다. 하지만 엔터프라이즈 시스템을 구축하기 위해 서비스를 설계하다보면 부득이하게 타 서비스와 연동해서 하나의 업무 프로세스가 종료되는 사례가 많다.

여기서 잠깐 ‘위키백과’에서 정의하고 있는 분산 트랜잭션의 개념에 대해서 알아보자. 분산트랜잭션 (영어: Distributed transaction)은 2개 그 이상의 네트워크 상의 시스템 간의 트랜잭션이다. 일반적으로 시스템은 트랜잭션 리소스(transaction resource)의 역할을 하고, 트랜잭션 매니저(transaction manager)는 이러한 리소스에 관련된 모든 동작에 대해 트랜잭션의 생성 및 관리를 담당한다. 분산 트랜잭션은 다른 트랜잭션처럼 4가지 ACID(원자성, 일관성, 고립성, 지속성) 속성을 갖추어야 하며, 여기에서 원자성은 일의 단위(UOW)를 위해 all-or-nothing 결과를 보증해야 한다.’ 라고 정의 되어있다.

sourcecode

Coffee 전문점에 필요할 것 같은 커피전문점 서비스 애플리케이션 기능을 예를 들어 보겠다. Coffee 전문점에서 커피를 주문하면 회원여부를 확인해서 포인트를 적립해 준다. Coffee 전문점에서 사용하는 시스템을 가정해서 생각해 보겠습니다. 만약 위와 같은 ‘커피주문’, ‘포인트적립’ 기능들은 과거 ‘모놀리스(Monolith)’ 형태의 애플리케이션에서는 두가지 기능을 하나의 트랜잭션으로 묶어서 구현했을 것이다. 다이어그램으로 표현해 보면 아래와 같다.

sourcecode

‘커피주문’ 후 ‘포인트적립’ 기능이 수행된다. 이 두 기능이 성공적으로 실행되면 하나의 트랜잭션이 종료된다. ‘커피서비스’라는 애플리케이션 하나에서 ‘커피주문’과 ‘포인트적립’기능을 모두 처리하는 것입니다. 그런데 이런 형태의 모놀리스 애플리케이션의 문제점은 기능장애에 대한 격리가 되지않고 애플리케이션 전체에 영향을 미친다는 것이다. ‘포인트적립’ 기능에 문제가 생기면 ‘커피주문’기능이 작동하지 않을 것이다. 이런점은 서비스 가용성 측면에서 부담이 되는 이슈가 될 것이다. 그럼 마이크로서비스로 설계해 보자. 서비스를 분할해서 ‘커피주문’과 ‘포인트적립’ 이라는 두개의 서비스로 하나의 애플리케이션을 두개의 애플리케이션으로 나누어 보겠다. 서비스를 두개로 분할하면 서로 다른 마이크로서비스로 영향도가 최소화 된다. ‘포인트적립’ 애플리케이션에 문제가 생겨도 ‘커피주문’ 서비스는 여전히 서비스가 가능하다.

sourcecode

그렇지만 완전히 독립적으로 동작하지 않는다. ‘커피주문서비스’, ‘커피회원서비스’가 순차적으로 실행되어야하고 하나의 트랜잭션으로 처리되어야 하기 때문이다. 물론 각 마이크로서비스가 참조하는 데이터에 대한 분할 설계를 좀 더 개선하면 의존성을 최소화 할 수 있지만 Saga 패턴을 설명하기 위한 예시이니 두개의 서비스 트랜잭션을 보장하기 위한 방법을 Saga 패턴으로 접근해 보자.

크리스리차드슨이 microservices.io에서 설명한 Saga패턴에는 두가지 접근 패턴이 있다. 하나는 오케스트레이션(Orchestration) 방식이고, 다른 하나는 코레오그래피(Choreography)방식이다. 먼저 오캐스트레이션 방식은 전체 트랜잭션의 시작과 끝은 확인하는 오케스트레이터(Orchestrator)와 각 마이크로서비스의 트랜잭션 시작과 종료를 제어하는 코디네이터(Coodinator)가 있다.

sourcecode

그림에 보는 것처럼 ‘커피주문서비스’와 ‘커피회원서비스’에서 수행하는 개별 트랜잭션에 대해 관리한다. 전체 트랜잭션의 상태를 모니터링하고 각 개별 트랜잭션 중 하나라도 실패하면 전체 서비스 트랜잭션에 대해서 보상 로직을 수행한다. 보상 로직은 트랜잭션 발생 전으로 데이터의 일관성을 유지하기 위한 처리로직이다. ‘보상트랜잭션’이라고도 한다. ‘보상트랜잰션’전략에 대해서는 다음에 설명하겠다.

그림에서 보면 오케스트레이터가 코디네이터에게 서비스의 시작 요청을 하고 코디네이터는 전체 트랜잭션을 시작한다. 이후 각 마이크로서비스를 수행하여 각 마이크로서비스의 개별 트랜잭션 결과를 받는다. 최종 실행되는 마이크로서비스의 결과가 정상이면 전체 트랜잭션이 성공했음을 오케스트레이터에 알린다. 오케스트레이터는 해당 비즈니스 로직의 수행상태를 성공으로 처리한다. 오케스트레이션 Saga패턴에서는 수행되는 마이크로서비스 간에는 위계가 있다. 즉 수행 순서가 있으면 각 마이크로서비스의 결과에 따라 후행 마이크로서비스가 수행 될 지 아니면 보상트랜잭션을 수행 할 지 결정한다.

다음은 코레오그래피 방식이다. 코레오그래피는 ‘여러 움직임들을 결합하여 댄스로 만드는 기술’이다. 여러 움직임들을 음악과 조화롭게 연결시키는 작업이다. 만약 여러명의 댄서가 있다면 그들이 음악에 맞춰 누가 지휘하는 것도 아닌데 음악 맞춰 하나의 완성된 공연을 만드는 것이다. 오케스트레이션 방식이 중앙의 지휘자가 전체 연주를 연주하는 오케스트라 연주에 비유한다면 코레오그래피는 발레에 비유할 수 있다. 음악에 맞춰 발레리나들이 각자의 역할에 맞게 춤을 추는 것이다.

sourcecode

코레오그래피 방식은 오케스트레이션 방식처럼 중앙에서 제어하는 코디네이터 역할이 없기 때문에 각 마이크로서비스는 이벤트를 구독하고 이벤트를 수신하면 자신의 서비스를 수행한다. 이후 완료되면 결과를 코레오그래퍼에 전달한다. 모든 이벤트는 이벤트버스(EventBus)를 통해서 전달한다. 코레오그래퍼(Choreographer)는 이벤트버스의 상태를 관찰하고 이벤트를 생성하는 역할을 수행한다. 특정 마이크로서비스(커피주문서비스)에서 장애가 발생하면 ‘커피회원서비스’에 실패 이벤트를 생성하여 전달해서 ‘포인트적립’ 취소기능 등 보상트랜잭션을 수행한다. 코레오그래피 방식은 마이크로서비스들의 실행 순서를 보장하지 않는 비동기 방식이다. 마이크로서비스들이 많아지면 디버깅 같은 작업들이 어렵고 복잡해지지만 확장성 측면에서 서비스 간 느슨한 관계를 만들수 있는 패턴이다.

패턴은 반복적으로 발생하는 문제에 대한 해결방법이다. 해결방법을 찾기보다는 기본적으로는 분산트랜잭션이 발생하지 않게 설계하는 것이 가장 좋은 설계일 것이다. 두 마이크로서비스가 서로 통신해야 할 경우가 많다는 것은 비즈니스와 데이터측면에서 상호 의존성이 높다는 의미이기도하다. 마이크로서비스 아키텍처는 애플리케이션을 비즈니스 변화에 쉽게 대응할 수 있고 빠르게 배포될 수 있는 구조로 설계하는 것이 목적이다. 전체 하나의 단일 애플리케이션으로 시작해서 효용성을 고려하여 조금씩 점진적으로 분할해 나가는 것이 바람직한 설계전략이다.

Updated: