공부

MSA란? 채용 공고에서 자주 보이는 마이크로서비스 아키텍처 이해하기

반응형
MSA란? 채용 공고에서 자주 보이는 마이크로서비스 아키텍처 이해하기

Architecture Note

MSA란? 채용 공고에서 자주 보이는 마이크로서비스 아키텍처 이해하기

구인 공고를 보다 보면 “MSA 기반 서비스 개발 경험”, “마이크로서비스 운영 경험”, “Spring Boot MSA 개발자 우대” 같은 문구를 자주 마주치게 된다. 처음에는 단순히 백엔드 개발에서 자주 쓰는 유행어처럼 보였지만, 조금 더 들여다보면 MSA는 프로젝트를 여러 개로 나누는 정도의 이야기가 아니다. 서비스를 어떻게 나누고, 어떻게 배포하고, 장애가 났을 때 어떻게 버티게 만들 것인가에 대한 아키텍처 선택이다.

왜 MSA를 알아보게 됐나

개발자 채용 공고를 보면 기술 스택에 Java, Spring Boot, JPA, Docker, Kubernetes 같은 단어와 함께 MSA가 붙어 있는 경우가 많다. “MSA 환경에서의 개발 경험”, “MSA 기반 대용량 서비스 운영”, “마이크로서비스 간 통신 설계 경험” 같은 문장도 자주 등장한다.

이런 문구를 보면 자연스럽게 질문이 생긴다. MSA가 단순히 Spring Boot 프로젝트를 여러 개 만드는 것인지, 아니면 더 깊은 운영 경험을 의미하는 것인지 말이다. 특히 “MSA 경험자 우대”라는 말은 단순 CRUD 개발만 해봤는지를 묻는 것이 아니라, 서비스가 나뉘었을 때 생기는 배포, 통신, 장애, 데이터 정합성 문제를 다뤄본 적이 있는지를 묻는 경우가 많다.

핵심 관점: 채용 공고에서 말하는 MSA 경험은 “서비스를 잘게 나눈 경험”보다 “나뉜 서비스들을 안정적으로 운영해본 경험”에 가깝다.

MSA의 핵심 정의

MSA는 Microservices Architecture, 즉 마이크로서비스 아키텍처의 줄임말이다. 큰 애플리케이션을 여러 개의 작은 서비스로 나누고, 각 서비스가 독립적으로 개발, 배포, 확장, 운영될 수 있게 만드는 구조를 말한다.

예를 들어 쇼핑몰 시스템을 생각해보면 회원, 상품, 주문, 결제, 배송, 알림, 리뷰 같은 기능이 있다. 모놀리스에서는 이 기능들이 하나의 애플리케이션 안에 들어가지만, MSA에서는 각 기능이 별도의 서비스가 될 수 있다.

user-service
product-service
order-service
payment-service
delivery-service
notification-service

중요한 점은 서비스 이름을 여러 개 붙였다고 MSA가 되는 것이 아니라는 점이다. 각 서비스가 따로 실행되고, 따로 배포될 수 있고, 자기 책임을 명확히 가져야 MSA에 가까워진다. 그래서 MSA를 한 문장으로 정리하면 다음과 같다.

MSA는 큰 시스템을 비즈니스 기능 단위의 독립 서비스로 나누고, 각 서비스를 독립적으로 개발·배포·확장·운영하는 아키텍처다.

모놀리스와 무엇이 다른가

MSA를 이해하려면 먼저 모놀리스를 알아야 한다. 모놀리스는 여러 기능이 하나의 애플리케이션 안에 들어 있는 구조다. Spring Boot로 만든 쇼핑몰 백엔드 하나에 회원, 상품, 주문, 결제 패키지가 모두 있고 하나의 jar 파일로 빌드되어 배포된다면 모놀리스라고 볼 수 있다.

모놀리스는 나쁜 구조가 아니다. 오히려 작은 팀, 초기 서비스, MVP 단계에서는 모놀리스가 더 빠르고 단순하다. 내부 Java 메서드 호출로 기능들이 연결되기 때문에 네트워크 장애를 고민할 필요가 적고, 하나의 DB 트랜잭션으로 여러 작업을 묶기도 쉽다.

모놀리스와 MSA 구조 비교 다이어그램
▲ 모놀리스는 하나의 배포 단위 안에 여러 기능이 모이고, MSA는 비즈니스 기능별 서비스가 독립 실행된다.

하지만 서비스가 커질수록 문제가 생긴다. 결제 기능만 수정했는데 전체 애플리케이션을 다시 배포해야 할 수 있고, 주문 트래픽만 늘었는데 전체 서버를 늘려야 할 수 있다. 여러 팀이 동시에 개발할 때 충돌도 잦아진다. MSA는 이런 문제를 줄이기 위해 기능을 독립 서비스 단위로 나누려는 접근이다.

구분모놀리스MSA
배포 단위하나의 애플리케이션서비스별 독립 애플리케이션
호출 방식내부 메서드 호출HTTP, gRPC, 메시지 이벤트
확장 방식전체 애플리케이션 확장필요한 서비스만 확장
트랜잭션상대적으로 단순서비스 간 정합성 설계 필요
운영 난이도비교적 낮음상대적으로 높음

예시 소스 구조로 보는 차이

MSA를 말할 때 “폴더를 나누면 MSA인가?”라는 질문이 자주 나온다. 결론부터 말하면 아니다. 폴더나 패키지를 나누는 것은 코드 정리 방식이고, MSA 여부를 가르는 더 강한 기준은 실행 단위와 배포 단위다.

아래 예시는 그대로 복사해서 비교용 메모나 발표 자료에 붙여 넣기 좋은 형태로 정리했다. 첫 번째는 모놀리스 구조이고, 두 번째는 MSA 구조다.

모놀리스 예시 구조

하나의 Spring Boot 애플리케이션 안에 여러 도메인 패키지가 들어 있다. 패키지는 분리되어 있지만 최종적으로는 하나의 shop-api.jar로 빌드되고 배포된다.

shop-api
├─ build.gradle
├─ src/main/java/com/example/shop
│  ├─ ShopApplication.java
│  ├─ user
│  │  ├─ UserController.java
│  │  ├─ UserService.java
│  │  └─ UserRepository.java
│  ├─ product
│  │  ├─ ProductController.java
│  │  ├─ ProductService.java
│  │  └─ ProductRepository.java
│  ├─ order
│  │  ├─ OrderController.java
│  │  ├─ OrderService.java
│  │  └─ OrderRepository.java
│  └─ payment
│     ├─ PaymentController.java
│     ├─ PaymentService.java
│     └─ PaymentRepository.java
└─ src/main/resources
   └─ application.yml

이 구조에서 주문 기능이 결제 기능을 호출하면 보통 같은 애플리케이션 내부의 Java 메서드를 호출한다.

@Service
public class OrderService {

    private final PaymentService paymentService;

    @Transactional
    public Long createOrder(OrderRequest request) {
        Order order = orderRepository.save(Order.create(request));
        paymentService.pay(order.getId(), request.amount());
        return order.getId();
    }
}

같은 JVM 안에서 호출되므로 구조가 단순하고 트랜잭션 처리도 비교적 쉽다. 대신 결제 기능만 수정해도 전체 애플리케이션을 다시 배포해야 할 수 있다.

MSA 예시 구조

MSA에서는 각 도메인이 별도의 애플리케이션으로 분리된다. 아래처럼 각각의 서비스가 자기 프로젝트, 자기 설정, 자기 배포 단위를 가진다.

shop-msa
├─ user-service
│  ├─ build.gradle
│  └─ src/main/java/com/example/user
│     ├─ UserServiceApplication.java
│     └─ user
├─ product-service
│  ├─ build.gradle
│  └─ src/main/java/com/example/product
│     ├─ ProductServiceApplication.java
│     └─ product
├─ order-service
│  ├─ build.gradle
│  └─ src/main/java/com/example/order
│     ├─ OrderServiceApplication.java
│     ├─ order
│     └─ client
│        └─ PaymentClient.java
├─ payment-service
│  ├─ build.gradle
│  └─ src/main/java/com/example/payment
│     ├─ PaymentServiceApplication.java
│     └─ payment
└─ docker-compose.yml

이제 주문 서비스는 결제 서비스의 Java 클래스를 직접 호출하지 않는다. 대신 HTTP API나 메시지 이벤트를 통해 요청한다.

@Component
public class PaymentClient {

    private final WebClient webClient;

    public PaymentClient(WebClient.Builder builder) {
        this.webClient = builder
            .baseUrl("http://payment-service:8080")
            .build();
    }

    public PaymentResponse requestPayment(PaymentRequest request) {
        return webClient.post()
            .uri("/payments")
            .bodyValue(request)
            .retrieve()
            .bodyToMono(PaymentResponse.class)
            .block();
    }
}

로컬에서 여러 서비스를 띄우는 모습은 다음처럼 볼 수 있다. 실제 실무에서는 Docker Compose나 Kubernetes 같은 도구를 사용해 각 서비스를 실행하고 연결한다.

services:
  order-service:
    image: shop/order-service:latest
    ports:
      - "8081:8080"

  payment-service:
    image: shop/payment-service:latest
    ports:
      - "8082:8080"

  delivery-service:
    image: shop/delivery-service:latest
    ports:
      - "8083:8080"
MSA 요청 흐름을 보여주는 애니메이션 GIF
▲ MSA에서는 하나의 사용자 요청이 주문, 결제, 배송 같은 여러 독립 서비스를 거쳐 처리될 수 있다. GIF가 티스토리에서 보이지 않으면 GIF 파일을 이미지 업로드로 별도 등록하는 방식이 더 안정적이다.

정리: 모놀리스는 하나의 애플리케이션 내부에서 도메인 패키지가 나뉘는 구조이고, MSA는 애플리케이션 자체가 서비스별로 나뉘는 구조다. 그래서 MSA에서는 서비스 간 호출이 내부 메서드 호출이 아니라 네트워크 요청이나 메시지 이벤트가 된다.

왜 네트워크 통신이 문제가 되는가

MSA를 처음 배울 때 가장 헷갈리는 부분이 있다. “jar 파일로 배포해서 서비스 호출하는 것인데, 왜 네트워크 통신이라고 하지?”라는 질문이다. 여기서 jar 파일은 실행 파일이고, 서비스 간 호출 방식은 별도의 문제다.

모놀리스에서는 OrderServicePaymentService를 직접 호출할 수 있다. 같은 JVM 안에 있기 때문이다. 하지만 MSA에서는 order-service.jarpayment-service.jar가 각각 별도 프로세스로 실행된다. 주문 서비스가 결제 서비스를 호출하려면 API 요청을 보내거나 메시지를 발행해야 한다.

jar 파일과 네트워크 통신 차이 설명 다이어그램
▲ jar 파일은 배포·실행 단위이고, 서비스 간 호출은 HTTP/gRPC/메시지 같은 네트워크 통신으로 일어난다.

같은 물리 서버 안에서 localhost:8082localhost:8083을 호출하더라도 네트워크 통신이다. 이때부터 단순 메서드 호출과 다른 문제가 생긴다. 상대 서비스가 죽어 있을 수 있고, 응답이 늦을 수 있고, 요청은 성공했지만 응답만 실패할 수도 있다. 같은 요청이 두 번 전달될 가능성도 있다.

그래서 MSA에서는 timeout, retry, circuit breaker, fallback, idempotency 같은 개념이 중요해진다. 서비스를 나누는 순간 시스템의 복잡도는 코드 내부에서 네트워크와 운영 영역으로 이동한다.

진짜 MSA와 분산 모놀리스

프로젝트에 서비스가 여러 개 있다고 해서 무조건 MSA라고 부르기는 어렵다. 가장 중요한 기준은 독립 배포 가능 여부다. 주문 기능만 바꿨을 때 주문 서비스만 배포할 수 있는가? 결제 서비스 장애가 전체 주문 시스템을 바로 멈추게 하지 않는가? 각 서비스가 자기 데이터를 책임지는가? 이런 질문에 답할 수 있어야 한다.

진짜 MSA와 분산 모놀리스 판단 기준 다이어그램
▲ 서비스가 여러 개라는 사실보다 독립 배포, 데이터 소유권, 장애 격리 가능 여부가 더 중요하다.

겉으로는 user-service, order-service, payment-service처럼 나뉘어 있지만 실제로는 항상 같이 배포해야 하고, 하나의 DB를 여러 서비스가 공유하고, 하나의 변경이 여러 서비스 수정으로 이어진다면 분산 모놀리스에 가깝다.

분산 모놀리스의 위험: 모놀리스의 단순함은 잃고, MSA의 운영 복잡도만 가져오는 구조가 될 수 있다. 그래서 서비스 분리는 기술 기준보다 비즈니스 책임 기준으로 접근해야 한다.

실무에서는 처음부터 완벽한 MSA를 만들기보다 모듈화된 모놀리스로 시작한 뒤, 트래픽과 팀 구조가 커지고 독립 배포의 필요성이 분명해졌을 때 서비스 분리를 검토하는 경우도 많다. MSA는 목표가 아니라 선택지다.

MSA 장단점

MSA의 장점은 독립성에서 나온다. 특정 서비스만 배포할 수 있고, 트래픽이 많은 서비스만 확장할 수 있으며, 팀별로 책임을 나누기 좋다. 예를 들어 알림 서비스에 장애가 나도 주문 접수는 계속 받을 수 있게 설계할 수 있다.

하지만 단점도 같은 지점에서 나온다. 서비스가 많아지는 만큼 배포 대상, 로그, 모니터링, API 계약, 장애 대응, 데이터 정합성 관리가 늘어난다. 모놀리스에서는 코드 한 줄로 호출하던 일을 MSA에서는 네트워크 요청으로 처리해야 한다.

장점

독립 배포, 독립 확장, 장애 격리, 팀별 책임 분리, 기술 선택 유연성을 얻을 수 있다.

단점

운영 복잡도, 네트워크 장애, 데이터 정합성, 초기 인프라 비용, 분산 모놀리스 위험이 커진다.

판단 질문의미
서비스 하나만 수정해서 하나만 배포할 수 있는가?독립 배포 가능성 확인
서비스별 데이터 소유권이 명확한가?DB 직접 공유로 인한 결합 방지
장애가 났을 때 영향 범위를 제한할 수 있는가?장애 격리 가능성 확인
로그와 트레이스로 요청 흐름을 추적할 수 있는가?운영 준비도 확인
서비스 간 API 계약이 문서화되어 있는가?팀 간 개발 안정성 확인

보상 트랜잭션과 Saga

MSA에서 특히 중요한 문제가 데이터 정합성이다. 모놀리스에서는 주문 생성, 재고 차감, 결제 승인을 하나의 DB 트랜잭션으로 묶을 수 있다. 중간에 실패하면 rollback 하면 된다.

하지만 MSA에서는 주문 서비스, 재고 서비스, 결제 서비스가 각각 자기 DB를 가질 수 있다. 이때 하나의 @Transactional로 모든 작업을 묶기 어렵다. 그래서 긴 비즈니스 흐름을 여러 개의 로컬 트랜잭션으로 나누고, 실패하면 앞에서 성공한 작업을 반대 작업으로 수습하는 방식을 사용한다. 이것이 Saga Pattern과 보상 트랜잭션의 핵심이다.

Saga 보상 트랜잭션 흐름 다이어그램
▲ 보상 트랜잭션은 이미 성공한 작업을 DB rollback처럼 지우는 것이 아니라 반대 비즈니스 작업으로 수습하는 방식이다.

예를 들어 주문 생성, 재고 차감, 결제 승인까지 성공했는데 배송 요청이 실패했다고 하자. 이때 이미 성공한 결제를 취소하고, 차감한 재고를 복구하고, 주문 상태를 취소로 바꾸는 작업이 필요하다. 이것은 DB rollback이 아니라 비즈니스적으로 되돌리는 작업이다.

결제 승인 후 취소는 단순히 DB row를 삭제하는 문제가 아니다. 실제 PG사에 결제 취소 API를 호출해야 한다. 그래서 보상 트랜잭션은 기술적인 트랜잭션 처리뿐 아니라 비즈니스 상태 설계와도 깊게 연결된다.

재시도와 모니터링

MSA에서는 재시도를 무조건 하면 안 된다. 일시적인 네트워크 장애, timeout, 502, 503, 504 같은 상황은 제한적으로 재시도할 수 있다. 하지만 잘못된 요청, 권한 없음, 재고 없음, 카드 한도 초과 같은 비즈니스 실패는 재시도해도 의미가 없다.

특히 결제, 포인트 차감, 쿠폰 사용처럼 중복되면 위험한 작업은 멱등성 설계가 필요하다. 같은 요청이 두 번 들어와도 결제가 두 번 되지 않도록 idempotencyKey나 요청 식별자를 저장하고 확인해야 한다.

MSA 모니터링 3요소 다이어그램
▲ MSA 운영에서는 메트릭, 로그, 트레이스를 함께 봐야 장애 위치와 원인을 빠르게 좁힐 수 있다.

또 MSA에서는 모니터링이 필수다. 모놀리스에서는 하나의 애플리케이션 로그만 봐도 흐름을 따라가기 쉬웠지만, MSA에서는 하나의 요청이 여러 서비스를 거친다. 그래서 메트릭, 로그, 트레이스를 함께 봐야 한다.

관측 대상대표 도구보는 내용
MetricsPrometheus, Grafana요청 수, 응답 시간, 에러율, CPU, 메모리
LogsELK, Loki에러 로그, 주문번호, 처리 상태, 예외 원인
TracesOpenTelemetry, Jaeger, Tempo한 요청이 어떤 서비스를 거쳤는지와 병목 위치

결국 MSA 경험이란 단순히 서비스를 나눠본 경험이 아니라, 나뉜 서비스들이 실패할 수 있다는 전제 위에서 안정적으로 운영해본 경험에 가깝다.

실제로 MSA를 볼 때 확인할 것들

MSA를 공부할 때는 개념만 외우기보다 실제 프로젝트를 본다고 생각하고 확인 기준을 세우는 것이 좋다. 서비스가 몇 개인지보다 더 중요한 것은 그 서비스들이 서로 얼마나 독립적인지다. 이름만 여러 서비스로 나뉘어 있고 실제로는 하나처럼 움직인다면 MSA의 장점은 얻기 어렵다.

첫 번째로 볼 것은 배포 흐름이다. 특정 기능을 수정했을 때 해당 서비스만 빌드하고 배포할 수 있어야 한다. 만약 주문 서비스의 작은 수정 때문에 회원 서비스, 결제 서비스, 배송 서비스까지 함께 배포해야 한다면 서비스 경계가 강하게 얽혀 있다는 신호다.

두 번째로 볼 것은 데이터 접근 방식이다. 주문 서비스가 결제 서비스의 테이블을 직접 수정하거나, 여러 서비스가 같은 테이블을 마음대로 읽고 쓰면 데이터 소유권이 모호해진다. 이 경우 장애나 변경이 생겼을 때 영향 범위를 예측하기 어렵다. MSA에서는 각 서비스가 자기 데이터를 책임지고, 다른 서비스는 API나 이벤트를 통해 필요한 정보를 주고받는 것이 기본 방향이다.

세 번째로 볼 것은 장애 대응 방식이다. 결제 서비스가 잠시 느려졌을 때 주문 서비스가 무한정 기다리면 전체 사용자 경험이 나빠진다. 그래서 timeout을 정하고, 재시도 횟수를 제한하고, 필요하면 서킷 브레이커로 호출을 잠시 차단해야 한다. 알림처럼 핵심 흐름이 아닌 기능은 실패해도 주문 자체가 완료될 수 있도록 설계할 수 있다.

네 번째로 볼 것은 관측 가능성이다. 여러 서비스가 연결된 구조에서는 “어디서 실패했는지”를 빠르게 찾아야 한다. 로그에 주문번호나 trace id가 남아 있어야 하고, 서비스별 응답 시간과 에러율을 볼 수 있어야 한다. 단순히 서버가 살아 있는지만 보는 것으로는 부족하다.

확인 항목좋은 신호위험 신호
배포서비스별 독립 배포 가능작은 변경에도 여러 서비스 동시 배포
데이터서비스별 데이터 소유권 명확여러 서비스가 같은 테이블 직접 수정
통신API 계약과 이벤트 스키마 관리내부 구현 변경이 다른 서비스에 바로 영향
장애timeout, retry, fallback 설계상대 서비스 장애가 전체 장애로 확산
운영메트릭, 로그, 트레이스 연결장애 원인을 로그에서 찾기 어려움

처음 공부할 때 추천하는 순서

MSA를 처음 공부한다면 도구 이름부터 외우기보다 모놀리스의 장점과 한계를 먼저 이해하는 편이 좋다. 그다음 서비스가 왜 독립 배포되어야 하는지, 서비스 간 통신에서 어떤 실패가 생기는지, DB가 나뉘면 트랜잭션을 어떻게 처리해야 하는지 순서대로 보면 개념이 잘 이어진다.

이 흐름이 잡힌 뒤에 Spring Cloud Gateway, Eureka, Kafka, Kubernetes, Prometheus 같은 도구를 보면 각각이 왜 필요한지 이해하기 쉽다. 도구는 MSA를 가능하게 해주는 수단이지, 도구를 썼다고 자동으로 좋은 MSA가 되는 것은 아니다. 결국 설계 의도가 더 중요하다. 이 기준을 기억하면 방향을 잃지 않는다. 실무에서도 그렇다.

정리

MSA는 서비스를 많이 만드는 구조가 아니다. 큰 시스템을 비즈니스 책임 단위로 나누고, 각 서비스를 독립적으로 개발, 배포, 확장, 운영할 수 있게 만드는 아키텍처다.

MSA의 장점은 독립 배포와 확장성이다. 하지만 그 대가로 네트워크 통신, 데이터 정합성, 재시도, 보상 트랜잭션, 모니터링, 배포 자동화 같은 운영 복잡도가 크게 증가한다.

그래서 MSA는 “최신이니까 써야 하는 기술”이 아니라 “독립성이 필요한 규모와 조직에서 선택할 수 있는 구조”로 이해하는 것이 좋다. 작은 프로젝트나 초기 서비스에서는 모놀리스가 더 좋은 선택일 수 있고, 서비스와 팀이 커졌을 때 MSA의 가치가 더 분명해진다.

채용 공고에서 MSA 경험을 요구하는 이유도 이 지점과 연결된다. 단순히 Spring Boot API를 만들어본 사람을 찾는 것이 아니라, 서비스가 나뉘었을 때 생기는 통신 실패, 데이터 정합성, 장애 추적, 배포 자동화, 운영 모니터링을 이해하는 사람을 찾는 것이다. 따라서 MSA를 공부할 때는 “서비스를 어떻게 나눌까?”에서 멈추지 말고 “나눈 뒤 어떻게 안정적으로 운영할까?”까지 함께 봐야 한다.

최종 한 줄: MSA는 쪼개는 기술이 아니라, 쪼갠 뒤에도 각 서비스가 독립적으로 살아남게 만드는 운영 중심 아키텍처다.

FAQ

Q1. Spring Boot를 쓰면 자동으로 MSA인가요?+

아닙니다. Spring Boot는 독립 실행 가능한 애플리케이션을 만들기 편하게 해주기 때문에 MSA에 자주 쓰일 뿐입니다. 핵심은 서비스가 독립적으로 배포되고 운영될 수 있느냐입니다.

Q2. MSA는 무조건 DB를 따로 가져야 하나요?+

원칙적으로는 서비스별 데이터 소유권이 명확해야 합니다. 꼭 물리 DB가 모두 달라야 한다기보다, 다른 서비스가 내 서비스의 데이터를 직접 수정하지 않는 구조가 중요합니다.

Q3. 분산 모놀리스는 왜 위험한가요?+

서비스는 여러 개인데 항상 같이 배포하고, DB를 공유하고, 장애가 줄줄이 전파되면 모놀리스보다 운영이 더 어려워질 수 있습니다. 나눴지만 독립성이 없기 때문입니다.

Q4. 보상 트랜잭션은 rollback과 같은가요?+

다릅니다. rollback은 아직 확정되지 않은 DB 작업을 되돌리는 것이고, 보상 트랜잭션은 이미 성공한 작업을 반대 비즈니스 작업으로 수습하는 것입니다.

Q5. MSA를 언제 도입하는 것이 좋나요?+

여러 팀이 독립적으로 개발해야 하고, 특정 기능만 자주 배포하거나 확장해야 하며, 운영과 모니터링 체계가 준비되어 있을 때 도입을 검토하는 것이 좋습니다.

참고 자료

반응형

Categories