Search

MSA라는 시류에 편승하기(1)

게시일
2023. 2. 26.
생성 일시
2023/02/26 17:27

MSA에 대한 개인적인 생각

모노리스 → MSA로의 전환은 비교가 안될 정도의 복잡도와 비용 증가로 이어진다.
대부분의 비지니스는 MSA가 필요 없다.
내가 근무했던 곳에선 아직 MSA를 경험해보지 않았다. 이후에도 할 일이 있을지 확신은 없다.
회의적인 이야기를 많이 했지만 그럼에도 MSA라는 시류에 편승해보려고 한다. 그 이유는,
어떤 비지니스에 MSA가 적합한지 여부와 개발자가 그것을 구축할 수 있는 역량이 있느냐는 다른 문제이다. 만약 정말 필요한 시점에 전혀 학습이 안되어 있다면?
원래 개발은 필요할 때 배우면서 하는 거지만 MSA는 단기간에 해내기엔 너무나도 방대하다.
MSA를 익히면서 백엔드에 보편적으로 사용되는 컴포넌트들과 여러 패턴을 익힐 수 있다. 본격적인 MSA가 아닌 멀티-서비스 구조나 모노리스에서도 여전히 유효한 개념들이 많다.
그러니 공부해서 손해 볼 건 없다. 가장 좋은 방법은 실무 환경에서 경험하는 거겠지만 MSA가 나를 찾아오지 않으니 내가 찾아갈 수 밖에 없다. 오버 엔지니어링이 되겠지만 사이드 프로젝트로 블로깅 앱 같은 걸 만들면서 여기에 MSA를 적용해보려고 한다. 어느정도 선행적인 학습과 리서치를 마쳤다.

작업 범위

제대로 된 MSA를 구축하려면 배워야할 것도 많고 작업량도 상당하다. 모든 걸 다 준비하고 시작하려면 언제 끝을 볼지 알 수 없다. 하고싶은 건 많지만 일단은 우선순위에 따라 작업 범위를 좁혔다.

높은 우선순위

MSA 환경에서 백엔드 개발자로서 내가 직접적으로 얻으려고 하는 경험들에 높은 우선순위를 할당했다. 앞으로 2~3주 내에 나올 아웃풋에는 아래의 내용들을 모두 포함시키고 싶다.
분산된 데이터 다루기
MSA 환경에서 실제 코드를 만지는 사람이 접하는 가장 흔한/골치 아픈 문제가 될 것 같다.
각 서비스가 별도의 물리적 저장소를 갖고, 이는 RDB 외에 다양한 DB의 조합이 될 수도 있다. 이런 환경에선 RDB가 보장하는 트랜잭션의 편리함을 누릴 수 없다.
데이터의 일관성을 보장하는 것은 온전히 개발자의 몫이 된다.
TCC, 아웃박스, 사가 등등 대처하는 패턴도 다양하다.
다양한 서비스간 통신 방식 적용
MSA에는 여러 서비스가 존재하고 이런 서비스들 간의 통신도 빈번하게 일어난다.
필요에 따라 동기적, 비동기적 방법을 적용하며, 이를 구현하는 도구와 패턴도 다양하다.
장애 상황에 대비하기 위한 재시도, 회로차단기 메커니즘도 마련되어야 한다.
높은 성능 달성
MSA를 필요로 하는 비지니스는 아마 대부분 높은 성능과 스루풋이 필요한 곳일 것이다.
단순히 ‘돌아가는’ 서비스 수준을 넘어서 실제로 많은 트래픽을 받는다고 가정하고 여러 최적화 패턴을 적용해보려고 한다.
쿠버네티스와 IaC 기반 인프라 구성
개발을 했다면 배포도 해야할 것이다.
MSA와 K8s는 거의 한몸처럼 붙어다니니 안 쓸 이유가 없다.
매니지드 서비스인 AWS EKS에 배포를 고려 중이고, 테라폼을 통한 IaC도 적용하려고 한다.

낮은 우선순위

이 중에 중요하지 않은 것은 없다. 다만, 보다 높은 우선순위의 작업들이 선행되어야 견적이 나오는 것들도 있고, 도구나 기술이 복잡해서 한정된 시간 내에 소화하기 힘든 것들도 있다.
CQRS, 이벤트 소싱
DDD 원칙을 strict 하게 가져가면 read model과 CQRS 도입이 거의 반강제된다. 그래서 DDD를 조금 느슨하게 적용하기로 했다.
CQRS와 이벤트 소싱을 함께 가져가는 경우도 꽤 있는 거 같다. 원자성을 보장하는 장점에는 공감하지만 복잡도가 어마어마 하게 커질 것 같다.
이벤트 소싱이 적합한 도메인이 따로 있다고 생각한다.
CI/CD
모노리스 기반 프로젝트에선 이미 여러번 CI/CD를 구축해본 경험이 있지만 MSA에선 고민할 부분이 크게 늘어난다. 모노레포로 할지/말지, 어떻게 전체 서비스에 대한 일괄적인 빌드-테스트-배포를 최소화하고 필요한 서비스만 처리할지 등 긴 고민이 필요한 부분이 많아 보인다.
MSA를 쓰는 조직에선 분명 DevOps 팀이 따로 있을 거고 이걸 내가 직접 구축하게 되진 않을거 같다. 구성 난이도도 꽤 높아 보이며, 조직에 따라 구성 방식도 천차만별일 것이다.
일단은 익숙한 도구들을 사용해서 전체 서비스를 자동 빌드, 배포 하는 방식만 적용해보는 데 그칠 것 같다.
관측성
MSA 환경에서 리퀘스트는 수많은 서비스를 거쳐 응답까지 도달할 수 있기 때문에 이 과정을 추적할 수 있는 장치가 반드시 마련되어야 한다.
또한 수많은 서버를 모니터링하고 메트릭을 수집할 수 있어야 한다.
단만 이것을 구성하기가 간단하지 않아보인다. 배워야할 도구도 산더미다. (EFK stack, Grafana, Prometheus, …)
서비스 메시 또한 관측성을 위한 여러 컴포넌트를 붙이는 데 필수적으로 사용되는 것 같다. 처음엔 서비스의 수가 한정적이고 구조가 복잡하지 않기에 일단은 배제하기로 했다.
어차피 관리형 서비스(EKS 등)에 배포할텐데 여기서 제공하는 로깅, 모니터링 도구를 최대한 활용하면서 날로 먹을 수 있는 방법은 없을지 머리를 굴리고 있다.
테스트
테스트를 완전히 배제하는 것은 아니다.
다만 모든 마이크로서비스에 대한 통합 테스트, 특히 이걸 CI 과정에서 어떻게 실행시켜야 하는지에 대한 의문이 아직 남아있다.

기술 스택

어떤 것은 MSA에서 발생하는 문제를 해결하기 위한 도구로써 채택했고, 어떤 것은 그냥 이전부터 써보고 싶었던거라 골랐다(사이드 프로젝트의 좋은 점이다).
MSA의 장점 중 하나는 polyglot 하다는 것이다. 몇몇 MSA 샘플들을 보면 하나의 언어/프레임워크(e.g. Java/Spring Cloud)에 종속적인 구현도 볼 수 있다. 이렇게 했을 때 가져갈 수 있는 편의성도 있겠으나, 실제 MSA를 운영에서 쓰는 환경이라면 여러 언어가 섞인 경우가 더 많을 것 같다.
그래서 익숙한 Node.js + 써보고 싶었던 Kotlin 조합을 가져가기로 했다. 기술 스택 선정도 이에 따라 나뉜다.

Kotlin

Kotlin
TypeScript 같은 그래도 나름 모던한 언어를 수년간 쓰다가 Java를 다시 보니 숨이 턱 막혔다.
같은 JVM인 이상 Java에서 자유로울 순 없겠지만 Kotlin으로 좀 더 세련된 코드를 작성해보려 한다.
Spring Boot
이러니 저러니 해도 프레임워크로서 완성도는 매우 높다.
MSA 레퍼런스가 가장 많다. Spring Cloud 생태계가 잘 갖춰져 있어, 여러 도구들을 편하게 쓸 수 있을 걸로 기대한다.
Non-blocking
전통적인 Spring MVC는 블로킹으로 동작하며 성능을 뽑아내는 데 한계가 있다.
WebFlux: 처음 스프링을 배울 때(2018년 쯤?) 보다 레퍼런스도 많고 사용하는 곳도 많아진 거 같다.
블로킹을 최소화하고 제대로 된 논 블로킹 서버를 만드려면 DB 혹은 Redis, Kafka 등을 다룰 때도 리액티브 해야한다. 지원하는 라이브러리를 잘 찾아서 쓰자.
Data persistence
WebFlux를 쓸 것이니 DB를 다루는 부분도 리액티브 해야한다.
일반적인 RDB에 대한 리액티브는 Spring Boot 자체에서 지원하는 부분이 아직 없다.
대안으로 제시되는 R2DBC는 완전한 ORM은 아닌 것 같다.
아직 mature 하다고 보기에 조심스러운 라이브러리지만, 풍부한 기능을 제공하는 ORM과 Kotlin의 DSL, Coroutine을 온전히 누리려면 다른 대안이 없는 거 같다.
Spring Data Reactive MongoDB
다행히 MongoDB는 Spring Boot 자체에서 리액티브를 지원한다.
도큐먼트 형태의 DB가 적절하다고 판단되는 서비스에 그냥 편하게 쓰면 될 것 같다.
GraphQL
GraphQL을 이전에 현업에서 써보긴 했으나 모노리스 환경이었다. MSA에서도 써보면 재미있지 않을까.
넷플릭스가 만든 DGS를 쓰기로 했다. 이전에 Node.js에서 다뤄본 TypeGraphQL과 비슷해 보인다.
federation은 적용하지 않기로 결정했다. 몇가지 문제가 예상 되는데,
각 서비스가 서브그래프를 구현해야 한다. 서비스가 GraphQL에 종속된다.
federation의 마법은 query에만 적용되지, mutation은 기존과 다를 게 없다.
이를 해결 하려면, 별도의 서비스 레이어를 두고(composition, aggregator) 여기에만 subgraph를 노출하는 GraphQL에 종속적인 구현을 맡길 수도 있다. 그러나 복잡도가 필요이상으로 커질 것 같고, 필요하다면 그때 도입해도 전혀 늦지 않을 것으로 판단된다.

Node.js

NestJS
직전에 실무에서 쓰던 게 이거라 익숙하다.
MSA 지원 기능들이 궁금했다. 이참에 써보면 되겠다.
ORM
실무 외에 사이드 프로젝트까지 포함하면 Node.js에서 돌아가는 메이저한 ORM은 Prisma 빼곤 다 써봤다. (Sequelize, TypeORM, MikroORM)
슬픈 일이지만 Node.js에서 멀쩡하고 쓸만한 ORM을 고르는 것은 참 어려운 일이다. 이번에도 적당히 골라서 쓰려고 한다. 익숙한 걸 쓸 수도 있고, 안써본 Prisma를 쓸 수도 있고.

일반

PostgreSQL
전통적인 RDB도 스택에 포함시키려 한다.
트랜잭션의 이점을 누리기엔 MSA 환경에선 한계가 있긴 할 듯.
MongoDB
수평 확장에 유리하다. 특히 Atlas에서 운영했을 때.
데이터가 비정형적인 경우 좋다.
Redis
가능하면 단순한 캐싱 외에 다양한 자료구조를 경험해보면 좋겠다.
gRPC
서비스간 동기적 통신에 적용하려고 한다.
REST에 비해 성능과 개발 경험에 이점이 있을 것으로 기대한다.
Kafka
서비스간 비동기적 통신에 적용하려고 한다.
Kafka와 RabbitMQ 사이에서 고민했으나 MSA 환경에선 Kafka가 여러모로 쓰임새가 많은 거 같다.
Kubernetes with AWS EKS
아마 많은 상용 환경은 K8s를 매니지드 서비스 위에서 운영할 것 같다.
GKE도 많이 사용되고 GCP도 이전에 써봤지만, 큰 이유는 없고 직장에서 가장 최근에 만지던 것이 AWS라 EKS를 쓰기로 결정했다.
이전에 ECS로 운영환경을 구축 했었는데 ECS → EKS로 옮겨가며 차이를 경험해보는 것도 꽤 재밌을 거 같다.
계획은 이렇다. 일단은 위에 높은 우선순위로 할당한 작업들에 집중하고, 이후에도 MSA를 지탱하는 여러 기술을 시험하는 플레이 그라운드로써 프로젝트를 계속 발전시킬 계획이다.
다음 포스트에선 앱 설계나 실제 작업의 중간결과물을 들고 오겠다.