결제서버 분리하기 - 1: Monolithic to MSA

1
post-thumbnail

Monolithic to MicroService의 결정

서비스가 커가면서 고민하게 되는 여러가지 옵션 중 하나가 바로 MSA입니다. 기존 모놀리식 아키텍쳐(이하 모놀리식)에서는 SPOF(Single Point Of Failure) 단일 장애점에 대한 위험을 늘 감수해야하고, scale-out을 하더라도 각각의 서비스 리소스와 상관 없이 모든 서비스가 scale-out 되어야 하기 때문에 시스템 리소스 비효율이 발생하기도 합니다. 그외에도 각 서비스가 강결합되어 있어, 전체 시스템 구조 파악이 어렵고, 수정 시 장애 영향 파악이 어려울 수 있다는 등의 문제가 있습니다.

현재 우리의 서비스는 모놀리식로 서비스가 운영되고 있습니다. 모놀리식의 장점도 명확합니다. 대부분의 개발자가 모놀리식을 경험해보았기 때문에 MSA에 비해 상대적인 러닝 커브도 낮고 프로젝트를 쉽게 시작할 수 있습니다. 또한 모든 코드가 단일 코드베이스에 존재하기 때문에 배포, 디버깅, 테스트 모니터링이 용이합니다.

물론 앞으로도 현재의 모놀리식 환경에서도 충분히 서비스를 서빙할 수 있고 지금의 방식대로 새로운 기능을 추가하고 서비스를 유지보수하며 운영 할 수 있습니다. 오히려 작은 팀의 규모에서 MSA는 관리 포인트만 추가되는 것이고, 운영 효율성을 낮출 수도 있기 때문에 한편으로는 오버엔지니어링이 될 수 있습니다. 비즈니스 사이드에서도 “왜 잘 되는 기능을 옮기는 것이죠 ?”라는 질문을 받을 수도 있습니다. 그럼에도 MSA를 결정하게 된 이유는 크게 두 가지 입니다.

💡 앞으로 서비스가 더 성장하고, 팀이 커가는 과정에서 현재의 모놀리식 구조를 유지하는 것이 우리팀의 생산성이나 서비스의 운영의 위험성을 높이는 것이라고 판단했습니다.

💡 기술적인 이유 뿐만 아니라, 우리 팀이 성장하고, 발전하기 위해서는 지난 몇 년 간 유지했던 모놀리식 구조를 벗어나 작게나마 MSA 환경에서 서비스를 서빙하는 것이 우리에게도 큰 배움이 있을 것이라는 확신이 있었습니다. 인재 영입의 관점에서도 MSA 환경에서 서비스를 서빙하는 것은 지원자들에게 큰 매력이라고 생각했습니다.

이러한 이유로 MSA의 첫삽을 뜨게 되었습니다.

MSA.. 고민 시작 .. !

고민 1. 무엇부터 옮겨야하지 ?

MSA를 결정하게 된 후 가장 큰 고민은 “무엇을 분리 해야하는가?” 에 대한 고민입니다. 모놀리식 구조로 이미 서비스를 런칭하고 있었기에 대부분에 서비스가 강결합되어 있었고, 하나의 데이터베이스를 기반으로 설계 되었기 때문에 쉽게 분리하기가 어려웠습니다.

그렇기 때문에 사실상 “무엇을 분리 해야하는가?”에 대한 고민보다는 “도대체 무엇을 분리 할 수 있을까?” 에 대한 고민을 해야하는 상황이었습니다. 특정 도메인을 분리하려고 하면 DB는 ? 연결된 비즈니스 로직은 ? 등 제약 사항이 많았고, 예상할 수 없는 사이드 이펙트에 대한 걱정도 컸습니다. 최종 결정은 “일단은 어떠한 서비스라도 분리 하자” 였습니다. 일단 분리를 하고 운영을 하다 보면 다음 서비스.. 그 다음 서비스..를 분리하는데는 이전보다 쉬울 것이라고 판단했고, 이 과정에서 진정으로 MSA가 우리팀의 핏에 맞는 환경인지도 판단 할 수 있을 것이라 생각했습니다.

그렇게 고민하다가 발견한 것이 바로 결제 서비스입니다. 결제 도메인의 가장 큰 특징은 바로 PG사와의 외부 통신이 일어난다는 것입니다. 그렇기에 여러 서비스 도메인들과의 결합도도 상대적으로 낮았고, DB 분리 측면에서도 결제 서버 앞단에서 메인 서버가 모든 결제의 유효성 검증을 한 후, 결제 서버로 PG사와의 거래 요청만 한고 결제 서버는 이 결과를 메인 서버로 전달해주는 역할만 한다면 기존 DB를 참조하지 않고도 결제 도메인을 분리를 할 수 있다고 생각했습니다.

고민 2. 어떻게 옮길 수 있을까 ?

팀에서는 Python-Django를 메인 기술스택으로 서비스를 운영하고 있습니다. Django는 다양한 내장 기능을 제공하지만 상대적으로 무겁다보니 마이크로 서비스용으로 운영하기에는 부적절하다고 생각했습니다. 또한 MSA 자체가 하나인 특정 언어, 프레임워크에 종속적이지 않은 장점을 경험하기 위해 기존 Python-Django을 벗어나보기로 결정했습니다. 최종 결정한 언어는 Golang입니다. Golang으로 결정한 이유는 크게 두 가지입니다.

  • 팀 내에서 Golang으로 별도의 서비스를 운영하고 있어 팀 내 언어 커버리지를 높이기 위해 결제 분리 프로젝트의 메인 언어를 Golang으로 결정했습니다.
  • 결제 서비스에서 가장 중요한 것은 운영 안전성이라고 생각했습니다. 언어 자체의 타입 제약이 없는 파이썬보다는 Golang으로 운영 안정성을 높이고자 했습니다. 이와 함께 컴파일언어의 성능적인 부분에서도 Golang의 충분히 이점이 있을 것이라 생각했습니다.

MSA와 함께 따라오는 키워드는 EventDriven, Kafka, DistributedTransaction, RPC .. 모놀리식 환경에서는 쉽게 접하기 힘든 개념들이었습니다. 각 개념을 하나하나 고려해보았습니다.

처음에는 EventDriven 구조로 설계해보았습니다.

  1. 메인 서버와 결제서버 사이 Kafka를 붙여 메인 서버가 결제 이벤트를 발행시키면
  2. 이를 구독하는 결제 서버가 PG사와의 거래를 처리하고
  3. 처리 결과를 다시 메인 서버로 전송하여 처리하는 하는 구조로 설계했었습니다.

사실 구현은 가능하지만 결제라는 도메인의 특징상 비동기로 설계하는 것이 맞지 않다고 생각했고, 직접 적인 결제보다는 결제 이후 대사처리를 비동기로 하는 것이 더 적합하다고 생각하여 하면서 EventDriven 고려 대상에서 제외했습니다.

두 번째는 gRPC 였습니다. gRPC는 구글에서 개발한 오픈소스로 HTTP/2 기반의 Remote Procedure Call 프레임워크입니다. 공통된 인터페이스 정의(Proto 파일)를 각 언어에 맞게 컴파일하여 생성되는 Stub을 각 클라이언트와 서버가 공통으로 사용합니다.

높은 header 압축률을 보장하고 중복을 제거하여 HTTP/1 에 비해 효율적입니다. 마이크로서비스를 운영하는데 있어서 통신 비용을 생각 하지 않을 수 없습니다. 몇 ms 라도 이득을 보는것이 중요한데, 이러한 측면에서 gRPC는 괜찮은 옵션이었습니다.

또한 자체 인증이나 암호화, channel이라는 쉬운 인터페이스 등 내장 기능이 풍부하여 이후의 추가적인 기능을 붙이는데도 크게 어렵지 않게 붙일 수 있을 것이라 생각했습니다.

이러한 장점들을 고려해보았을 때 최종적으로 기존 HTTP RESTful 보다는 gRPC를 선택하여 진행하는 것이 보다 많은 이점이 있을 것 같아 gRPC를 선택하게 되었습니다.

Payment + Golang + gRPC = PayGo

PayGo. 결제 프로젝트명이자 결제 대행 서버의 이름입니다. Pay라는 서비스 성격과 Golang을 적절히 섞은 프로젝네이밍입니다. 이 PayGo의 목표는 크게 두 가지 였습니다.

  • 장애없는 결제 처리
  • 결제 레이턴시 발생시키지 않기

본질적으로 안전한 결제 서비스를 서빙해야했고, 하나의 서버에서 처리하던 결제를 별도의 서버에서 나눠 진행하면서 각 서버간의 통신 및 처리 과정에서의 레이턴시를 최대한 발생시키지 않는 것이 중요하다고 생각했습니다. 여기까지가 PayGo가 나오기까지의 고민과 의사 결정의 배경이었습니다. 이후의 편에서는 실제 클라이언트와 서버에서 어떻게 구조를 가져갔고 이과정에서 발생한 고민들을 한 번 적어보도록 하겠습니다 !

1개의 댓글

comment-user-thumbnail
2024년 4월 16일

이 글을 시작으로 고민하시는 글 차례로 읽어보려고합니다!
짤이 너무 재밌고 공감되네요 ㅋㅋㅋㅋ

답글 달기