Web

MSA에서 다른 서비스의 트랜잭션이 실패된다면 어떻게 해야할까?, Saga

mayleaf 2024. 4. 13. 10:32

이 글은 MSA 환경에서 다른 서비스의 트랜잭션 Fail으로 인한 보상로직을 작성하는 방법에 대해서 다루는 글입니다.

이 글을 쓰는 이유

최근에 MSA하다가 A 컴포턴트에서 트랜잭션을 마치고, B 컴포넌트에 이벤트를 던져두었더니, B 컴포넌트가 제대로 처리를 못하면 어떻게 해야하나라는 질문을 받았습니다. 그래서 해결책을 찾아보다가 이 해결책으로 Saga pattern을 알게 되었고, Saga pattern에 대해서 공유하고자 글을 씁니다.

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

 

Microservices Pattern: Pattern: Saga

Implement transactions using a saga, which is sequence of local transactions

microservices.io

Saga pattern에 대해서 잘 정리해둔 글입니다.

본문

우선 Saga 패턴은 어떤 줄임말로 만들어진 패턴이 아니라 긴 여정을 의미하는 단어 Saga입니다.

서로 다른 도메인들 사이에서 이벤트를 주고받는 과정을 은유적으로 긴 여정으로 표현한 것 같습니다.

이 Saga Pattern을 아래 목차에 따라서 서술하겠습니다.

목차

  1. 문제의 예시 케이스
  2. 해결 방안
  3. SagaPattern의 정의를 다시 되돌아보기

예시 케이스

 

 

서로 분산된 환경에 배포된 두 서비스가 있다고 해볼까요? 주문서비스에서 주문을 받으면, 결제도 해야겠죠? 잔고를 확인해야할 것이고 돈이 잘 들어있으면 문제 없이 결제가 끝날 것입니다.

이 케이스에서는 각각의 서비스에서 실패가 발생했을때를 고려하지 않기 때문에 각각의 서비스에서 주문이 들어오면 바로 트랜잭션을 커밋하고, 고객 서비스도 잔고를 깎아버리고 트랜잭션을 커밋하고 끝이 납니다.

 

하지만 잔고가 없다면 어떻게 될까요? 주문 서비스는 이미 주문이 들어갔다고 트랜잭션을 끝냈는데, 결제는 안된 상황이 발생할 수 있습니다.

 

그렇기 때문에 우리는 실패에 대한 케이스도 준비를 해야합니다. 어떻게 해야할까요?

연결된 서비스가 성공을 하면 성공을 했다는 알림을 받고, 실패를 했다면 실패했다는 것을 알아야 할것입니다.

여기에서 두 서비스 간에 통신이 아래 사진처럼 API로 주고 받으면 어떻게 될까요?

1,2,3이 이뤄지고나서 4번을 호출하려고 할때 OrderService가 통신이 불가능한 상태가 된다면 다시 Rejected된 상태로 만들기 위해서 많은 고생을 해야할 것입니다. pending 상태가 일정 이상으로 남은 주문들을 확인하고 그 주문들이 customer쪽에서 실패된 기록이 있는지도 확인해야할 것입니다. 이런 장애가 있는지도 우린 알 수 없기때문에, schedule로 계속 체크해야할 것이구요. 모든 pending된 주문 건들에 대해서 계속 확인하면서 업데이트를 쳐주는건 정말 큰 부하일 것입니다.

 

그리고 Customer팀이 Order 팀의 API가 어떻게 구현되어있는지 알아야한다는 문제도 있습니다. OrderService의 API를 호출하기 위해서는 결국 어떻게 호출해야하는지 알아야하고, 그게 Customer 도메인의 관심사에도 들어가버립니다.

해결 방안

그래서 이런 문제를 해결하고자 API를 통해서 통신하기보다는 아래 그림처럼 메시지 기반으로 소통하는 것을 권장합니다.

메시지를 읽고 로컬 트랜잭션이 완료된 다음 Reply 메시지 채널에 결과를 보내고, 해당 채널을 OrderService에서 구독해서 reject이면 주문을 취소하고, 성공했으면 주문이 그대로 완료됩니다. Customer서비스는 자신이 만든 이벤트만 정의해서 메시지스트림에 던져두면 되고, 자신이 처리해야하는 이벤트에 대해서만 관심을 가지게 되고, 비즈니스 로직의 실패에 따른 보상로직도 Orderservice에서 처리해주기때문에 무결하게 개발을 할 수 있습니다.

 

Saga pattern의 정리

사가는 로컬 트랜잭션의 흐름입니다. 각각의 서비스에서는 로컬 트랜잭션을 실행하고, 트랜잭션이 완료되었다는 결과를 이벤트 스트림에 던져두고요. 그리고 이 로컬 트랜잭션이 실패하는 경우에 따른 보상로직을 구현할 수 있도록 구현을 해줘야한다는 것이 사가패턴의 핵심입니다.

더 간단하게 정리해보면, 서비스간의 소통중에서 실패가 발생했을때에 대한 보상로직을 구현해야한다이고, 이를 신뢰성 있게 구현하기 위해서 메시지를 사용하자는 것입니다.

배운  점

Saga 패턴은 이커머스나 구독, 결제등이 분리되어있는 서비스의 무결성을 지키기 위해 필요한 패턴으로 보입니다. 핵심적인 부분은 분산된 트랜잭션을 보장하기 위해서 어떻게 구현을 해야할까하는 부분인 것 같습니다.

사실 이 Saga만 놓고 봤을때에는 아직도 문제가 되어보이는 부분들이 있어보입니다. Saga만 사용한다면 로컬 트랜잭션을 마치고, reply 채널에 메시지를 던진 이후에 Customer 서비스가 죽거나, CommandChannel에 offset을 커밋하지 못하게 되는 경우에 메시지를 여러번 처리하게 될 수 있습니다. 이러한 부분에 대해서는 메시지를 컨슈밍하는 쪽에서 체크를 해줘야하는데, 메시지 아이디나, 내용을 통해서 중복성을 처리해줘야만 합니다.

그리고 또 OrderService에서 이벤트를 발행할때에도 트랜잭션 스코프 안에서 이벤트를 발행할텐데요. 이벤트가 발행된 이후 트랜잭션에 실패할 경우등 문제가 될 수 있는 케이스(디비 제약사항, 네트워크 이슈등)이 많기 때문에 이런 부분들을 보완해줄 수 있는 방법을 찾아야합니다.