마이크로서비스의 망령과 작별하기: 2025년, 모듈러 모놀리식의 귀환
지난 10년간, 개발자 면접이나 시스템 설계 논의에서 확장성(Scale)에 대한 질문이 나오면 정답은 늘 정해져 있었습니다. **"마이크로서비스(Microservices)"**였죠.
개발자가 고작 3명인 초기 스타트업조차도 아직 유저 트래픽은 커녕 MVP도 안 나왔는데 쿠버네티스(k8s) 클러스터부터 올리고 봅니다. 500줄이면 끝날 비즈니스 로직을 6개의 서비스로 쪼개 놓습니다. 전형적인 **'이력서 주도 개발(Resume Driven Development)'**이자, 오버 엔지니어링의 시대였습니다.
하지만 2025년 현재, 분위기가 완전히 달라졌습니다. 아마존 프라임 비디오(Amazon Prime Video)가 마이크로서비스에서 모놀리식으로 회귀하며 인프라 비용을 90%나 절감한 사례는 업계에 큰 충격을 주었죠. DHH를 필두로 한 '모놀리식의 위엄(Majesty of the Monolith)' 담론도 다시 힘을 얻고 있습니다.
개발자들은 이제서야 뼈아픈 현실을 마주하고 있습니다.
"마이크로서비스는 '복잡성'이라는 고금리 사채이며, 대부분의 팀은 그 이자를 감당할 능력이 없다."
이 글은 마이크로서비스가 무조건 나쁘다는 게 아닙니다. 다만 우리가 맹목적으로 쫓았던 그 유행이 왜 식어가고 있는지, 그리고 그 대안인 **'모듈러 모놀리식(Modular Monolith)'**으로 어떻게 현실적인 시스템을 구축할 수 있는지 이야기해보려 합니다.
'분리(Decoupling)'라는 달콤한 환상
마이크로서비스 찬성론자들의 논리는 그럴싸했습니다. "시스템을 쪼개라! 팀간 의존성을 없애라! 배포를 자유롭게 하라!"
이론적으로는 완벽합니다. 서비스 A와 서비스 B가 깔끔한 API로만 대화하니까요.
하지만 현실은 어떤가요? 우리는 **'분산된 모놀리스(Distributed Monolith)'**라는 괴물을 만들었을 뿐입니다.
1. 독립성은 없다, 네트워크 호출만 남았을 뿐
서비스 A가 B를 호출하고, B가 다시 C를 호출하는 구조를 봅시다. 서비스 C의 응답 포맷이 바뀌면 어떻게 되나요? A, B, C를 관리하는 세 팀이 모여서 회의를 하고, 배포 순서를 조율해야 합니다.
IDE에서 Refactor > Rename 단축키 한 번이면 끝날 일이, 3개 레포지토리의 PR과 배포 전쟁으로 변질됩니다. 결합도(Coupling)를 낮춘 게 아니라, 결합된 지점을 **가장 느리고 불안정한 구간인 '네트워크'**로 옮겨버린 셈입니다.
2. 레이턴시 세금 (Latency Tax)
모놀리식에서 함수 호출은 메모리상에서 나노초(ns) 단위로 끝납니다. '공짜'나 다름없죠.
하지만 마이크로서비스에서는 이야기가 다릅니다.
- JSON 직렬화 (CPU 낭비)
- 네트워크 전송 (레이턴시 발생)
- 로드 밸런서 통과
- JSON 역직렬화 (CPU 낭비)
- 로직 수행
- ...반복
단순한 유저 프로필 조회 하나 때문에 내부적으로 HTTP 요청이 50번씩 오가는 시스템을 본 적이 있습니다. 이건 N+1 문제를 DB가 아니라 네트워크 레벨로 키운 꼴입니다. 아무리 캐시를 발라도 근본적인 구조적 결함은 해결되지 않습니다.
운영 지옥 (Operational Nightmare)
"우리 팀은 넷플릭스가 아닙니다."
이 사실을 인정해야 합니다. 인프라 관리 리소스가 부족한 중소규모 팀에게 마이크로서비스는 재앙입니다.
- 로그는 어디에?: 예전엔
tail -f나grep이면 충분했습니다. 이젠 요청 하나가 죽으면, Jaeger나 Datadog 같은 분산 트레이싱 툴 없이는 원인조차 못 찾습니다. - 트랜잭션의 악몽:
BEGIN TRANSACTION ... COMMIT으로 끝나던 원자성(Atomicity)이 사라졌습니다. 이제 SAGA 패턴이니 보상 트랜잭션이니 하는 복잡한 로직을 구현해야 합니다. 데이터 정합성 맞추다가 밤샙니다. - 개발 환경 구축: 신규 입사자가 로컬에서 서버 한번 띄우려면 도커 컨테이너 15개를 띄워야 합니다. 맥북 팬 소리에 옆 자리 동료 목소리가 안 들릴 지경이죠.
대안: 모듈러 모놀리식 (Modular Monolith)
그렇다면 우리는 다시 거대한 스파게티 코드, GlobalUtils.java가 난무하는 10년 전으로 돌아가야 할까요? 아닙니다.
해답은 모듈러 모놀리식에 있습니다.
물리적으로는 하나의 배포 단위(Monolith)지만, 논리적으로는 완벽하게 격리된 모듈 구조를 갖추는 것입니다.
모듈러 모놀리식의 핵심 원칙
- 배포는 한 번에: CI/CD 파이프라인은 하나입니다. 운영이 압도적으로 단순합니다.
- 통신은 함수로: 모듈 간 통신은 HTTP가 아니라 메서드 호출입니다. 성능 저하가 없습니다.
- 경계는 엄격하게: 이게 제일 중요합니다.
User모듈의 DB 테이블이나 내부 클래스를Order모듈에서 마음대로 import 할 수 없게 막아야 합니다.
어떻게 구현하나?
Java Spring이라면 Maven/Gradle 멀티 모듈, Node.js라면 Nx나 Monorepo 툴, 혹은 단순히 ESLint 규칙만으로도 충분합니다.
src/ modules/ users/ index.ts # (공개) 외부에서 쓸 수 있는 인터페이스만 export api/ # (비공개) DTO, Service Interface core/ # (비공개) 도메인 로직 db/ # (비공개) Repository orders/ ... shared/ # (최소화) 로깅, 유틸 등 app.ts # 모듈 조립
orders 모듈에서 ../users/db/UserRepository를 직접 import 하려고 하면 빌드 에러를 내뿜게 만드세요. 아키텍처는 문서가 아니라 코드로 강제해야 합니다.
마이크로서비스는 언제 필요한가?
마이크로서비스 자체가 나쁜 기술은 아닙니다. 다만 **'기본값(Default)'**이 되어서는 안 된다는 겁니다.
다음 조건에 부합할 때만 고려하세요.
- 조직 규모: 백엔드 개발자만 100명이 넘어가서, 배포 한번 하려면 결재를 3번 받아야 하고 머지 충돌 때문에 하루를 다 날릴 때. 이때는 기술적 비용을 지불하고 조직의 효율을 사는 겁니다.
- 물리적 제약: 이미지 처리 모듈은 GPU가 필요하고(Python), 주문 처리는 고성능 I/O가 필요할 때(Golang/Node).
- 격리 필요성: 특정 기능(예: 실시간 채팅)에만 트래픽이 폭주해서 전체 서버를 다운시킬 위험이 있을 때, 그 녀석만 떼어내는 전략.
결론: 유행 대신 실리를
시스템 설계는 트레이드오프의 예술입니다.
95%의 기업, 99%의 프로젝트에서 마이크로서비스는 "단순한 함수 호출을 복잡한 분산 처리로 바꾸는" 멍청한 거래입니다.
2025년, 여러분이 보여줄 수 있는 최고의 기술적 통찰력은 **"굳이 쪼개지 않는 용기"**입니다.
모듈러 모놀리식으로 시작하세요. 경계를 잘 지키며 코드를 짜세요. 그리고 정말로 필요할 때, 그때 떼어내도 늦지 않습니다.
가장 지루한(Boring) 기술이 가장 돈을 잘 벌어다 준다는 사실, 잊지 마세요.
관련 도구 둘러보기
Pockit의 무료 개발자 도구를 사용해 보세요