사가(saga) 패턴

차분한열정·2021년 6월 6일
0

하나의 서비스 내에서 ACID 트랜잭션을 수행하는 것은 쉽다. 그런데 마이크로서비스 아키텍처에서 만약 여러 서버시의 데이터를 업데이트하는 트랜잭션은 구현하기가 까다롭다. 이 때의 트랜잭션은 ACID 트랜잭션 대신 사가(saga)라는 메시지 주도 방식의 로컬 트랜잭션을 사용해야 한다. 그런데 사가는 ACID에서 I(isolation)이 빠진 ACD만 지원한다.

예를 들어 하나의 요청에 대한 트랜잭션이 각 서비스의

A서비스의 트랜잭션
B서비스의 트랜잭션
C서비스의 트랜잭션
D서비스의 트랜잭션
E서비스의 트랜잭션
..

과 같은 여러 단계의 순차적인 트랜잭션들을 요구할 때, 만약 특정 단계의 트랜잭션이 실패하게 되면, 사가에서는 보상 트랜잭션으로 변경분을 롤백한다.

즉, 위의 경우에는

A서비스의 트랜잭션 - A 보상 트랜잭션
B서비스의 트랜잭션 - B 보상 트랜잭션
C서비스의 트랜잭션 - C 보상 트랜잭션
D서비스의 트랜잭션 - D 보상 트랜잭션
E서비스의 트랜잭션 - E 보상 트랜잭션

이런 식으로 각 트랜잭션에 대응되는 보상 트랜잭션이 있다. 물론 단지 값을 단순히 읽기만 하는 등의 트랜잭션의 경우 굳이 보상 트랜잭션이 필요하지 않은 경우도 있다.

1. 사가 패턴의 종류

사가 패턴은 각 서비스가 메시지 기반으로 통신하는 코레오그래피(choreography) 방식과 하나의 사가 오케스트레이터가 사가 참여자에게 커맨드 메시지를 보내 작업을 지시하는 오케스트레이션(orchestration) 방식이 있다.

(1) 코레오그래피 방식

이 방식에서는 각 서비스가 이벤트 기반 통신을 한다. 즉, 각 서비스는 메시지 브로커에 여러 이벤트 메시지를 보내고 각 채널을 구독하고 있던 서비스들은 해당 이벤트가 발생하면 그에 따른 트랜잭션을 수행하고 다시 이벤트를 발생한다. 이때 주의할 점은 사가 참여자가 확실하게 통신하려면 각 참여자가 그만의 트랜잭션을 수행할 때 메시지를 발행하는(해당 데이터베이스 안의 테이블에 메시지를 insert) 동작도 반드시 트랜잭션 안에 포함시켜야 한다는 것이다.(이것을 '트랜잭셔널 메시징'이라고 한다). 그럼 나중에 폴링 방식이나 트랜잭션 로그 테일링 방식으로 메시지 브로커에 메시지를 전달할 수 있게 된다.

이런 코레오그래피 방식은 단순하고, 느슨한 결합이 된다는 장점은 있지만, 각 로직이 각 서비스에 종속되어 있기 때문에 현재 아키텍처에서의 사가가 전반적으로 어떤 식으로 흘러가는지 플로우를 파악하기가 어렵다는 단점이 있다. 이때문에 복잡한 사가는 코레오그래피 방식보다는 주로 오케스트레이션 방식을 사용한다.

(2) 오케스트레이션 사가

여기서는 각 사가 참여자가 할 일을 알려주는 오케스트레이터가 있다. 이렇게 되면 모든 로직이 오케스트레이터에 있기 때문에 이해하기가 편해진다.

2. 사가 패턴의 한계

사가 패턴은 ACID 중에서도 I를 보장하지 못한다. 즉, 복수의 사가가 수행될 때 한 사가의 특정 트랜잭션이 커밋한 변경분을 다른 사가가 바라볼 수 있다는 뜻이다. 로컬 디비에서야 트랜잭션을 수행할 때 이런 문제가 없었지만 사가 패턴에서는 어쩔 수 없는 한계인데 이런 문제를 최소화할 수 있는 방안들이 있기는 하다. I를 보장하지 못하면 소실된 업데이트 문제(한 사가가 변경한 것을 다른 사가가 다른 값으로 덮어써버림), 더티 읽기 문제(한 사가가 아직 채 업데이트하지 못한 값을 다른 사가가 읽어버림) 등이 발생할 수 있습니다.

3. 대책

이런 한계를 그나마 극복하기 위한 대책으로는 다음과 같은 방법들이 있다. 일단 그 전에 하나의 사가에 존재할 수 있는 트랜잭션 종류 3가지를 살펴보자.

  • 보상 가능 트랜잭션: 보상 트랜잭션으로 롤백 가능한 트랜잭션
  • 피봇 트랜잭션: 사가의 진행/중단 지점. 피봇 트랜잭션이 커밋되면 사가는 완료될 때까지 실행된다. 피봇 트랜잭션은 보상 가능 트랜잭션, 재시도 가능한 트랜잭션 그 어느 쪽도 아니지만 (최종) 보상 가능 트랜잭션 또는 (최초) 재시도 가능 트랜잭션이 될 수는 있다.
  • 재시도 가능 트랜잭션: 피봇 트랜잭션 직후의 트랜잭션, 반드시 성공한다.

자, 이제 각 대책들을 살펴보자.

(1) 시맨틱 락

보상 가능 트랜잭션이 생성/수정하는 레코드에 무조건 PENDING* 등과 같은 플래그를 세팅하는 대책이다. 아직 완벽한 커밋 전이라서 변경될지 모른다는 표시를 하는 것이다. 그리고 락을 걸거나 그냥 두더라도 이것이 경고의 의미가 되도록 두는 것이다. 그럼 만약 하나의 사가(A)가 PENDING 으로 표시해둔 곳을 다른 사가(B)가 업데이트하려는데 이렇게 PENDING_ 이라고 써있는 것을 보면 어떻게 해야할까? 일단 B를 취소하고 클라이언트에게 나중에 다시 시도하라고 알리거나, 해당 락이 해제될 때까지 요청을 블로킹하는 것이다. 대신 이렇게 하려면 락을 관리하는 부담을 감수해야 한다.

(2) 교환적 업데이트

은행 계좌 인출과 입금과 같이 업데이트를 어떤 순서로도 실행 가능하게 설계하면 하나의 사가가 업데이트한 내용을 다른 사가가 덮어쓸 일은 없다.

(3) 비관적 관점

(4) 값 다시 읽기

(5) 버전 파일

(6) 값에 의한 대책(비지니스 위험성 기준 선별)

profile
성장의 기쁨

0개의 댓글