마이크로서비스 아키텍처(MSA)란 무엇인가? 모놀리식과 마이크로서비스 아키텍처 간의 선택

마이크로서비스-아키텍처

모놀리식과 마이크로서비스 아키텍처 간의 선택은 배포 빈도, 팀 자율성, 인프라 비용, 그리고 시스템을 안정적으로 유지하는 데 필요한 조직적 범위 전반에 걸쳐 영향을 미친다.

2024년 O’Reilly 조사에 따르면, 기업의 61% 마이크로서비스를 도입했으나, 그중 29% 복잡성을 주된 이유로 다시 모놀리식 구조로 회귀한 것으로 나타났다. 이 높은 회귀율은 반복적으로 나타나는 실패 패턴을 시사한다. 즉, 조직들이 이 아키텍처가 실제로 무엇을 요구하는지 이해하기도 전에 마이크로서비스를 도입한다는 것이다.

본 가이드는 각 아키텍처가 무엇인지, 구조적으로 어떻게 다른지, 그리고 각각의 선택이 타당한 조건이 무엇인지를 다룬다.

모놀리식 아키텍처 (Monolithic Architecture) 

모놀리식은 단일 배포 단위다. 프레젠테이션 계층, 비즈니스 로직, 데이터 접근 계층이 하나의 코드베이스, 하나의 빌드 프로세스, 하나의 데이터베이스를 공유한다. 배포 시 하나의 아티팩트가 생성된다.

이러한 단순성은 진정한 엔지니어링 속성이다. 모놀리식은 로컬에서 실행하기 쉽고, 엔드-투-엔드 테스트가 용이하며, 장애 발생 시 추적이 간단하다. 모든 컴포넌트 간 호출은 인-프로세스 함수 호출 방식으로 이루어지므로, 계층 간 네트워크 오버헤드가 전혀 없다.

대부분의 개발자가 처음 접하는 두 프레임워크인 Spring Boot와 ASP.NET MVC는 기본적으로 모놀리식을 구축한다.

모놀리식의 성능 저하 지점

모놀리식은 시스템이 성장함에 따라 개발 속도, 배포 위험, 확장 정밀도 등 세 가지 축에서 성능이 저하된다.

개발 속도 측면에서, 대규모 공유 코드베이스는 협업 마찰을 유발한다. 40명의 엔지니어가 하나의 레포지토리에 커밋할 경우, 병합 충돌이 빈번해지고, CI 큐가 길어지며, 단일 변경의 영향 범위를 가늠하기 어려워진다. 결제 계산을 수정하는 개발자는 주문 내역, 사용자 프로필, 추천 엔진에 미치는 영향까지 검증해야 한다. 이 모두가 동일한 코드베이스를, 그리고 종종 동일한 데이터베이스 스키마를 공유하기 때문이다.

배포 위험 측면에서, 어떤 변경이든 전체 애플리케이션을 재배포해야 한다. 이메일 알림 모듈의 한 줄짜리 버그 수정이라도 전체 빌드, 전체 테스트 스위트, 전체 배포 사이클을 거쳐야 한다. 소규모 변경 사항들은 개별적으로 배포되지 않고 일괄 처리되어, 릴리즈당 변경 표면이 늘어난다.

확장 정밀도 측면에서, 모놀리식은 하나의 단위로 확장된다. 검색 기능이 CPU 부하의 90%를 차지하더라도, 올바른 대응은 전체 애플리케이션을 확장하는 것이다. 즉, 검색 엔진과 함께 결제 모듈, 사용자 인증, 관리자 패널의 복제 인스턴스까지 실행해야 하며, 이 컴포넌트들이 유휴 상태일 때도 마찬가지다.

마이크로서비스 아키텍처(MSA) 무엇인가?

마이크로서비스-아키텍처MSA

마이크로서비스 아키텍처(MSA)는 애플리케이션을 소규모의 느슨하게 결합된, 독립적으로 배포 가능한 서비스들의 집합으로 구조화하는 아키텍처 스타일이다. 각 서비스는 특정 비즈니스 기능을 담당하고, 경량 프로토콜(예: HTTP/REST)을 통해 통신하며, 자체 데이터베이스를 관리한다. MSA는 복잡한 애플리케이션의 신속하고 빈번하며 안정적인 소프트웨어 전달을 가능하게 하며, 모듈형 소프트웨어 또는 서비스 지향 소프트웨어라고도 불린다.

마이크로서비스로 구축된 이커머스 플랫폼은 상품 서비스, 주문 서비스, 사용자 서비스, 알림 서비스를 각각 독립적인 프로세스로 운영하며, 각 서비스는 자체 데이터베이스와 자체 배포 파이프라인을 보유한다.

모놀리식과의 구조적 차이는 배포 독립성과 장애 격리에 있다. 주문 서비스의 결함이 상품 서비스를 중단시키지 않는다. 알림 서비스의 배포는 나머지 플랫폼과의 조율된 릴리즈 없이도 이루어진다.

Netflix 2009 전환이 대표적인 사례다. 기존 모놀리식으로는 감당할 수 없는 인프라 수요에 직면한 Netflix는 애플리케이션을 독립적으로 배포 가능한 서비스들로 분리하고 퍼블릭 클라우드로 이전했다. 이 전환을 통해 하루에도 수천 건의 코드 배포를 처리할 수 있는 고가용성 시스템을 구축할 수 있었다. 현재 Netflix는 500 이상의 독립 서비스를 운영하고 있다.

마이크로서비스 아키텍처의 핵심 구성 요소

서비스 통신

서비스 간 데이터 교환 방식을 규율하는 세 가지 주요 패턴이 있다.

동기식 HTTP/gRPC 호출은 계속 진행하기 전에 즉각적인 응답이 필요한 작업에 적합하다. 결제 흐름에서 현재 재고를 확인해야 할 때, 상품 서비스를 호출하고 응답을 기다린다. gRPC는 바이너리 직렬화와 멀티플렉스 연결을 통해 지연 시간 오버헤드를 줄여, 고빈도 내부 호출에서 REST보다 선호된다.

비동기식 메시지 전달은 호출자가 응답을 기다리지 않고 계속 진행할 수 있는 작업에 적합하다. 주문 확인 후 이메일 알림이 발송되지만, 결제 프로세스가 이메일 발송을 기다릴 필요는 없다. 주문 서비스는 브로커에 메시지를 게시하고, 알림 서비스는 이를 독립적으로 처리한다. 알림 서비스가 일시적으로 사용 불가한 경우, 메시지는 큐에 누적되어 복구 후 처리된다.

마이크로서비스-아키텍처MSA

주요 메시지 브로커 구현:

  • RabbitMQ: 확인(acknowledgment) 보장을 갖춘 신뢰성 높고 유연한 라우팅
  • Apache Kafka: 고처리량, 순서 보장, 내구성 있는 이벤트 스트리밍
  • AWS SQS: 클라우드 네이티브 환경을 위한 관리형 큐잉

서비스 메시는 인프라 계층에서 통신을 처리한다. 각 서비스가 자체적으로 재시도 로직, 서킷 브레이킹, 상호 TLS를 구현하는 대신, Istio나 Linkerd와 같은 메시가 서비스 간 트래픽을 가로채어 이러한 정책을 투명하게 적용한다.

API 게이트웨이

외부 클라이언트(브라우저, 모바일 애플리케이션, 서드파티 통합)는 개별 마이크로서비스를 직접 호출해서는 안 된다. 클라이언트-서비스 간 직접 연결 구조는 내부 아키텍처를 노출시키고, 인증 및 속도 제한 같은 공통 관심사를 모든 서비스에 분산시킨다.

API 게이트웨이는 모든 외부 트래픽의 단일 진입점 역할을 한다. 게이트웨이는:

  • 수신 요청을 적절한 서비스로 라우팅
  • 요청이 내부 서비스에 도달하기 전에 인증 적용
  • 속도 제한 적용
  • 여러 서비스의 응답을 단일 클라이언트 페이로드로 집계

게이트웨이 없이는 각 서비스 팀이 인증, 로깅, 속도 제한을 독립적으로 구현하게 되어, 시스템 전반에 걸쳐 구현 방식의 불일치와 일관성 저하가 발생한다. 주요 게이트웨이 구현으로는 Kong, AWS API Gateway, NGINX 등이 있다.

서비스 디스커버리

모놀리식에서는 컴포넌트들이 인-프로세스 참조를 통해 서로를 찾는다. 마이크로서비스 환경에서는 서비스들이 동적으로 할당된 호스트 위에 별도 프로세스로 실행되며, 종종 오케스트레이터가 언제든 생성, 소멸, 재배치할 수 있는 컨테이너 내에서 동작한다. 정적 IP 구성은 주소가 지속적으로 변경되기 때문에 적합하지 않다.

서비스 디스커버리는 서비스 레지스트리를 통해 이 문제를 해결한다. 서비스 레지스트리는 각 서비스 인스턴스의 위치를 추적하는 동적 디렉터리다. 서비스가 시작되면 자신의 주소를 등록하고, 종료될 때 등록을 해제한다.

두 가지 디스커버리 패턴이 널리 사용된다.

  • 클라이언트 사이드 디스커버리: 호출 서비스가 레지스트리에 직접 질의하여 사용 가능한 인스턴스를 선택하고, 자체적으로 로드 밸런싱을 관리
  • 서버 사이드 디스커버리: 모든 호출이 로드 밸런서나 게이트웨이를 통해 라우팅되며, 해당 컴포넌트가 호출 서비스를 대신하여 레지스트리에 질의함으로써 디스커버리를 호출 서비스로부터 투명하게 처리

Consul, Eureka, etcd가 대부분의 운영 환경에서 레지스트리로 사용된다. Kubernetes는 자체 DNS 시스템을 통해 서비스 디스커버리를 기본 제공한다.

컨테이너 오케스트레이션

마이크로서비스와 컨테이너는 아키텍처적으로 독립된 개념이지만, 실제로는 함께 사용된다. 컨테이너는 서비스와 런타임 의존성을 이식 가능한 격리 단위로 패키징하여, 개발, 스테이징, 운영 환경에서 동일하게 동작한다.

Docker가 주요 컨테이너 런타임이다. Kubernetes는 대규모로 컨테이너를 오케스트레이션하며, 다음을 처리한다:

  • 서비스 배포 및 롤링 업데이트
  • 부하에 따른 수평적 확장
  • 자동 재시작을 포함한 상태 모니터링
  • 구성 가능한 롤백 조건

오케스트레이션 계층 없이는 다수의 호스트에 걸친 수십 개의 독립 서비스 운영이 관리 불가능한 조율 문제로 변한다.

관찰 가능성(Observability)

모놀리식은 하나의 프로세스에서 하나의 로그 스트림을 생성한다. 마이크로서비스 시스템은 모든 서비스에서 로그 스트림을 생성하며, 단일 사용자 요청이 완료되기까지 다섯 개의 서비스를 거칠 수 있다. 장애는 그 중 어느 서비스에서도 발생할 수 있다.

분산 시스템에서의 관찰 가능성은 세 가지 계측 계층을 필요로 한다.

중앙화된 로깅은 모든 서비스의 로그 출력을 질의 가능한 저장소에 집계한다. ELK 스택(Elasticsearch, Logstash, Kibana)과 Grafana Loki가 가장 널리 배포된 솔루션이다.

메트릭 수집은 모든 서비스에 걸쳐 요청률, 오류율, 지연 시간 백분위수, 자원 사용률을 지속적으로 기록한다. Prometheus가 메트릭을 수집하고, Grafana가 이를 대시보드와 알림으로 렌더링한다.

분산 추적은 단일 요청이 여러 서비스를 거치는 과정을 추적하며, 각 단계에서 타이밍과 결과를 기록한다. Jaeger와 Zipkin이 추적 수집 계층을 구현한다. OpenTelemetry는 서비스가 트레이스 데이터를 출력하도록 계측하는 표준 SDK가 되었다.

관찰 가능성은 운영 중인 마이크로서비스에서 필수적이다. 아키텍처가 복잡성을 프로세스에 걸쳐 분산시키기 때문에, 관찰 가능성은 그 복잡성을 파악 가능하게 만들어준다.

마이크로서비스의 장점

마이크로서비스-아키텍처MSA

독립적 배포 

각 서비스는 자체 릴리즈 사이클을 가진다. 팀은 주문 서비스나 상품 서비스 팀과 조율 없이도 알림 서비스의 변경 사항을 배포할 수 있다. 대규모에서 이것은 배포 빈도의 주요 동인으로, 서비스별로 하루에 여러 번 운영 환경에 배포하는 것을 가능하게 한다.

목표화된 확장

불균형적인 트래픽을 받는 서비스는 독립적으로 확장된다. 유휴 서비스에 할당된 자원은 일정하게 유지된다. 기능 도메인에 걸쳐 불균등한 부하 분산을 가진 시스템의 경우, 이는 전체 모놀리식을 확장하는 것에 비해 인프라 비용을 절감한다.

장애 격리

서비스 장애는 공통 의존성을 공유하지 않는 한 관련 없는 서비스로 전파되지 않는다. YouTube의 추천 서비스에 장애가 발생하더라도, 로그아웃된 사용자는 공개 동영상에 계속 접근할 수 있다.

기술 이질성

각 서비스는 도메인 요구사항에 따라 자체 언어, 프레임워크, 데이터 저장소를 선택한다. 서비스들은 공유 런타임이 아닌 정의된 인터페이스를 통해 통신한다.

마이크로서비스의 비용 위험

마이크로서비스-아키텍처MSA

분산 시스템의 복잡성

네트워크 호출은 실패한다. 서비스들이 일시적으로 사용 불가해진다. 메시지가 지연되거나 중복 전달될 수 있다. 모든 서비스 간 호출은 모놀리식에는 존재하지 않는 오류 처리를 필요로 한다: 재시도, 타임아웃, 서킷 브레이커, 그리고 우아한 성능 저하.

데이터 일관성

모놀리식은 원자적 커밋 의미론을 갖는 단일 데이터베이스 트랜잭션으로 여러 작업을 묶을 수 있다. 해당 작업들이 별도의 데이터베이스를 가진 서비스에 걸쳐 있을 경우, 원자적 트랜잭션이 불가능하다. 분산 트랜잭션은 서비스에 걸쳐 로컬 트랜잭션과 보상 작업의 시퀀스를 조율하는 SAGA와 같은 패턴을 필요로 한다.

운영 오버헤드

열 개의 서비스를 운영하려면 열 개의 CI/CD 파이프라인, 열 개의 배포 구성, 열 개의 모니터링 설정이 필요하다. 마이크로서비스를 도입하기 전에 DevOps 인프라 구축을 건너뛴 팀은 이를 관리할 도구 없이 운영 비용을 부담하게 된다.

디버깅 복잡성

하나의 서비스에서 나타나는 장애가 다른 서비스에서 시작되었을 수 있다. 분산 추적이 처음부터 내장되지 않으면, 운영 환경에서 근본 원인을 파악하는 것이 여러 서비스에 걸친 수 시간의 로그 검토로 이어질 수 있다.

모듈형 모놀리식 (Modular Monolith) 

결합된 모놀리식과 완전히 분산된 마이크로서비스 시스템 사이에서, 모듈형 모놀리식은 명시적인 고려를 받을 만하다.

마이크로서비스와 달리, 전체 시스템은 단일 아티팩트(하나의 서버 또는 프로세스)로 패키징되어 배포된다. 모듈은 도메인 주도 설계(DDD) 원칙을 활용하여 비즈니스 기능(예: “주문” 또는 “결제”)에 따라 분리된다.

모듈형 모놀리식은 단일 단위로 배포되지만 엄격한 내부 모듈 경계를 적용한다. 각 모듈은 자체 데이터를 소유하고, 명시적인 인터페이스를 노출하며, 인접 모듈의 내부에 직접 접근하지 않는다. 모놀리식의 배포 및 운영 단순성을 유지하면서도, 내부 구조가 도메인 분리를 강제한다.

실질적인 가치는 이것이 만들어내는 마이그레이션 경로에 있다. 모듈의 확장 요구사항이 독립 서비스로의 추출을 정당화할 때, 이미 경계가 존재한다. 작업은 대규모 코드베이스에서 암묵적 의존성을 풀어내는 것이 아니라, 잘 정의된 컴포넌트를 추출하는 것이다. 결합된 모놀리식에서 직접 마이크로서비스로 전환하는 팀들은 일반적으로 마이그레이션 전이 아니라 도중에 그러한 암묵적 의존성을 발견한다.

마이크로서비스 또는 모놀리식 아키텍처 선택 기준

마이크로서비스는 다음 조건 중 최소 두 가지가 충족될 때 적합하다.

  • 전체 애플리케이션의 확장으로는 효율적으로 처리할 수 없는, 의미 있게 다른 확장 요구사항을 가진 별개의 기능 도메인이 존재할 때
  • 팀 규모가 경계 있는 서비스 소유권으로 해소될 수 있는 조정 마찰을 유발할 때 (세 팀 이상이 단일 배포 단위를 공유할 때가 대략적인 기준)
  • 릴리즈 빈도가 개발 역량이 아닌 공유 배포 위험으로 인해 제한될 때
  • 특정 도메인이 기존 스택으로는 충족할 수 없는 기술 요구사항을 가질 때

모놀리식 또는 모듈형 모놀리식은 다음 경우에 적합하다.

  • 제품이 초기 개발 단계이며 도메인 경계가 아직 파악 중일 때
  • 팀 규모가 충분히 작아 공유 코드베이스가 최소한의 조정 비용만 유발할 때
  • 다른 기능 도메인과 실질적으로 다른 확장 요구사항을 가진 기능 도메인이 없을 때
  • 조직이 여러 독립 서비스를 안정적으로 운영할 DevOps 인프라가 부족할 때

 

모놀리식에서 마이크로서비스로의 마이그레이션

마이그레이션은 점진적으로 이루어져야 한다. 운영 중인 시스템의 전면 재작성은 거의 성공하지 못하는데, 새로운 시스템이 오래된 시스템이 수년에 걸쳐 축적한 운영 성숙도를 갖추는 동안 두 시스템을 동시에 운영해야 하기 때문이다.

스트랭글러 피그(Strangler Fig) 패턴이 표준적인 점진적 접근 방식이다. 기존 모놀리식 앞에 프록시 또는 API 게이트웨이를 배치한다. 개별 모듈을 하나씩 독립 서비스로 추출하고, 트래픽이 모놀리식의 코드 경로에서 새 서비스로 점진적으로 전환된다. 추출된 서비스에서 회귀가 발생하면, 트래픽은 모놀리식의 구현으로 다시 라우팅된다.

데이터 마이그레이션이 가장 어려운 부분이다. 두 모듈이 데이터베이스 스키마를 공유하는 경우, 하나를 추출하려면 해당 데이터를 분리하고 명확한 소유권을 확립해야 한다. 전환 기간 동안 데이터는 기존 스키마와 새 서비스의 데이터베이스 간에 동기화가 필요할 수 있다. 추출을 시작하기 전에 스키마 의존성을 파악하면 비용을 크게 줄일 수 있다.

위험을 최소화하는 순서는 다음과 같다.

  • 가장 명확한 경계와 가장 낮은 결합도를 가진 모듈을 먼저 추출
  • 첫 번째 추출이 운영 환경에 배포되기 전에 관찰 가능성 인프라 구축
  • 팀이 안정적으로 운영할 수 있는 것만 추출 — 대부분의 시스템에서 대부분의 모듈은 모놀리식에 남아 있음

 

자주 묻는 질문(FAQ)

마이크로서비스는 어떻게 서로 통신하는가?

두 가지 주요 방식이 있다. 즉각적인 응답이 필요할 때는 REST 또는 gRPC를 통한 동기식 호출. 호출자가 응답을 기다리지 않고 계속 진행할 수 있을 때는 메시지 브로커(RabbitMQ, Kafka, AWS SQS)를 통한 비동기식 메시지 전달.

마이크로서비스에 Docker Kubernetes 반드시 필요한가?

정의상 필수는 아니지만, 실제로는 그렇다. Docker는 각 서비스를 이식 가능한 격리된 컨테이너로 패키징한다. Kubernetes는 대규모로 해당 컨테이너들을 오케스트레이션하며, 배포, 확장, 상태 모니터링을 처리한다. 오케스트레이션 없이는 많은 독립 서비스를 관리하는 것이 불가능해진다.

마이크로서비스에서 가장 어려운 부분은 무엇인가?

데이터 일관성. 모놀리식은 여러 작업을 단일 원자적 데이터베이스 트랜잭션으로 처리한다. 작업들이 별도의 데이터베이스를 가진 서비스에 걸쳐 있을 경우, 그 보장이 사라진다. 분산 트랜잭션은 상당한 복잡성을 추가하는 SAGA와 같은 패턴을 필요로 한다.

기업들이 마이크로서비스에서 모놀리식으로 돌아가는 이유는 무엇인가?

운영 복잡성. 수십 개의 서비스를 관리하려면 성숙한 DevOps 인프라, 분산 추적, 중앙화된 로깅, 그리고 상당한 엔지니어링 오버헤드가 필요하다. 해당 기반을 구축하기 전에 마이크로서비스를 도입한 팀은 제품 개발보다 인프라 관리에 더 많은 시간을 소비하게 된다.

마이크로서비스에서 관찰 가능성이란 무엇인가?

분산 시스템을 파악 가능하게 만드는 중앙화된 로깅, 메트릭 수집, 분산 추적의 조합. 단일 요청이 다섯 개의 서비스를 거칠 수 있다. 관찰 가능성 없이는 요청이 어디서 실패했는지 파악하는 것이 여러 시스템에 걸친 수 시간의 수동 로그 검토로 이어진다.

서로 다른 마이크로서비스가 서로 다른 프로그래밍 언어를 사용할 있는가?

그렇다. 각 서비스는 도메인 요구사항에 따라 자체 언어, 프레임워크, 데이터베이스를 선택한다. 서비스들은 공유 런타임이 아닌 정의된 인터페이스를 통해 통신한다. 이것은 마이크로서비스의 진정한 장점 중 하나이지만, 팀들은 불필요한 복잡성을 피하기 위해 가능한 한 표준화해야 한다.

SAGA 패턴이란 무엇인가?

여러 서비스에 걸친 분산 트랜잭션을 관리하기 위한 패턴. 하나의 원자적 데이터베이스 트랜잭션 대신, SAGA는 로컬 트랜잭션의 시퀀스를 조율한다. 한 단계가 실패하면, 보상 트랜잭션이 선행 단계들을 롤백한다. 이는 모놀리식이 자동으로 처리하는 것을 명시적인 조율 로직으로 대체한다.

서비스 메시(Service Mesh) 무엇인가?

라우팅, 재시도, 서킷 브레이킹, 로드 밸런싱, 상호 TLS를 포함한 모든 서비스 간 통신을 처리하는 인프라 계층. 각 서비스가 이 로직을 독립적으로 구현하는 대신, Istio나 Linkerd와 같은 메시가 이를 투명하게 적용한다. 서비스 수가 많아져 서비스별 네트워킹 코드가 유지보수 부담이 될 때 실용적이다.

관련 게시물

Your Growth, Our Commitment

HBLAB operates with a customer-centric approach,
focusing on continuous improvement to deliver the best solutions.

위로 스크롤