포스트

MSA 에서 서비스를 운영해보니


MSA에서 서비스를 운영해보니: 환상과 현실 사이

개요

안녕하세요! 6개월간 MSA로 서비스를 운영하면서 느낀 점을 솔직하게 공유드리려고 합니다. ‘MSA 하지 마세요’ 라는 제목으로 글을 쓸까도 했지만, 그냥 담백하게 내용을 공유드리는 편이 나을 것 같다는 생각이 들더라고요. 🙂

이상과도 같던 MSA: 신입 개발자의 로망

신입 개발자에게 MSA 서비스란 무엇일까요? 저에게는 꼭 한번 경험해보고 싶었던 환상적인 세계였습니다. 제가 개발자로 일을 시작한 이후부터는 업계에 MSA 서비스 운영이라는 말이 마치 ‘요즘 시대의 개발자 필수 역량’ 인 것처럼 들려왔고, 저 또한 그 환상에 빠져들었습니다. “확장에 용이하고, 유지 보수에 편리한 MSA를 사용하면 어떤 복잡한 서비스도 레고 블록 쌓듯 착착 해낼 수 있겠지!” 하는 달콤한 기대감이 가득했죠.


실제로 당시에 강의를 들으면서 이런 코드를 작성했는데, 다양한 서비스가 유기적으로 묶이는 모습이 마치 교향악단의 악기들처럼 조화롭게 동작한다는 점이 매우 흥미로웠습니다. 이렇게 서비스를 구성하는게 모던 아키텍쳐지! 라고 생각하면서 언젠가 이렇게 작동하는 서비스를 만들겠다고 스스로 다짐하기도 했습니다. 그러던 차에 MSA로 서비스를 하고 있는 조직에 합류할 기회가 주어졌고, “아, 나도 드디어 MSA 세계에 입문하는구나!” 하는 부푼 마음으로 합류했습니다. 마치 해리포터가 처음 호그와트에 입학한 기분이었달까요?

현실적 장점: “와, 정말 좋잖아!”

스케일 아웃의 편리함: 탄력적인 서비스의 마법

가장 큰 장점은 역시 예상했던 대로 스케일 아웃이 매우 유연하다는 점이었습니다. 우리 서비스는 특정 기간에 갑자기 사용량이 폭증하는 특성이 있었는데요. 마치 옷 쇼핑몰이 봄이 되면 봄옷들이 잘팔리듯, 해당 시즌이 도래하면 10개의 서비스 중에 1~2 개의 사용량은 치솟는데 비해서 다른 서비스들은 ‘내 일’이 아니라는 듯이 조용한 사용량을 보이고 있었습니다.


이럴 때가 MSA 의 장점이 빛을 발하는 순간입니다! 사용량이 몰리는 특정 서비스만 간단한 설정을 통해서 스케일 아웃을 하면 됩니다. 이건 마치 식당에서 갑자기 손님이 몰릴 때 주방장만 더 투입하는 것과 같은 효율적인 방식이죠. 모놀리스였다면 무거운 식당 건물을 통째로 하나 더 지어야 했을 겁니다. 😅

비용 측면에서도 상황에 따라 클라우드 리소스를 유연하게 조절하면서 필요한 서비스에 할당할 수 있어서 이게 바로 현대의 모던 아키텍쳐이고, Infrastructures As Code 의 위엄이구나 싶었습니다.(물론 이건 MSA 라기보다는 Terraform 과 Kubernetes 의 장점일지도)

테스트의 용이성: 작은 조각들의 안정감

각 서비스별로 독립적으로 테스트하기가 정말 쉬웠습니다. 슬라이스 테스트에서는 필요한 객체만 Fixture로 준비하면 됐고, 통합 테스트도 관련 모듈만 임포트하거나 모킹할 수 있었습니다.

만약 하나의 거대한 모놀리틱이었다면? 통합 테스트가 마치 크리스마스 트리의 전구처럼 - 하나가 고장나면 모두 안 켜지는 - 불안정한 상태가 되었을 겁니다. 테스트 실행 시간도 커피 한 잔 마시고 올 정도로 오래 걸렸겠죠.

배포 과정도 ‘개발 → 스테이징 → 상용’으로 이어지는 흐름에서 각 서비스마다 독립적인 CI/CD 파이프라인이 가능해서, “내 서비스는 내가 확실하게 책임진다!”는 주인의식도 생겼습니다. 뿐만아니라 배포 후, 모니터링에 있어서도 확인해야할 포인트가 명확하기에 QA 도 정말 쉬웠습니다.

CQRS 패턴 적용의 깔끔함: 각자의 자리에서 빛나는 순간

CQRS 패턴을 사용하면 서비스의 추상화와 캡슐화가 마치 잘 정돈된 서랍장처럼 깔끔해집니다. 내부적으로 port 기반의 인터페이스를 통해 서비스별로 통신하고, query와 usecase 기반으로 각 서비스가 자신의 책임을 명확히 가지도록 구성했습니다.

이 구성 덕분에 각 서비스별 모듈화가 “이건 내 일이야”라고 확실하게 정의되었고, 서비스별로 최적화를 위한 다양한 방법을 도입하는 데도 부담이 없었습니다.

어떤 서비스는 Celery로 비동기 작업을 처리하고, 또 다른 서비스는 Redis를 애용하며, 어떤 서비스는 DynamoDB에 정착했습니다. 이처럼 서비스별로 ‘최선’의 구현을 선택하는 데 있어 다른 서비스에 영향이 없다는 점은 개발자에게 마음껏 놀아볼 수 있는 무대를 제공하는 좋은 환경이라고 생각합니다. 모든 개발자는 새로운 기능과 자유로운 환경에서 일하는 것을 좋아하잖아요.

현실적 단점: “아… 이런 건 몰랐는데…”

기술의 난잡함: 바벨탑의 탄생

MSA의 가장 큰 장점 중 하나는 각 서비스별로 최적의 기술 스택을 선택할 수 있다는 점이지만, 이것이 양날의 검이 되어 돌아왔습니다. 프로젝트가 진행됨에 따라 우리 시스템은 마치 바벨탑처럼 각양각색의 언어와 프레임워크가 공존하는 생태계가 되었습니다.

특히 신규 개발자 온보딩 과정에서 이 문제가 두드러졌습니다. “이것도 알아야 하고, 저것도 알아야 하고… 혹시 이 기술도 사용해보셨나요?” 라고 물어보고 싶을 정도였죠.

한 개발자가 모든 기술 스택을 완벽히 이해하기는 불가능에 가까웠고, 결국 개발자들은 특정 서비스의 ‘수호자’가 되어 버렸습니다. 만약 그 ‘수호자’가 휴가를 떠났는데 해당 서비스에 문제가 생긴다면? 피가 말리는 기분입니다. “휴가중에 죄송하지만 전화해도 될까요?” 와 같은 상황이 발생하고는 합니다.

또한 공통 라이브러리나 유틸리티 기능을 여러 언어로 중복 개발해야 하는 상황도 발생했습니다. 같은 기능을 여러 서비스에 중복되서 만들면서 “이게 맞나?”라는 생각이 항상 들고는 합니다. 개발자로서 공통화의 원칙에 위배되는 행위를 하는 건 언제나 고통스러운 것 같습니다.

코어 레거시에 대한 부채: 고대 유물과의 공존

가장 큰 도전은 시스템의 심장 역할을 하는 레거시 코어 서비스였습니다. 이 서비스는 마치 마추픽츄 같다고 할까요. 웅장하지만, 이제는 만든 사람과 유지보수 했던 사람들이 없어서 맥이 끊기다보니 후대의 개발자들이 고고학을 하는 심정으로 뜯어보면서 간간히 유지보수 할 수밖에 없었습니다.

문제는 이 레거시 코어가 원래 모놀리스 시스템에서 진화해 온 것이라, MSA의 세련된 철학과는 거리가 멀다는 점이었습니다. ORM의 N+1 쿼리 문제는 마치 잔잔한 호수에 던진 돌이 퍼져나가듯 전체 시스템 성능에 영향을 미쳤고, 복잡한 비즈니스 로직은 서비스의 핵심이지만 스파게티 코드 그 자체라고 생각이 들었습니다.

뿐만 아니라 이 코어 로직은 여러 개발자들의 스타일이 섞인 “다양성의 전시장” 같았습니다. 어떤 부분은 함수형으로, 어떤 부분은 객체지향으로, 또 어떤 부분은 “그냥 돌아만 가면 돼”라는 생존형 코드로 작성되어 있었죠. 농담처럼 전해지는 정말 웃지 못할 내용의 주석들도 있었고요. 이 코드를 변경하는 것은 마치 칼날 위에서 춤추는 것과 같았습니다.

결국 “작동하는 것은 건드리지 말자”는 소극적인 접근이 팀의 모토가 되었고, 이는 기술 부채라는 눈덩이를 계속 굴리는 결과를 가져왔습니다. 마치 미래의, 해당 기술 스택에 더 친수하고 더 용감한 개발자들이 해결해 주길 바라는 마음으로요.

불투명한 내부 의존성: 보이지 않는 실타래

MSA 환경에서 가장 과소평가했던 부분은 서비스 간 내부 의존성의 복잡성과 불투명성이었습니다. 초기에는 깔끔한 다이어그램으로 표현되던 서비스 간 호출이, 시간이 지남에 따라 마치 복잡한 거미줄처럼 변해갔습니다.

예를 들어, 한 사용자가 ‘확인’ 버튼을 클릭했을 뿐인데, 그 요청은 API 게이트웨이를 시작으로 5개 이상의 내부 서비스를 돌아다녀야 했습니다. 마치 단순한 질문을 했는데 부서 간 핑퐁이 시작되는 대기업의 고객센터 같았죠.

한 서비스의 API를 살짝 수정했을 뿐인데, 멀리 떨어진 다른 서비스에서 에러가 터져 나오는 경험은 마치 나비효과를 직접 목격하는 듯했습니다. “내 코드가 저기까지 영향을 미친다고? 난 그런 적 없는데!” 하는 놀라움의 순간들이 많았습니다.

특히 문서화가 제대로 되지 않은 암묵적 의존성들이 존재했기 때문에, 코드 변경의 영향 범위를 파악하는 데 때로는 실제 개발보다 더 많은 시간이 소요되었습니다. “이것만 바꾸면 될 줄 알았는데…” 혹은 “바꿔도 되나..?” 와 같은 말들을 할 수밖에 없었습니다.

장애 대응의 어려움: 디버깅의 악몽

MSA 환경에서 장애가 발생했을 때의 상황은 마치 영화 ‘인셉션’의 한 장면 같았습니다. 문제의 원인이 어디에 있는지 여러 층의 현실을 넘나들며 찾아야 했죠.

한 서비스의 문제가 마치 도미노처럼 다른 서비스로 쓰러져 가는 ‘장애 전파’ 현상은 디버깅을 미로 탐험과 같이 만들었습니다. “A 서비스가 타임아웃이 났는데, 원인은 B 서비스의 DB 락이었고, 그건 사실 C 서비스의 비정상적인 요청 폭증 때문이었다!” 같은 추리 소설급 결론에 도달하기까지의 과정이 고통스러웠습니다.

다행히도(?) 우리는 분산 트레이싱이 가능한 로그 시스템을 사용하고 있었지만, 만약 그런 시스템이 없었다면? 아마도 각 서비스의 로그를 수동으로 조회하며 타임스탬프를 맞춰보는 고고학자 같은 작업을 했을 겁니다. “이 로그와 저 로그 사이에 2초의 간극이 있는데, 그 2초 동안 무슨 일이 있었던 거지?” 하는 미스터리를 풀어야 했겠죠.

결론: 그래도 MSA, 사랑해요 (조건부로)

6개월간의 MSA 여정을 돌아보면, 마치 롤러코스터를 탄 기분입니다. 정상에 올랐을 때의 전망은 멋지지만, 급격한 하강 구간에서는 비명을 지르게 되는 그런 경험이었습니다.

MSA는 마법의 지팡이가 아닙니다. 그저 특정 문제를 해결하기 위한 도구일 뿐이죠. 모든 도구가 그렇듯, 적절한 상황과 준비가 갖춰졌을 때 진가를 발휘합니다.

만약 MSA를 도입하려는 분들께 조언을 드린다면:

  1. 처음부터 너무 많은 서비스로 쪼개지 마세요. 모듈러 모놀리스로 시작해서 필요할 때 분리하는 것이 현명할 수 있습니다.
  2. (⚠️매우 중요⚠️)분산 트레이싱과 모니터링에 초기부터 투자하세요. 이것이 나중에 여러분의 정신 건강을 지켜줄 겁니다.
  3. 서비스 경계를 비즈니스 도메인 중심으로 설계하세요. 기술 스택이나 팀 구조가 아닌, 실제 비즈니스 기능을 기준으로요.
  4. 문서화를 소홀히 하지 마세요. API 계약, 의존성 관계, 데이터 흐름 등을 명확히 기록해 두세요.
  5. 기술 스택 다양성에 제한을 두세요. 모든 최신 기술을 시험해보고 싶은 욕구를 억제할 필요가 있습니다.

MSA는 복잡한 시스템을 관리하는 강력한 방법이지만, 그 자체로 새로운 복잡성을 가져온다는 점을 항상 기억해야 합니다.
만약 MSA 를 도입하신다면 꼭 충분한 시간과 검토를 거쳐서 잘 설계하세요. 이 설계 과정에서 무엇을 공통으로 두고, 무엇을 독립할거며, 비즈니스를 어떻게 쪼갤지에 대해서 도메인 전문가와 많은 논의를 해야만 합니다. 그럼에도 불구하고, 저는 이 경험을 통해 많은 것을 배웠고, 더 나은 아키텍트와 개발자로 성장했다고 생각합니다. 이상으로 여러분의 MSA 여정도 성공적이길 바랍니다! 🚀

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.