본문 바로가기

끄적끄적

[끄적끄적] 44만 SAGA 를 처리하며 얻은 인사이트

완료된 SAGA의 수

 

안녕하세요, 제가 하고 있는 사이드 프로젝트 깃 애니몰즈 에서는 유저간의 거래, 쿠폰 사용, 포인트 지급 등등 많은 영역에 걸쳐서 SAGA를 사용하고 있는데요. SAGA를 처음 사용할때는 여러가지 문제가 발생했었지만, 최근에는 꽤나 안정화 된것 같아서 SAGA 를 사용하면서 얻은 인사이트를 정리하고자 합니다.

 

사용하는 SAGA 프레임워크는 아래와 같습니다. 

 

GitHub - devxb/Netx: Saga framework / Supports redis stream and blocking, reactive.

Saga framework / Supports redis stream and blocking, reactive. - devxb/Netx

github.com

 


사이드에서 SAGA를 하는 이유

SAGA를 사이드 프로젝트에 도입하기로 결심한 이유는 운영에 신경을 덜 쓰기 위해서 인데요. (사이드에 시간을 덜 쓸수록 본업에 집중할 수 있으니까요!)

외부 api 호출, 이기종 혹은 다른 DB 등등 데이터 변경이 항상 함께 성공해야하는 로직을 하나의 물리적인 로컬 트랜잭션으로 묶을 수 없거나 묶는게 비효율적인 경우, 로컬 트랜잭션과 다른 로직을 분리하게 됩니다.

 

이때, 트랜잭션 중간에 로직이 실패하면 데이터의 상태가 일부만 성공한 상태로 남아있게 됩니다.

이렇게 되면, 재시도를 통해 해소하기 힘들어지며, 어디서 문제가 발생할지 예측이 힘들어 집니다. 또한, 매 상황마다, 개발자가 DB에 접근해서 데이터의 정합성을 맞춰줘야 하는 귀찮음이 발생합니다.

 

깃 애니몰즈에서는 이러한 문제를 해결하기 위해서 분산 트랜잭션의 로직이 중간에 실패하면 자동으로 롤백되게 되는 프로세스를 만들어 두었어요.

 

결과로 처음에는 안정화에 시간이 조금 걸렸지만, 현재는 운영에 신경을 덜 쓰며 사이드 프로젝트를 진행할 수 있게 되었습니다.


적절한 기술은?

제가 생각하는, 이벤트 기반의 SAGA를 하기 위해서 필수적으로 지원되어야 하는 기능은 아래와 같습니다.

 

1. 한번 발행된 이벤트는 컨슈머가 메시지를 처리할때까지 유실되면 안된다.

2. 발행된 이벤트를 컨슈머가 그룹별로 읽을 수 있어야 한다.

 

이때, 제가 찾은 기술은 카프카와 레디스 스트림 인데요, 이중에서 저는 레디스 스트림을 선택했습니다. 레디스 스트림은 카프카에 비해서 기본 속도가 빠르며 (10배), ACK와 컨슈머 그룹 기능을 지원하기 때문에, SAGA를 구현하기 충분할 뿐더러, 속도가 중요한 트랜잭션에서도 더 사용하기 좋을것이라 판단했기 때문입니다.

 

물론, 레디스 스트림은 카프카 처럼 하나의 토픽을 분산시켜서 처리할 수 없다는 단점과 인메모리 기반이기 때문에, 데이터가 날라갈 수 있다는 단점이 있지만, 사이드라서 토픽을 분산시킬 필요가 없을것이라 생각한것도 레디스 스트림을 선택한 이유중 하나입니다.


오케스트레이션 vs 코레오그라피와 더 선호하는것

깃 애니몰즈에서는 오케스트레이션과 코레오그라피 방식을 같이 사용하고 있는데요.

처음에 두 방식을 선택하는 기준은 트랜잭션이 복잡하면 오케스트레이션 아니라면 코레오그라피 였습니다.

 

왼쪽 : 포인트 지급 코레오그라피의 일부분 / 오른쪽 : 경매장에서 상품 삭제 오케스트레이션

 

구현 초기에는 코레오그라피가 구현이 빠르고 간단해서 더 선호되었었는데요. 나중에 운영을 하다보니 코레오그라피같은 경우
다음 로직이 어디에 존재하는지 알기 힘들며, 하나의 이벤트로 구현에 따라 여러개의 핸들러가 트리거될 수 있다는 점 때문에 로직을 작성한 저도 플로우를 따라가기가 힘들더 라구요. (유지보수하기 힘들어지는거죠)

반면 오케스트레이션 같은 경우는 중앙에서 모든 로직을 관리하기 때문에 유지보수하기 편했습니다. 물론 코레오그라피에 비해서 오케스트레이션이 구현할게 늘어나긴 하지만.. 대부분의 상황에서는 추후 유지보수성을 올리는게 더 이득이 되는것 같았어요.

 

그래서 현재는 새로운 로직을 (정말 정말) 간단한 로직이며 추후 로직이 추가되지 않을것 같다면 코레오그라피로 구현하고, 대부분의 상황에서는 오케스트레이션으로 구현하고 있습니다.


SAGA 에서 발생할 수 있는 격리성 문제와 해결

SAGA는 Isolation을 지원해주지 않는다는 말을 들어보셨을거 같은데요.

그 이유는 SAGA중간에 일어나는 다수의 로컬트랜잭션이 따로 DB에 반영되기 때문입니다.

 

예를들어, 아래와 같은 트랜잭션 순서가 있을때 (동그라미 하나가 로컬트랜잭션 하나 입니다.)

유저 A, B, C 가 동시에 SAGA를 수행했다고 가정해보겠습니다.

펫 거래 SAGA

 

이때, 펫의 수량은 1이라 한명에게만 판매될 수 있으므로, 2명의 유저는 "판매자 포인트 지급" 까지 진행하다가, 이미 앞서 진행된 SAGA에 의해서 펫 상태가 BUY로 변경되었기 때문에 아래 플로우를 타고 롤백되게 됩니다.

 

펫 거래 롤백 SAGA


비효율적이죠?

그렇다면 아래와 같이 SAGA를 변경해보면 어떨까요? 

 

변경된 SAGA

 

이미 다른 SAGA에 의해서 SAGA가 시작하자마자 상점에 있는 펫 상태가 BUY로 변경되었기 때문에 A, B, C 유저중 두명은 SAGA를 시작하자마자 튕겨져 나가고, 롤백이라는 비효율적인 프로세스를 태울 필요가 없어지게 됩니다.

 

하지만, SAGA를 처음으로 진행한 유저가 사실은 상점에서 펫을 살 포인트가 충분한 유저가 아니였고, 나머지 두명의 유저는 상점에서 펫을 살 수 있는 유저라면 어떻게 될까요?

 

이번에는 서비스에 비효율적인 문제를 가져온 꼴이 되버립니다.

이미, 첫번째 SAGA에 의해서 상점의 펫 상태는 BUY로 변해버렸고, 롤백될때까지 경매장 화면에는 펫이 보이지 않을것이기 때문입니다.

또한, 깃 애니몰즈에서는 거래가 완료되었을때 누가 구매했는지등의 데이터를 영수증 느낌으로 DB에 적재하고 있는데요. 첫번째 프로세스에서 펫을 이미 BUY로 변경해줬기 때문에 SAGA 순서가 늘어나는등의 문제가 생기게 됩니다.

 

그래서 깃 애니몰즈는 아래와 같이 WAIT_BUY와 WAIT_SOLD_OUT 상태를 둬서, SAGA를 진행하기 전에 상점에 있는 펫 상태를 변경해주도록 수정해 주었어요.

 

최종적으로 완성된 펫 구매 프로세스

 

 

이것과 관련된 내용은 여기 서 더 자세히 다루고 있으니 관심 있으신분은 한번 보셔도 좋을거 같습니다!

 

[끄적끄적] 주문 Saga Isolation 부족 해결하기

Saga는 트랜잭션의 ACID특징중 Isolation을 보장하지 못하는것으로 알려져 있습니다.실제로, Isolation이 부족하기 때문에 여러 문제가 발생할 수 있는데요.이번 글 에서는 Isolation이 보장되지 않으면

dlwnsdud205.tistory.com

 


마치며

이것외 에도 redis-stream을 이용해서 어떻게 SAGA를 구현하는지, SAGA인터페이스와 Listen to yourself pattern등 하지 못한 이야기가 많은데요. 

이것들은 다음 글 에서 적어보도록 하겠습니다.

 

제가 사용하는 SAGA프레임워크는 아래와 같습니다. 

https://github.com/devxb/Netx

 

GitHub - devxb/Netx: Saga framework / Supports redis stream and blocking, reactive.

Saga framework / Supports redis stream and blocking, reactive. - devxb/Netx

github.com