2026년, 모듈형 모놀리스로 조용히 돌아가는 팀들

|MSA & Architecture|19분 읽기

아무도 말하지 않는 변화

최근 몇 년간 개발팀들 사이에서 조용한 흐름이 하나 있습니다. 마이크로서비스로 쪼개놓았던 시스템을 다시 하나로 합치는 팀들이 늘어나고 있거든요. 물론 예전의 스파게티 모놀리스로 돌아가는 건 아닙니다. **모듈형 모놀리스(Modular Monolith)**라는 형태로요.

저희 팀도 몇 년 전에는 다른 팀들과 똑같이 기존 백엔드를 마이크로서비스로 분리했습니다. 그때는 그게 당연한 선택처럼 느껴졌어요. 독립적인 배포, 명확한 팀 소유권, 그리고 '확장성'이라는 매력적인 단어들이 있었으니까요. 컨퍼런스에서도, 채용 공고에서도 다들 그렇게 말하고 있었고요.

그런데 몇 달 지나고 나니 현실은 좀 달랐습니다. 서비스 간에 502가 무작위로 튀고, 문제 하나를 잡으려면 대시보드 5개를 띄워놓고 3개 팀이 모여 디버깅 세션을 여는 일이 생겼어요. 지연 시간은 예측이 안 됐고, 로그는 사방에 흩어져 있는데 정작 필요한 한 줄은 늘 다른 곳에 있었습니다.

개별 서비스만 떼어놓고 보면 다 멀쩡했어요. 그런데 시스템 전체로 보면 묘하게 취약했습니다. 그때 우리는 대부분의 팀이 입에 올리기 싫어하는 질문을 던지기 시작했죠. "혹시 우리가 과하게 대응한 건 아닐까?"

마이크로서비스가 해결하려던 문제들

마이크로서비스가 매력적인 건 분명합니다. 이론적으로는 독립적인 스케일링이 가능하고, 배포 주기가 빨라지고, 팀 간 소유권이 명확해지고, 기술 선택의 유연성도 생깁니다. 전부 실제로 가능한 이야기예요. 거짓말은 아닙니다.

문제는 중소 규모 팀에서 실제로 일어나는 일이 좀 다르다는 거죠. 그냥 함수 호출 한 번이면 끝나던 게 네트워크 호출로 바뀌는 순간, 복잡도가 한 단계 올라갑니다. 실패가 결정론적이지 않고 확률론적이 돼요. 어제 잘 되던 호출이 오늘 가끔 안 되고, 그 '가끔'을 추적하는 게 일이 됩니다. 관측 가능성을 확보하는 것 자체가 누군가의 전담 업무가 되죠.

로컬 개발 환경을 띄우는 것도 고통스러워집니다. 화면 하나 띄워보려고 서비스 대여섯 개를 로컬에 올려야 하는 상황을 한 번쯤 겪어봤을 겁니다. 그리고 지연 시간은 조용히 누적됩니다. 초기에는 잘 안 느껴져요. 호출이 한두 개일 때는 티가 안 나니까요. 시스템이 자라고 서비스 간 호출이 사슬처럼 길어지고 나서야 비로소 체감하게 됩니다.

원인과 결과를 분리해서 보면 이렇습니다. 마이크로서비스가 문제를 만든 게 아니라, 우리가 필요하지도 않은 분산을 먼저 도입한 것이 원인이고, 위의 증상들은 그 결과였어요.

모듈형 모놀리스라는 대안

모듈형 모놀리스는 예전의 스파게티 코드로 돌아가는 게 아닙니다. 배포는 단일 유닛으로 하되, 내부에는 엄격한 경계를 둡니다. 도메인을 명확히 나누고, 모듈 간 공유 상태를 최소화하는 구조예요.

그림으로 단순하게 그리면 이런 모습입니다:

[클라이언트]
    ↓
[API 레이어]
    ↓
[모듈형 모놀리스]
    ├── 인증 모듈
    ├── 주문 모듈
    ├── 결제 모듈
    └── 알림 모듈
    ↓
[데이터베이스]

핵심은 모듈 사이에 네트워크 호출이 없다는 점입니다. 하나의 코드베이스 안에 깔끔하고 통제된 경계만 존재하죠. 마이크로서비스가 주려던 '경계'라는 이점은 가져가되, 분산 시스템이 딸려오는 복잡성은 떼어내는 겁니다. 경계는 꼭 네트워크여야만 생기는 게 아니라는 거죠.

실제로 더 나았던 지점들

지연 시간은 즉시 체감됩니다

마이크로서비스에서는 주문 → 결제 → 인증 → 데이터베이스로 이어지는 홉마다 네트워크 오버헤드(보통 5~20ms), 직렬화/역직렬화, 재시도 로직, 그리고 새로운 실패 지점이 하나씩 얹힙니다. 각각은 작아 보여도 사슬로 엮이면 무시 못 할 무게가 되죠.

모듈형 모놀리스에서는 주문서비스.주문처리()결제서비스.청구()인증서비스.유효성검사()처럼 그냥 함수 호출입니다. 밀리초가 아니라 마이크로초 단위로 끝나요. 같은 일을 하는데 단위가 세 자리 달라지는 셈입니다.

디버깅이 다시 인간적이 됩니다

마이크로서비스에서 문제가 터지면 서비스 사이를 넘나드는 요청을 추적해야 하고, 흩어진 로그를 손으로 이어 붙여야 하고, 분산 트레이싱이 제대로 켜져 있기를 기도해야 합니다. 기도가 들어맞는 날이 생각보다 적더라고요.

모듈형 모놀리스에서는 하나의 프로세스, 하나의 로그 스트림, 하나의 스택 트레이스면 됩니다. 실행 경로를 실제로 끝까지 따라갈 수 있다는 것, 이게 생각보다 엄청난 생산성 차이를 만듭니다. 추측이 아니라 추적으로 문제를 잡을 수 있으니까요.

실패를 통제할 수 있습니다

분산 시스템에서는 느린 의존성 하나가 연쇄 반응을 일으킵니다. 타임아웃이 나면 재시도가 돌고, 그 재시도가 부하를 키우고, 결국 시스템 전체가 무너지는 그림이죠. 폭풍처럼 몰아치는 재시도(retry storm)를 한 번 겪어보면 이게 농담이 아니라는 걸 알게 됩니다.

모놀리스 내부에서는 실패가 즉각적이고 눈에 보입니다. 재시도 폭풍도 없고, 시스템이 어떻게 동작할지 머릿속으로 추론하기가 훨씬 쉬워집니다.

실제 운영 사례로 보는 차이

저희가 다뤘던 단순한 흐름 하나를 예로 들겠습니다: 주문 생성 → 사용자 인증 → 결제 → 주문 저장

마이크로서비스 버전에서는 4개의 서비스와 3번의 네트워크 호출이 끼어 있었습니다. 평균 지연은 약 180ms, 간헐적으로 400~600ms까지 튀었어요. 실패 양상도 지저분했습니다. 결제 시간 초과 → 재시도 → 이중 청구 예외가 나거나, 인증이 늦어지면서 전체 요청이 막히는 일이 잊을 만하면 반복됐죠.

모듈형 모놀리스 버전에서는 동일한 비즈니스 로직을 단일 서비스의 함수 호출로만 처리했습니다. 평균 지연은 약 45ms로 떨어졌고, p99도 안정적으로 유지됐어요. 내부 재시도가 필요 없었고, 실패는 예측 가능한 형태로 나타났습니다. 어디서 깨질지가 코드에 그대로 보이니까요.

이건 단순히 숫자 몇 개가 좋아진 게 아닙니다. 사용자 경험과 개발팀 생산성 양쪽에 동시에 영향을 주는 변화였어요. 응답이 빨라지는 동시에, 새벽에 호출받는 횟수가 줄었다는 뜻이기도 하고요.

'모듈형'을 진짜로 구현한다는 것

많은 팀이 미끄러지는 지점이 바로 여기입니다. 경계 없는 모놀리스는 그냥 혼란스러운 코드 덩어리일 뿐이거든요. '모놀리스로 합쳤다'는 말과 '모듈형으로 설계했다'는 말은 완전히 다른 이야기입니다. 진짜 모듈형에서는 각 모듈이 자기 인터페이스를 갖고, 다른 모듈 내부에 손을 대지 않으며, 계약(contract)을 통해서만 대화합니다.

Go 언어 예시:

// order/service.go
type OrderService struct {
    paymentService PaymentService
    authService    AuthService
}

func (s *OrderService) CreateOrder(userID string, amount float64) error {
    if err := s.authService.Validate(userID); err != nil {
        return err
    }

    if err := s.paymentService.Charge(userID, amount); err != nil {
        return err
    }

    return saveOrder(userID, amount)
}

Java 언어 예시:

// 결제 모듈
public class PaymentService {
    public void charge(String userId, double amount) {
        // 내부 로직
    }
}

// 주문 모듈
public class OrderService {
    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void createOrder(String userId, double amount) {
        paymentService.charge(userId, amount);
    }
}

중요한 건 모듈끼리 공유하는 데이터베이스 테이블이 없고, 남의 내부 클래스에 직접 접근하지도 못한다는 점입니다. 결제 모듈이 주문 테이블을 직접 읽기 시작하는 순간, 경계는 이미 무너진 거예요. 이게 단순한 코드 정리와 모듈형 설계를 가르는 결정적 선입니다.

흔한 오해들과 현실

"확장성이 떨어진다"

절반만 맞는 이야기입니다. 보통은 수직 확장부터 해보는데, 이것만으로 충분한 경우가 생각보다 많아요. 인스턴스를 키우면 메모리도 효율적으로 쓰고 네트워크 홉도 줄어듭니다. 수평 분할은 진짜 필요해진 다음에 해도 늦지 않아요. 처음부터 수만 RPS를 가정하고 설계하는 팀이 실제로 그 트래픽을 받는 경우가 얼마나 되는지 생각해보면 됩니다.

"팀들이 서로 코드를 망친다"

이건 경계가 약할 때만 생기는 문제입니다. 모듈 설계가 단단하고 소유권이 명확하면, 분산 구조의 복잡성 없이도 마이크로서비스가 주려던 격리 효과를 그대로 얻을 수 있어요. 경계를 지키는 건 네트워크가 아니라 합의와 리뷰입니다.

"나중에 다시 다 짜야 한다"

꼭 그렇지는 않습니다. 모듈이 깨끗하게 분리돼 있으면 나중에 특정 모듈을 서비스로 떼어내는 것도 최소한의 변경으로 가능해요. 오히려 모듈형 모놀리스는 미래의 마이크로서비스 전환을 위한 좋은 발판이 됩니다. 먼저 경계를 코드 안에서 검증해보고, 정말 분산이 필요해진 모듈만 밖으로 꺼내는 거죠. 순서가 반대인 겁니다.

마이크로서비스가 여전히 맞는 영역

물론 마이크로서비스가 쓸모없다는 얘기는 아닙니다. 이런 상황에서는 여전히 분산이 더 나은 답일 수 있어요:

  • 각 부분마다 독립적인 스케일링이 반드시 필요한 경우
  • 팀 규모가 크고, 완전히 자율적인 조직 구조를 가진 경우
  • 결제 시스템이나 머신러닝 파이프라인처럼 격리 자체가 정말 중요한 경우

다만 처음부터 이 아키텍처로 출발한 선택에 대해서는, 대부분의 팀이 시간이 지나고 후회하더라는 게 제가 본 현실입니다. 필요해서 나눈 것과 멋있어 보여서 나눈 것은 결국 운영 단계에서 표가 납니다.

회귀하는 팀들의 진짜 이유

마이크로서비스가 나빠서가 아닙니다. 다음과 같은 현실적인 이유들 때문에 팀들이 모듈형 모놀리스로 돌아가고 있어요:

  1. 대부분의 시스템은 그 정도 복잡성을 필요로 하지 않는다
  2. 분산 시스템 디버깅에 드는 비용이 너무 크다
  3. 아키텍처의 우아함보다 지연 시간이 더 중요하다
  4. 서비스가 너무 많아지면 개발자 생산성이 급격히 떨어진다

결국 팀들이 최적화하는 대상은 개발 속도, 운영의 간편함, 예측 가능한 성능입니다. 모듈형 모놀리스가 마침 그 균형점에 가깝게 앉아 있는 거죠. 화려한 분산 다이어그램보다, 새벽 3시에 혼자서도 따라갈 수 있는 스택 트레이스가 더 가치 있다는 걸 다들 한 번씩 겪고 나서 깨닫는 것 같습니다.

솔직한 단점들

모듈형 모놀리스도 완벽하진 않습니다. 배포가 전체 시스템에 영향을 주고, 경계를 유지하려면 꾸준한 자제력이 필요하고, 확장의 단위가 덜 세밀합니다. 그리고 방심하면 "거대한 진흙 덩어리"로 변질될 위험도 늘 깔려 있죠. 경계는 한 번 그어두면 끝나는 게 아니라, 코드 리뷰 때마다 지켜내야 하는 합의에 가깝습니다.

다만 핵심 차이는 이겁니다. 이 문제들은 분산 시스템의 혼란보다 훨씬 다루기 쉽다는 것. 코드 리뷰와 아키텍처 가이드라인 정도로도 충분히 통제 가능한 수준이에요. 적어도 네트워크 너머에 숨은 문제를 쫓아다니는 것보다는요.

핵심은 타이밍

결국 이건 업계가 "퇴보"한 게 아니라, 현실적인 요구에 맞춰 타이밍을 다시 맞춘 겁니다. 분산 시스템은 강력해요. 하지만 그만큼 비용이 듭니다. 그 비용을 낼 준비가 되기 전에 먼저 청구서부터 받는 게 문제였던 거죠.

대부분의 팀이 뒤늦게 깨닫는 건 의외로 단순한 사실입니다. 확장 가능한 제품을 만들기 위해 분산 시스템이 먼저 필요한 게 아니라, 이해할 수 있는 시스템이 먼저 필요하다는 것. 복잡성은 정말 필요해진 다음에 더해도 늦지 않습니다.

생각해보면 중요한 건 아키텍처 패턴 그 자체가 아닌 것 같아요. 그걸 언제, 왜 선택하느냐. 같은 패턴도 그 두 가지에서 갈립니다.

#모듈형모놀리스#마이크로서비스#아키텍처#백엔드#개발생산성