본문 바로가기

Knowledge/Software Design

Distributed transaction (분산트랙잭션) (with. Saga pattern)

예전부터 MSA는 많이 듣고 이론으로 많이 접해봤지만, MSA가 완벽히 적용되서 운용되는 서비스를 직접 경험해보지 못했다.

실 운용 서비스에서 연관관계가 높은 서비스들을 어떻게 도메인을 분리하고 통신하고 연동하고 있을지 궁금하다.

 

대부분 내가 경험해봤던건 모놀리틱 서비스에서 MSA로 넘어가는 과정들이였다.

그 과정 속에서 직접적으로 와닿았던 문제는 크게 다음과 같은 2가지 문제였다.

 

1. 네트워크 통신 비용

   - MSA가 되면 기존과 다르게, 수많이 흩어진 서비스로부터 데이터를 가져와야한다. 어떻게 풀어낼 수 있을까? CQRS?

2. 분산트랜잭션

 

해당 포스트에선 두번째 문제 분산트랜잭션에 대해서만 다루고, 이를 해결하기 위한 방법을 정리해본다.

 

# 분산 트랜잭션

모든 아키텍쳐가 그렇듯 MSA에도 단점은 존재한다. MSA화를 직접 해보면서 더더욱 느낀다.

그 중 하나는 원자성을 보장할 수 없는 구조라는 것이다.

 

모놀리틱 구조에서는 코드가 물리적으로 같은 공간에 있었으므로 Spring의 도움을 받아 @Transactinal 어노테이션만 붙여주면 트랜잭션 기능을 손쉽게 사용할 수 있었다.

 

하지만, MSA에서는 물리적으로 분리된 여러 마이크로 서비스들이 독립적으로 존재하기 때문에, 각 서비스에서 발생하는 DB작업을 하나의 트랜잭션으로 묶기 위해서는 추가적인 고민이 필요하다.

이러한 분산 환경에서도 원자성을 보장하도록 하는 것이 바로 분산 트랜잭션이라고 한다.

 

분산 트랜잭션을 구현하는 방법에는 어떤 것들이 있을까?

 

# 자체적 코드로 풀어내기

원시적인 방법이라고 할 수 있다. 다른 라이브러리 도움없이 코드로 풀어내는 것이다.

try-catch를 이용해 MSA 서비스들을 관리하고, 보상 트랜잭션을 구현하는 것이다.

이는 코드 자체에서 생각해야할 것도 많고, 코드 자체가 많이 복잡해진다. 또한, 보상 트랜잭션에서 조차 실패한다면 답이 없다.

 

# two-phase commit

commit을 2번의 스텝을 거쳐 진행하는 것을 말한다.

1. prepare phase

   - coordinator 노드는 트랜잭션에 참가한 서비스들에게 커밋할 준비가 되었는지 물어본다. 

   - 참가한 서비스들은 yes 또는 no를 리턴한다.

2. commit phase 

   - 서비스들의 응답에 따라 커밋 또는 롤백을 진행한다.

 

이러한 two-phase commit 방법은 단순하고 유용하지만, 몇가지 단점을 가지고 있다.

1. 트랜잭션의 책임은 coordinator 노드에 있고, 이는 단일 장애 지점이 될 수 있다.

2. 모든 서비스의 응답을 기다려야 하므로, 하나의 서비스만 늦어져도, 시스템 전체적으로 느려질 수 있다.

3. NoSQL에서는 지원하지 않는다. 또한, 함께 사용하는 DBMS가 같은 제품군이여야 하므로, DBMS의 polyglot한 구성은 어렵다.

 

# saga pattern

saga pattern은 eventual consistency의 개념을 바탕에 두고, 각 서비스들의 로컬 트랜잭션을 연속적으로 수행하는 패턴을 말한다.

 

eventual consistency란?

한글로 말하면 최종 일관성이라는 뜻이다. 최종 일관성이란 사용자가 보기엔 잠깐 불일치하는 데이터가 있을 수 있지만, 결국에는 모든 데이터가 일치한다는 것을 말한다.

잠깐이라도 사용자에게 불일치된 데이터가 보이면 안되는 상황이라면, PENDING과 같은 상태값을 두고 사용자에게 일치될때까지 해당 상태값을 보여주는 식으로 구성할 수 있다.

 

saga pattern은 첫번째 서비스의 로컬 트랜잭션이 성공했다면, 두번째 서비스의 로컬 트랜잭션이 실행되는 식으로, 한 서비스의 로컬 트랜잭션 성공이 trigger가 된다.

만약, 중간에 트랜잭션이 실패했다면, 보상 트랜잭션이 실행된다.

이러한 보상 트랜잭션은 retryable & idempotent 해야한다. 

이러한 saga pattern을 구현할 수 있는 방법으로 2가지 방법이 존재한다.

- Choreography-based saga 

- Orchestration-based saga

 

# Choreography-based saga  

Choreography-base saga는 Event 방식으로 비동기로 작동하는 패턴이다.

각각의 서비스는 이벤트를 발행하고, 트리거하여 개별적으로 작동한다.

 

- 장점

   - 간단하고 구축하기 쉽다.

- 단점

  - 어떤 서비스가 어떤 이벤트를 수신하는지 추측하기 어렵다.

  - MSA 서비스가 많아지면 많아질수록, 어떤 이벤트가 발생하고 있고, 현재 전체 트랜잭션의 상태를 인지하기 어렵다.

 

 

# Orchestration-based saga

Orchestration 방식은 Command 방식으로 트랜잭션을 총괄하는 서비스(Orchestrator)가 존재하여, 각 서비스들에게 명령을 내리며, 총 사이클을 관리한다.

 

- 장점

  - 서비스 간의 종속성이 없고, Orchestrator가 호출하기 때문에, 분산 트랜잭션의 중앙 집중화가 된다.

  - 각 서비스는 다른 서비스를 신경쓸 필요 없으니, 복잡성이 줄고 테스트하기가 편리하다.

  - 롤백을 쉽게 관리할 수 있다.

- 단점

  - Orchestrator 라는 추가 인프라가 투입되므로, 이를 관리해야한다.

 

# saga pattern 구현체

Saga pattern 을 구현할 수 있는 프레임워크들이 있다.

- Axon framework saga (https://docs.axoniq.io/reference-guide/v/3.1/part-ii-domain-logic/sagas)

- Eventuate tram saga (https://eventuate.io/docs/manual/eventuate-tram/latest/getting-started-eventuate-tram-sagas.html)

- Temporal.io (https://temporal.io/)

- Apache camel (https://camel.apache.org/components/3.18.x/eips/saga-eip.html)

- 등등... 

 

그러나 기존 운용되고 있는 서비스에 saga 만을 위해 새로운 프레임워크를 적용하는 것은 다소 어려운 일이다.

spring framework에 saga를 지원하는 가벼운 라이브러리등이 존재하면 좋겠지만, 지금까진 그런건 보이지가 않는다...

 

결국 지금은 kafka를 이용해 evetual consistency를 직접 구현하려고 노력하고 있다.

 

실제로 MSA가 잘 정착하고 대규모 웹 서비스를 운용하는 곳에선 어떤 방법으로 분산트랜잭션을 구현하고 있을지 궁금하다.

 

 

 

참고

- https://www.baeldung.com/cs/saga-pattern-microservices

- https://microservices.io/patterns/data/saga.html

- https://www.msaschool.io/operation/integration/integration-four/

'Knowledge > Software Design' 카테고리의 다른 글

도메인(Domain) 관점 모듈 분리 (With. DDD, MSA)  (1) 2023.11.26
TDD (Test Driven Development)  (0) 2017.11.18