[발표자료] 온라인 주문 서비스를 서버리스 아키텍쳐로 구축하기 – AWS Community Day Online

AWS Community Day Online 2020 「온라인 주문 서비스를 서버리스 아키텍쳐로 구축하기」세션의 총정리 및 보충설명이 포함된 포스팅입니다.
2020.10.17

안녕하세요!ㅎㅎ Classmethod 컨설팅부 소속 김태우입니다.

도쿄는 오늘부터 갑자기 기온이 뚝 떨어져서 반팔 반바지채로 밖에 나갔다가, 추워서 다시 얼른 집으로 들어가버렸습니다ㅎㅎ 환절기인데 다들 건강 유의하시기 바랍니다! :)

이번 포스팅은 COVID-19 의 장기화로 인해, 처음으로 온라인으로 개최된 AWS 커뮤니티데이 행사인 AWS Community Day Online 에서 「온라인 주문 서비스를 서버리스 아키텍쳐로 구축하기」라는 제목으로 발표했던 세션의 발표자료를 공유드림과 동시에, 발표영상에서 시간적인 제약으로 인해 미처 다 이야기하지 못했던 부분들에 대한 보다 상세한 설명을 공유하기 위한 목적으로 작성되었습니다. 행사에 참여해주시고 제 세션을 시청해주셨던 분들께 진심으로 감사드립니다.

발표 영상

발표 슬라이드

발표에서 설명한 데모 코드

(Github) twkiiim/aws-community-day-2020-demo

Distributed Sagas

Youtube 강의 영상

발표 때 못 다한 이야기

당연한 이야기지만, 실서비스에 분산 트랜잭션을 적용하기 위해서는 좀 더 깊이 있는 수준까지 Distributed Sagas 를 이해하고, 트랜잭션의 각각의 태스크들이 실패할 경우에 대한 시나리오를 꼼꼼하게 분석해야합니다. 우선은 Distributed Sagas 에 대해 좀 더 자세하게 알아보고, 이를 Step Functions 로 구현할 때에 주의해야할 점들에 대해 좀 더 자세히 알아보도록 하겠습니다.

Two Phase Commit (2PC)

그전에 우선, 발표영상에서 분산트랜잭션 알고리즘으로 언급했던 Two Phase Commit (2PC) 에 대해 잠깐 다시 짚고 넘어가고자 하는데요, 2PC 는 트랜잭션에 참여하는 각각의 서비스/리소스를 홀딩시켜버립니다. 예를 들어, 아래와 같은 시나리오를 생각해볼 수 있습니다.

항공권 예약할건데, 다른 애들한테도 물어보고 와야하니까 다른일 하지말고 잠깐만 기다려~

호텔 예약할건데, 다른 애들한테도 물어보고 와야하니까 잠깐만 기다려~

렌트카 예약할건데, 다른 애들한테도 물어보고 와야하니까 잠깐만 기다려~

다른 애들이 괜찮대! 항공권 예약확정해줘

다른 애들이 괜찮대! 호텔 예약확정해줘

다른 애들이 괜찮대! 렌트카 예약확정해줘

네, 2PC 를 사용하면 예약확정을 하기까지 각 서비스/리소스에 대해 홀딩을 걸어버려서, 다른 요청들을 처리할 수 없게되어 버리는 상황이 발생해버립니다. 즉, 스케일링하기가 매우 어려운 구조가 되므로 대규모 트래픽을 항상 염두에 둬야하는 마이크로 서비스 아키텍쳐에서는 적절히 활용하기 어려운 알고리즘입니다.

트랜잭션이 원자적(Atomic)으로 동작하면서 동시에 서비스/리소스를 고립(Isolation)시키기 때문에 발생하는 현상인데요, 이후에 설명할 Distributed Sagas 은 비원자적(No Atomic) 트랜잭션을 보장하고, 비고립성(No Isolation)을 보장하는 알고리즘이므로 마이크로 서비스 아키텍쳐에서 사용하기에 훨씬 적합합니다.

Distributed Sagas 에 대하여

단일 데이터베이스 안에서 긴 시간동안 처리되는 트랜잭션을 처리하는 기법을 Sagas 라고 하는데요, Distributed Sagas 는 이를 MSA 환경에 맞춰 개선한 분산 트랜잭션 프로토콜입니다.

발표영상에서는 미처 설명하지 못했던 Distributed Sagas 의 중요한 특징들에 대해 이해하기 위해 Distributed Sagas 강연에서 발췌한 슬라이드와 함께 하나씩 설명해보도록 하겠습니다.

Distributed Saga 란, 하나의 비즈니스 수준에서의 액션을 의미하는 "요청"과 "보상 요청" 의 콜렉션 이라고 Caitie 는 설명하고 있습니다.

여기서 중요한건 이 요청(requests)보상 요청(compensating requests) 의 특징인데요,

요청의 경우,

  • 멱등성 (Idempotent)
  • 실패 가능 (Can Abort)

의 조건을 만족해야하고, 보상요청의 경우,

  • 멱등성 (Idempotent)
  • 순서교환가능 (Commutative)
  • 실패 불가능 (Can Not Abort)

의 조건을 만족해야합니다. 각각이 의미하는 바가 무엇인지 좀 더 자세히 알아보겠습니다.

우선 요청의 경우에 "멱등성" 이 의미하는 바는 "같은 입력값이 주어졌을때 몇번을 실행시켜도 결과값의 상태는 같아야한다" 는 말입니다. 예를 들어, 호텔 예약을 하기위해, "Tom 이 1234호를 2020/10/17 에 1박합니다" 라는 요청을 1번을 처리하던지 100번을 처리하던지 결과적인 상태는 같아야한다는 말이죠. 네, 당연한 조건처럼 느껴지네요!

근데, 이런 성질이 왜 필요할까요? 만약 요청을 했는데 요청이 타임아웃 되어버렸다면 요청을 보낸 쪽에서는 태스크는 성공했는데 응답만 못받은건지, 혹은 태스크가 실패하고 응답도 못받은건지 알 수 있는 방법이 없습니다. 따라서 다시 한번 요청을 해야하는 경우가 종종 발생할텐데요, 이럴 때에 이 멱등성을 보장하게 된다면 안심하고 여러번 요청을 시도할 수 있게됩니다.

다음으로, 요청의 "실패가능" 이 의미하는 바가 무엇일까요? "Tom 이 1234호를 2020/10/17 에 1박합니다" 라는 요청을 받았을때, 이미 해당일자 해당 객실이 예약되어 있을 경우 받은 요청을 실패처리 시킬 수 있다는 것이죠! 이것도 너무 당연한가요?ㅎㅎ

요청의 경우에는 이 두가지 성질을 반드시 만족하도록 코드를 작성해야합니다. 이번에는 보상 요청에 대해 살펴보겠습니다.

보상요청에 대해서도 "멱등성" 은 성립해야합니다. "Tom 이 2020/10/17 에 1박 예약한 1234호를 예약취소처리합니다" 라는 요청은 몇번을 처리해도 같은 결과가 되어야겠죠.

두번째로는 "순서교환가능" 인데요, 이게 무슨말이냐하면, 호텔을 예약한 다음에 호텔 취소를 하든, 호텔 취소를 한다음에 호텔 예약을 하든, 결과값은 결국 예약했다가 취소한 꼴이 된다는 걸 보장해야한다는 의미입니다. 즉, 호텔예약이라는 요청을 처리하다가 어떤 이유로 인해 처리가 늦어지게 되었는데, 그 사이에 다른 쪽 요청이 실패하여 전체적으로 보상요청을 실행시키는 과정에서 예약 취소라는 보상 요청을 먼저 처리하게 되었다면, 이후에 다시 처리가 늦어졌던 호텔 예약 요청을 처리하더라도 예약이 되면 안되는 것을 보장해야한다는 성질입니다. 말이 어렵나요?ㅎㅎ 요약하면, 트랜잭션을 롤백시키는 과정에서 보상 요청을 요청보다 먼저 처리하게 되는 경우가 있을텐데, 이 때 "요청 → 보상 요청" 순서로 실행하던지, "보상 요청 → 요청" 순서로 실행하던지 결과는 같아야한다는 말입니다.

마이크로 서비스 아키텍쳐에서는 비동기적으로 태스크들이 처리되는 것을 전제로 한 경우가 일반적이므로, 이러한 비동기적인 태스크들이 처리되는 순서에 주의할 필요가 있습니다. Distributed Sagas 의 보상 요청은, 실행 순서와 관계없이 서로 짝이되는 요청과 보상요청이 둘 다 실행되면, 의미론적으로는 결과값이 처음과 같음을 보장해야한다는 것입니다. 여기서 "의미론적으로는" 이라는 표현을 썼는데요, 여기에 대해서는 잠시 후에 다시 설명드리도록 하겠습니다.

마지막으로 "실패불가능" 입니다. 어떤 일이 있어도 반드시 보상 요청은 성공해야합니다. 즉, 보상 요청이 성공할 때까지 실행시키는 메커니즘이 필요하다는 것이죠.

다 직관적으로 이해가 되시나요? :)

그렇다면 잠깐 언급한 "의미론적으로는" 결과값이 처음과 같아야한다고 했던 표현은 무엇을 의미할까요?

보상 요청을 처리한 후에는 요청을 처리하기 전과 같은 상태로 돌아가야한다는 당연한 전제가 있는데, 이 때 완벽하게 이전의 상태로 되돌린다기보다는 의미론적으로(semantically) 같은 상태로 되돌리는 것을 의미합니다. 이게 무슨말일까요?

결제(요청)한 후에 환불(보상 요청)을 처리했다고 하면, 의미론적으로는 잔고의 변화가 없기때문에 결제하기전과 같은 상태이지만, 돈이 빠져나갔다가 다시 들어온 거래 기록이 남아있게 됩니다. 즉, 의미론적으로는 같은 상태이지만 실제로는 데이터가 추가된 상태이므로 완전히 같다고는 할 수 없는거죠.

또 다른 예로 고객에게 이메일을 발송(요청)했는데, 이를 번복하는 메일을 한번 더 발송(보상 요청)하는 것도 의미론적으로는 고객에게 정정 메일을 보냈기때문에 처음과 같은 상태로 되돌린다는 정보전달을 했다고 볼 수 있습니다.

이렇듯, 보상 요청은 의미론적으로 요청을 처리하기 전과 같은 상태로 되돌린다는 관점에서 바라보셔야합니다.

자, 여기까지 요청과 보상요청에 대한 성질에 대해 알아보았는데요, 제가 공개한 데모코드는 어디까지나 AWS 에서의 서버리스 아키텍쳐를 보여드리기 위한 용도와 샘플 코드를 제공하기위한 목적이 강하므로, 위의 요청과 보상요청의 성질을 전부 만족시킨다고는 할 수 없습니다. 따라서, 실제로 Distributed Sagas 를 구현하려고 하실때에는 반드시 이러한 성질들에 대한 이해를 하시고, 꼼꼼하게 케이스들을 분석하셔야 올바른 코드를 작성하실 수가 있게됩니다.

데모코드에서 사용된 AWS CDK 에 대한 포인트 설명

발표에서 소개한 블로그 모음

온라인 주문 서비스를 서버리스 아키텍쳐로 구축하기 #분산트랜잭션 #실시간데이터스트리밍

AWSKRUG 구로디지털 #gudi 소모임 – 37회차 발표자료입니다.

AWS Amplify 노하우 시리즈 포스팅을 공개합니다! #aws #amplify

AWS CDK (Cloud Development Kit) 를 소개합니다 #aws #awscdk #aws입문

AWS 재입문 블로그 – AppSync 편 (한국어)

마치며

사실 세션을 준비하는 기간동안, 업무에서도 도전적인 과제들이 많이 겹치는 바람에 체력적으로 많이 부담이 되었습니다..^^; 어떻게든 겨우겨우 영상 편집까지 끝냈는데 내용적으로 불충분하다고 느껴지는 부분들이나, 이래저래 실수했던 부분들도 보여서 마음이 좀 아팠지만..(ㅋㅋ) 본 포스팅을 통해 그런 부분들이 보완되었으면 하는 마음이 있습니다!

개인적으로 서버리스 개발을 할 때에는 거의 Serverless Framework 를 주로 선호하다가 최근이 되어서야 CDK 를 본격적으로 사용하기 시작했는데요, 서버리스 개발 및 배포에 가장 어울리는 툴은 AWS CDK 가 아닐까 싶을정도로 궁합이 너무 잘 맞는 것을 피부로도 느끼고 있습니다. AWS CDK 에 대해서도 이야기하고싶은 것들이 아주 많이 남아있어서, 아마 당분간 AWS CDK 포스팅이 많이 올라오지 않을까 싶습니다! (기대해주세요!ㅎㅎ)

또, 발표영상에서도 이야기했듯 AWS Amplify 와 함께 AWS AppSync 조합의 활용도 더욱 늘어날 것 같습니다. 가까운 시일내에 AppSync 와 CDK 를 활용하여 더욱 효과적인 개발을 가능하도록 하는 개발 노하우도 공개하고자하니 이것도 많이 기대해주세요 :)

이상, 컨설팅부의 김태우였습니다 :)