누군가에게는 이 제목이 상당히 도전적으로 들릴 수도 있을 것 같습니다. 마이크로서비스 아키텍처는 지난 10년간 많은 프로젝트에서 그 가치를 증명해왔는데 그 성공적인 적용을 의심하는 것 자체가 불경스러운 말처럼 들리기 때문입니다. 하지만 누군가에게는 이 제목이 상당히 의문스럽게 들릴 수도 있을 것 같습니다. “마이크로서비스 아키텍처가 ‘항상’ 성공했다고?” 아마도 마이크로서비스 아키텍처에 받은 상처가 많은 개발자들이 주로 이런 생각을 할겁니다.
마이크로서비스 아키텍처의 시작을 어느 시점으로 보느냐는 관점에 따라 다를 수 있지만, 적어도 마이크로서비스 아키텍처의 확산에 2014년 3월, 마틴 파울러와 샘 뉴먼이 작성한 마이크로서비스라는 글이 상당한 영향을 끼쳤음을 부정할 수는 없을 것입니다. 그리고 사실 이 글에서 제시된 마이크로서비스 아키텍처라는 개념 자체는 기존 소프트웨어 개발에서 발생하는 문제점들을 극복하기 위해 많은 프로젝트 현장에서 시도되고 있던 다양한 시도들을 정리한 것이기 때문에 많은 아키텍트들과 개발자들에게 마이크로서비스 아키텍처의 개념이 받아들이기 어렵다거나 이해하지 못할 공상 같은 느낌으로 다가오지는 않았습니다.
사실 거의 대부분은 마음만 먹으면 할 수 있는 것들이었죠. 특히나 당시에는 이미 넷플릭스 OSS 스택이 릴리즈 된 상태였고 당시에는 퍼블릭 클라우드 환경의 활용도 활발하게 논의되고 있던 시점이기 때문에 기술적으로도 큰 제약은 없었습니다. 더군다나 모바일 애플리케이션 시장이 놀라운 속도로 성장하고 있던 시점이었기 때문에 새로운 시스템을 구축해야 하는 니즈도 예산도 인력도 충분한 상황이었습니다. 덕분에 국내의 보수적인 소프트웨어 개발 현장에서도 마이크로서비스 아키텍처는 비교적 빠르게 확산될 수 있었습니다. 그리고 대부분 성공적으로 안착했습니다.
문제는 마이크로서비스 아키텍처의 확산이 지나치게 빠르게, 지나치게 성공적으로 마무리 되었다는 것입니다. 일반적으로 마이크로서비스 아키텍처의 부작용이라고 한다면 메모리와 CPU 레벨에서 끝날 수 있는 작업이 네트워크 레벨로 옮겨지면서 생기는 성능 문제들, 조직의 구조와 맞지 않는 서비스 경계로 인해 유발되는 조직 개편과 리팩토링의 무한 루프, 충분히 자동화되지 못한 빌드와 배포 파이프라인 덕분에 점점 규모가 커지는 배포 작업들, 조직간 커뮤니케이션의 부재로 인해 발생하는 수많은 오해와 오류 등등이 떠오를 수 있습니다. 그리고 마이크로서비스의 적용 경험이 한참 쌓인 지금 시점에서는 누구나 이런 부작용을 방지하려면 ‘당장 내일부터 마이크로서비스를 적용합시다’라는 말을 하기 전에 준비하고 고민해야할 것들이 매우 많다는 사실도 알고 있을 겁니다. 하지만 마이크로서비스가 막 유행을 타기 시작한 시점에는 ‘우리는 이제 마이크로서비스 아키텍처를 적용했습니다’라는 선언의 유혹을 이기지 못한 많은 프로젝트 현장이 충분한 준비와 고려없이 마이크로서비스 아키텍처라는 간판을 내걸기 시작했습니다. 그 결과 마이크로서비스 아키텍처는 지나치게 빠르게 현장에 퍼져나가기 시작했고, 부작용에 대한 검증이 시작되기도 전에 ‘성공’이라는 성적표를 발급하고, 누군가는 그 성공의 보상을 받고 현장을 떠났고 누군가는 ‘성공한 프로젝트’를 이어 받아서 부작용을 감내하기 시작했습니다. 그리고 마이크로서비스 아키텍처에 상처 받는 개발자들이 나타나기 시작했죠.
사실 이런 상황은 굳이 마이크로서비스 아키텍처에서만 유별나게 나타나는 상황은 아닙니다. 특히나 연간 목표나 KPI를 위주로 개발 목표가 정해지는 기관이나 회사에서는 어떤 새로운 기술 트렌드가 확산될 때 이 기술의 ‘적용’이 목표가 되는 경우가 상당히 많이 발생합니다. 예를 들어서 앱 스토어에 방치되어 있는 수많은 저품질의 공공기관 애플리케이션들이나 ARS보다 나은 점을 찾아볼 수 없는 수많은 챗봇들이 그 사례 중 하나입니다. 모바일 애플리케이션, 챗봇, 머신러닝, 클라우드, LLM, 마이크로서비스 아키텍처는 모두 각기 그 기술이 지향하는 목표가 분명하게 있고 풀고자 하는 문제가 있는데 아쉽게도 많은 현장은 그 기술이 왜 트렌드가 되었는지, 어떤 문제를 해결하고자 하는지, 무엇을 만들고자 하는지에 관심을 가지지 않고 ‘우리도 이거 적용했어요’라는 보고를 올리기 위한 프로젝트를 발주하고 있습니다. 그렇게 정치적인 이유로 프로젝트가 시작되면, 그 프로젝트는 반드시 성공할 수밖에 없는 상황이 됩니다. 모바일 애플리케이션은 앱 스토어에 등록만 되면 성공이고, 챗봇은 고객센터 전화번호만 알려줘도 성공이 됩니다. 마이크로서비스 아키텍처는 어떨까요? 프로젝트의 성공과 실패를 판정하는 사람들이 이 서비스들이 제대로 분리되었는지, 지속적으로 배포 가능한 환경을 구축했는지, 변경에는 유연하게 대응할 수 있는지 등을 판단할 수 있을리가 없으니 대부분의 마이크로서비스 전환 프로젝트는 프리패스로 성공할 수밖에 없었습니다. 물론 그 많은 프로젝트들이 다 엉망이었다거나 하는 척만 했다는 것은 아닙니다. 대부분은 각자가 이해한 방식대로 마이크로서비스 아키텍처를 구현해냈습니다. 다만 그 목표가 ‘마이크로서비스 아키텍처의 적용’ 이었던 많은 프로젝트들은 대부분 어정쩡하게 성공하게 되었습니다. 정말로 마이크로서비스를 적용해서 얻고자 했던 가치가 무엇인지 아무도 모르는 상태로요.
다만 이 상황 자체는 조금 답답할 수는 있어도 아주 큰 문제라고 보기는 어려웠습니다. 그런데 문제는 어느 순간부터 마이크로서비스 아키텍처가 목표가 아닌 수단이 되기 시작했고, 어정쩡한 성공의 기억은 미화되어 그냥 성공의 기억으로 남게 되었다는 점입니다. 그 결과 새로운 프로젝트를 마이크로서비스 아키텍처로 만드는 것은 너무나 당연해져서 서비스를 억지로라도 나누어야 되는 상황이 나오기 시작했고, 쿠버네티스(Kuberentes) 환경에서 유레카(Eureka)를 띄우는 것과 같은 망측한 결과물이 나타나기 시작했습니다. 2015년이나 2016년쯤에 완성된 마이크로서비스 아키텍처는 넷플릭스 스택, 혹은 스프링 클라우드 스택을 사용하는 것이 당연했고, 이 스택의 적용이 ‘성공’했으니 퍼블릭 클라우드와 쿠버네티스를 기반으로 하는 2018년이나 2019년의 프로젝트에서도 성공 방정식을 적용하듯이 기존 스택들을 끌어오기 시작한 것입니다.
저는 2019년도에 모 대형 SI 프로젝트의 제안서를 작성하는 작업을 한 적이 있었습니다. 당장의 사업만 수백억 규모이고 후속 사업을 고려하면 수천억이 넘는 규모였던 그 프로젝트의 제안서는 당연히 기존의 낡아빠진 레거시 시스템을 클라우드, 쿠버네티스, 마이크로서비스 아키텍처로 개혁시켜주겠다는 포부를 담을 수 밖에 없었습니다. 경쟁사들도 똑같은 소리를 할 것이 뻔했으니까요. 물론 모놀리식 시스템을 마이크로서비스 아키텍처로 전환하는 과정은 샘 뉴먼의 Monolith To Microservices 같은 책을 보면 알 수 있듯이 이미 체계가 많이 잡혀 있었고, 환경에 따라 차이는 있지만 그 제안 자체가 불합리하다거나 비현실적인 것은 아니었습니다. 다만 SI 프로젝트의 차세대 프로젝트는 적어도 10년 이상 유지된 시스템의 모든 기능을 새로 구현해야 한다는 규모에서 오는 어려움도 있었고, 따라서 아무것도 하지 않고 그저 프로그래밍 언어와 프레임워크 정도만 최신화 하는 작업도 주어진 리소스 내에서 완수하기에는 쉽지 않다는 제약이 있었습니다. 그리고 그 모든 것보다 더 큰 제약은, 대부분의 시스템들이 데이터베이스 중심의 비즈니스 로직을 가지고 있었다는 점이고, 이 말은 다시 말해서 매우 복잡한 쿼리에 중요한 로직이 다 박혀있을 가능성이 높다는 말입니다. 그래서 많은 SI 프로젝트들이 차세대 프로젝트를 수행할 때 이 쿼리를 거의 건드리지 않고 그대로 가져온 다음 그 바깥쪽 프레임워크만 최신화 하는 형태로 진행되는 경우가 많습니다. 그래서 실제로 2010년대의 차세대 프로젝트들은 2000년대의 시스템을 다시 만드는 작업이 아니라 1990년대의 쿼리를 가져와서 시스템을 재구축 하는 형태로 진행된 경우가 많았습니다. 이런 이유로 SI 프로젝트들은 새로운 시스템을 구축하는 것이 아니라면 NoSQL을 도입하기도 어려웠죠.
그런데 문제는 NoSQL이 아니었습니다. 그건 그냥 안 쓰겠다고 제안하면 되는 것이니까요. 가장 큰 문제는 마이크로서비스 아키텍처로 전환하면서 수백줄 짜리 쿼리 수천개를 가져올 수는 없었다는 것입니다. 모놀리식 시스템을 마이크로서비스 아키텍처로 전환할 때는 보통 기존의 거대한 시스템을 일단 적절한 기준으로 여러 개의 마이크로서비스로 나누고 나면, 데이터베이스 역시 논리적으로 나누어서 각각의 서비스가 각각의 데이터베이스를 독립적으로 사용하는 구조를 만들게 됩니다. 마이크로서비스 시스템은 기본적으로는 분산 시스템이고, 각각의 독립적으로 분산된 마이크로서비스가 하나의 데이터베이스를 공유 리소스로 사용하게 되면, 그 데이터베이스가 시스템의 병목지점이자 단일 실패 지점으로 작용하게 되어 마이크로서비스 아키텍처가 지향하고자 하는 목표중 상당수를 모놀리식 시스템의 레벨로 끌어내리게 됩니다. 때문에 마이크로서비스 아키텍처로 만들어진 시스템들은 설령 비용등의 문제로 물리적인 데이터베이스가 하나만 존재하는 상황에서도, 최소한 논리적으로는 각각의 서비스가 다른 데이터베이스 혹은 스키마, 네임스페이스를 사용하도록 디자인하여 필요한 경우 데이터베이스를 분리할 수 있도록 대비하는 형태로 설계하게 됩니다.
그런데 레거시 쿼리 수백개를 그대로 사용해야 한다는 상황은, 당연하게도 각각의 서비스가 사용하는 데이터베이스가 논리적으로 구분되지 못하고 서로 다른 서비스의 영역에 위치한 테이블을 조인해서 사용한다는 것이고, 이렇게 엮여있는 관계를 풀기 위해서는 적어도 자신의 영역 외부를 참조하는 쿼리를 모두 새로 짜야한다는 말이 됩니다. 그리고 이 말은 필요한 예산이 유의미하게 증가한다는 말로 이어지고, 결국 제안자는 둘 중 하나를 선택해야 했습니다. 마이크로서비스 아키텍처의 적용을 포기하거나, 더 비싼 값으로 제안하거나.
그래서 제안실의 경험 많은 아키텍트들은 한참을 회의하더니 이윽고 모두가 만족할 수 있는 결론을 내렸습니다. ‘우리는 마이크로서비스 아키텍처를 적용합니다’ ‘그리고 데이터베이스는 하나만 가져갑시다’ 그러자 저를 포함한 제안실의 경험 적은 아키텍트들이 알러지 반응과 비슷한 반응을 보였습니다. ‘그게 마이크로서비스 아키텍처가 맞나요?’ 그러자 제안실의 현자들이 사회 생활 경험이 부족한 애송이들에게 큰 가르침을 주었습니다. ‘이것이 우리 방식의 마이크로서비스 아키텍처다’ 그 순간 저는 고개를 떨굴 수 밖에 없었습니다. 물론 감격스러워서 그런 것은 아니었습니다.
사실 ‘마이크로서비스 아키텍처’는 그렇게 중요하지 않습니다. 정확히 말하면 저 타이틀 자체는 아무것도 아니라는 것입니다. 진짜 중요한 것은 마이크로서비스 아키텍처가 왜 시스템을 여러 작은 서비스로 분할하려고 하는지 그 배경과 목적이 더 중요합니다. 지난 10년간 너무 많은 프로젝트들이 마이크로서비스 아키텍처라는 타이틀에 집착하여 나누지 않아도 될 시스템을 나누게 되었고, 그 결과 모놀리식 시스템이 더 적합한 프로젝트들이 마이크로 서비스로 분할되어 부작용만 잔뜩 뿜어내는 구조를 가지게 되었습니다. 이 시스템은 몇 개의 서비스로 분할되는 것이 맞는가? 와 같은 구조적인 해석을 요구하는 질문에 딱 떨어지는 정답이 없다보니 마이크로서비스 아키텍처에 대한 각각의 해석이 난무하게 되었고, 이 해석이 각자의 성공 경험으로 치환되면서 세상에는 수많은 ‘우리식 마이크로서비스 아키텍처’가 생겨나게 되었습니다. 그래서 가끔은 마이크로서비스 아키텍처에 대한 상이한 이해가 많은 충돌을 야기하기도 했습니다. 자존심 강한 개발자들이 아키텍처에 대한 의견 차이로 싸우기 시작했을때 현장에서 어떤 일이 벌어지는지는 19년차 미만의 개발자가 읽기에는 부적절할 수 있어서 상상의 영역으로 남겨두겠습니다.
그래서 사실 이 시점에서 고백하자면, 모든 마이크로서비스 아키텍처가 항상 어정쩡하게 성공하는 것은 아닙니다. 10년이 넘는 기간동안 수도 없이 많은 마이크로서비스 시스템들이 만들어졌고, 그 중에는 충분히 성공적으로 만들어졌다고 평가할 수 있을만큼 잘 만들어진 시스템들도 많습니다. 마이크로서비스 아키텍처에 대한 해석이 분분해지고, 개발 경험이 파편화되면서 어떤 경우에는 올바르게 잘 만든 시스템들과 그 시스템의 설계가 어떤 환경에서는 부실한 구조로 오인되기도 합니다. 가장 대표적인 사례가 마이크로서비스 환경에서 트랜잭션 보장에 대한 견해 차이일 것입니다. 처음 마이크로서비스 아키텍처가 도입되기 시작했을 때 많은 사람들의 고민은 ‘분산 환경에서 트랜잭션을 어떻게 보장할 수 있을까’에 대한 것이었습니다. 왜냐면 당시에는 아직 사람들이 마이크로서비스 아키텍처에 익숙하지 않았고, 그렇기 때문에 고전적인 개발에서 반드시 지켜야 하는 가치들을 절대로 포기하지도 않던 상황이었습니다. 그 가치 중 대표적인 것이 트랜잭션이었고, ‘트랜잭션을 보장하지 않는다’라는 선택지는 쉽게 선택하기 어려웠습니다.
그래서 이론적으로는 마이크로서비스 환경에서 트랜잭션을 보장하기 위한 다양한 제안들이 있었고, 많은 개발자들이 많은 시도를 했지만, 결국 시스템이 복잡해지면 기존 데이터베이스 기반의 트랜잭션만큼 엄격한 정합성을 보장하는 것이 거의 불가능하다는 결론을 가지게 되었습니다. 이런 곤란한 상황에서, 마이크로서비스 아키텍처의 본질적인 지향점을 지키고자 하는 일부 개발자들은 트랜잭션을 보장하는 것보다는 결과적인 일관성을 보장하는 쪽이 더 적합한 구현이라고 생각하고 이 방향으로 구조와 설계를 개선시켜 나갔고, 어떤 개발자들은 보상 트랜잭션 기반의 정합성을 유지하기 위해서 노력했습니다. 물론 어떤 방향이 맞고 어떤 방향이 틀리다는 식의 해석은 무례할 수 있지만, 10년이 지난 지금 생각해보면 결과적 일관성을 지키기 위해 노력하는 쪽이 조금 더 유의미한 성과를 내지 않았을까라는 생각은 있습니다. 다만 이런 생각을 가지고 있는 사람은 고전적인 방식의 트랜잭션의 보장에 훨씬 더 높은 비중을 두고 있는 조직에서는 데이터의 정합성이 깨질 수도 있는, 기본이 안 된 시스템을 설계하는 능력이 부족한 사람으로 오인될 위험이 생기게 됩니다. 더 큰 문제는 그 마이크로서비스 아키텍처에 대한 주된 해석이 마이크로서비스의 본질에 가까운 해석이 되는게 아니라 목소리 큰 사람의 해석이 되어버리면서, 새롭게 개발 현장에 입문하는 개발자들에게 마이크로서비스에 대한 큰 혼란을 가가져오게 될 위험성이 생기게 된다는 것입니다. 저는 이것이 부실하게 성공한 마이크로서비스 아키텍처 프로젝트들이 남겨놓은 후유증과 같은 부작용이라고 생각하고 있습니다.
마이크로서비스 아키텍처가 본격적으로 확산되기 시작한지 10년이 되었고, 그 시기에 만들어진 시스템들이 이제 레거시 시스템이 되어가는 이 시점에서 마이크로서비스 아키텍처가 왜 등장하게 되었고 그 이전에는 어떤 문제들이 있었는지, 혹시 마이크로서비스 아키텍처가 당연하게 쓰이는 지금에는 그 문제들이 다 해결되었는지 생각해보는 것은 너무 목표 지향적으로 달려온 소프트웨어 개발 현장에는 적절한 브레이크 포인트가 될 수 있을 것 같다는 생각을 해봅니다. 조금 역설적으로 들릴 수 있지만, 마이크로서비스 아키텍처에 대한 이해가 깊어지면 오히려 마이크로서비스 아키텍처를 사용하지 않는 선택을 할 수도 있는 것이고, 그럴 수 있다면 저희는 마이크로서비스 아키텍처를 정말로 필요한 곳에 적절하게 적용할 수도 있을 것입니다.
물론 2014년의 개발 환경과 2024년의 개발 환경이 달라진 것이 많은 만큼 2014년에 정리된 마이크로서비스 아키텍처의 원칙들을 헌법처럼 생각하고 따를 필요는 없지만, 적어도 내가 중요한 것을 놓치지는 않았는지, 혹은 무언가 잘못 이해하고 잘못 적용하고 있었던 것은 아닌지에 대해 한 번 돌이켜 보기 위한 좋은 시작점이 될 수는 있습니다. 모든 분야가 그렇듯이, 명작은 시대를 넘어서는 법이고 가끔은 본질적인 것에 대한 탐구가 현실적인 문제에 대한 해답이 될 수도 있는 것이니까요.
이 글에서 작성된 내용과 그림의 일부는 MSA 워크샵: MSA 개발 환경 구축부터 성능, 보안 등 심화주제까지 강의의 교안을 인용하여 작성되었습니다.