MicroService Architecture의 loosely coupled 특성과 문제점

정은영·2025년 1월 29일
2

CS

목록 보기
22/24

loosely coupled

MSA(Microservice Architecture)는 loosely coupled라는 특성을 가지고 있습니다.
MSA가 loosely coupled를 어떻게 가능하게 할까요?


서비스 간 독립성 및 독립적인 데이터 관리

각 Micro Service는 독립적인 코드베이스와 데이터베이스를 갖습니다.

서비스는 특정 비즈니스 기능을 담당하며, 다른 서비스와의 의존성을 최소화합니다. 이로 인해 하나의 서비스가 변경되더라도 다른 서비스에 영향을 미치지 않습니다.

또한, 각 서비스는 자신만의 데이터베이스를 가지며, 다른 서비스의 데이터에 직접 접근하지 않습니다.

예시를 통해 MSA와 모놀리식 아키텍처(Mobolithic Architecture) 비교하며 MSA가 어떻게 서비스 간 독립성을 확보하는지 설명하겠습니다.


예시 시나리오는 온라인 쇼핑몰 시스템입니다.

  • 주요 기능: 사용자 관리, 주문 관리, 결제 관리, 배송 관리
  • 문제 상황: 결제 로직 변경
  • 결제 시스템에서 새로운 결제 수단인 암호화폐를 추가하려고 합니다.

이 문제 상황에 대하여 MSA와 모놀리식 아키텍처로 각각 구현했을 때의 차이를 알아보겠습니다.


1) 모놀리식 아키텍처
모놀리식 아키텍처에서는 모든 기능이 하나의 큰 애플리케이션 안에 통합되어 있습니다. 사용자 관리, 주문 관리, 결제 관리, 배송 관리가 모두 같은 코드베이스와 데이터베이스를 공유합니다.

결제 로직을 변경하기 위해 결제 모듈의 코드를 수정해야 합니다. 하지만 결제 모듈이 주문 모듈, 사용자 모듈 등과 같은 코드베이스에 있기 때문에 결제 모듈을 변경하면, 주문 모듈이나 사용자 모듈에서 사용하는 결제 관련 코드에도 사이드 이펙트가 있을 수 있습니다. 따라서, 결제 모듈을 변경하려면 전체 애플리케이션을 다시 테스트하고 배포해야 합니다.

이렇게 모놀리식 아키텍처에서는 서비스 간 결합도가 높기 때문에 한 부분의 변경이 전체 시스템에 영향을 미칠 수 있습니다.

또한, 모든 모듈이 하나의 데이터베이스를 공유합니다. 결제 테이블을 변경하면 주문 모듈도 영향을 받을 수 있습니다.


2) MSA
MSA에서는 아래처럼 각 기능이 독립적인 서비스로 분리되어 있으며 각 서비스는 독립적인 코드베이스와 데이터베이스를 가지며, API를 통해 서로 통신합니다.

  • 사용자 서비스: 사용자 정보 관리
  • 주문 서비스: 주문 생성 및 관리
  • 결제 서비스: 결제 처리
  • 배송 서비스: 배송 상태 관리

결제 서비스에서 새로운 결제 수단인 암호화폐를 추가하려고 합니다. 결제 서비스는 독립적으로 개발되고 배포되기 때문에 다른 서비스는 영향을 받지 않습니다. 주문 서비스는 여전히 결제 서비스의 API를 호출하기만 하면 되고, 결제 서비스의 내부 구현이 어떻게 바뀌든 API 호출 방식이 바뀌지 않는 한, 상관 없습니다. 결제 서비스만 독립적으로 테스트하고 배포하면 됩니다.

MSA에서는 각 서비스가 API를 통해 통신하고 내부 구현을 숨기기 때문에 서비스 간 결합도가 낮습니다.

그리고 각 서비스는 자신만의 데이터베이스를 가집니다. 결제 서비스의 데이터베이스를 변경해도 주문 서비스의 데이터베이스는 영향을 받지 않습니다.

API를 통한 통신

위 예시에서 언급했듯 서비스간 통신은 주로 RESTful API, gRPC, 메시지 큐 등을 통해 이루어집니다. 서비스는 내부 구현을 노출하지 않고 API를 통해 상호작용하기 때문에 서비스 간 결합도가 모놀리식 아키텍처보다 상대적으로 낮습니다.

폴리글랏 아키텍처

폴리글랏 아키텍처(Polyglot Architecture)는 여러 프로그래밍 언어, 프레임워크, 데이터베이스, 기술 스택을 함께 사용하는 아키텍처를 말합니다. 이 아키텍처의 목표는 각 컴포넌트나 서비스가 자신에게 가장 적합한 기술을 선택할 수 있도록 하는 것이 목표인데요. MSA는 각 마이크로 서비스가 독립적으로 개발, 배포, 운영을 가능하도록 하기 때문에 폴리글랏 아키텍처를 구현하기에도 매우 적합합니다.

서비스 특성에 따라 적합한 프로그래밍 언어, 프레임워크, 데이터베이스를 선택할 수 있어 기술적 결합도를 감소시킬 수 있습니다.

독립적인 배포와 확장

각 서비스는 독립적으로 배포하고 확장할 수 있습니다. 특정 서비스의 트래픽이 증가하면 해당 서비스만 스케일 아웃할 수 있으며, 전체 시스템을 재배포할 필요가 없습니다.

컨테이너 기반 플랫폼과의 호환성

컨테이너 기반 플랫폼(Docker, Kubernates)과 MSA의 Scalability(확장성)과 Resiliency(탄력성/복원력)이라는 특성의 호환성이 좋습니다.

Scalability(확장성)
컨테이너(Docker)는 각 서비스를 독립적으로 패키징하고 실행할 수 있도록 하고, Kubernetes는 서비스의 인스턴스를 오토 스케일링으로 자동으로 확장하거나 축소할 수 있습니다. 이러한 컨테이너 플랫폼의 특성은 MSA에서 서비스 인스턴스를 여러 개 실행하여 부하를 분산하거나 특정 서비스만 확장하는 등 Scalability를 구현할 수 있는 환경을 제공합니다.

Resiliency(탄력성/복원력)
MSA는 각 서비스가 독립적으로 운영되기 때문에 한 서비스의 장애가 전체 시스템으로 확산되는 것을 방지할 수 있어 Resiliency가 좋습니다. 이를 가능하게 하는 것이 컨테이너인데요. 컨테이너는 각 서비스를 격리된 환경에서 실행하기 때문에, 한 서비스의 문제가 다른 서비스에 영향을 미치지 않습니다.또한 Kubernetes는 장애가 발생한 컨테이너를 자동으로 재시작하거나 교체할 수 있습니다.

MSA와 함께 등장한 새로운 문제점들

RESTful 통신의 제약

Restful 통신은 synchronous하게 동작합니다. synchronous하게 동작한다는 것은 한 서비스가 다른 서비스에 요청을 보내면 응답을 받을 때까지 대기를 해야합니다. 이로 인한 제약사항들이 있는데요. 요청한 서비스가 응답을 받을 때까지 대기해야 하므로 지연이 발생할 수 있고 리소스도 점유하게 됩니다. 그리고 한 서비스가 다운된다면 그 서비스를 호출하는 다른 서비스도 영향을 받을 수 있습니다.

또한, RESTful 통신을 사용하면 서비스 간 의존성이 강해지게 됩니다. 예시를 통해 살펴봅시다.

온라인 쇼핑몰 시스템은 다음 서비스들을 갖습니다.

  • Order Service
  • Payment Service
  • Inventory Service
  • Shipping Service

1) 새로운 서비스가 추가될 때마다 기존 서비스들이 호출 로직을 수정해야 합니다.
온라인 쇼핑몰에 "쿠폰 할인 서비스"가 새롭게 추가된다고 가정해봅시다. 새로운 흐름에 대해 다음과 같은 고민이 생길거에요.

  • 이 쿠폰은 어느 단계에서 쿠폰을 처리해야 하지? Order Service가 Coupon Service를 먼저 호출해서 쿠폰을 적용해야 할까? 그럼 Order Service의 호출 로직을 수정해줘야 하겠네.

  • 결제 전에 할인 금액을 적용해야 하니까 Payment Service가 Coupon Service를 호출해야 하나? 그럼 Payment Service는 원래 결제만 담당하는 역할이었는데 이제는 쿠폰 로직까지 고려를 해야 하네...

즉, 새로운 서비스가 추가될 때마다 기존 서비스들의 호출 순서를 계속 수정해야 하는 문제가 발생합니다.

2) 주문 취소, 실패 등의 경우 서비스 호출 순서를 정하기 어렵습니다.

고객이 주문을 취소하면 모든 관련 서비스가 롤백되어야 합니다.

  • 결제가 완료되었는데 주문이 취소된 경우 : Payment Service에서 결제 취소
  • 재고가 차감되었는데 주문이 취소된 경우 : Inventory Service에서 재고 복구
  • 배송이 진행되었는데 주문이 취소된 경우 : Shipping Service에서 배송 취소

그럼 다음과 같은 고민이 생기게 됩니다.
주문 취소는 결제, 재고, 배송 중 누구에게 먼저 전달해야 할까?

  • 결제 취소 후 재고 복구는 어떻게 해야 할까?
    • Order Service는 Payment Service를 호출해서 결제 취소를 요청하고 Payment Service는 결제를 취소하고 취소 상태를 Order Service에 반환하고 결제 취소가 성공하면 Order Service는 Inventory Service를 호출해서 재고를 다시 증가시키고…
  • 배송이 시작됐으면 먼저 배송을 취소해야 하나?

이처럼 한 서비스가 다른 서비스의 상태를 고려해야 하는 경우가 많아지면, 호출 흐름을 직접 관리해야 하므로 서비스 간 강한 결합도가 생기게 됩니다.

Database per Service로 인한 데이터 일관성 문제

  • 데이터 중복: 여러 서비스가 동일한 데이터를 독립적으로 관리해야 합니다.
    - 반정규화된 데이터
  • 데이터 일관성: 분산된 데이터베이스 간의 데이터 일관성을 유지하기 어렵습니다.
  • 복잡한 조회: 여러 서비스의 데이터를 조합해야 하는 경우, 복잡한 조회가 필요합니다.

MSA는 Database per Service입니다.

이로 인해 여러 서비스의 데이터를 조합해야 하는 경우에도 복잡한 조회 로직이 필요합니다.

서비스마다 데이터베이스가 다 다르니까 한 서비스에서 다른 서비스의 데이터를 수정하고 싶으면 api로 요청하고 조회를 하려고 해도 api로 요청을 하고…

이런 식으로 하다보면 복잡성이 증가하고 성능 문제도 발생을 할 수가 있어요.
그래서 동일한 데이터를 여러 데이터 베이스마다 저장을 해놓게 되는데 이를 반정규화된 데이터라고 합니다.

여러 서비스에 동일한 데이터가 중복 저장되므로 데이터의 일관성을 유지하기 어렵고 데이터 불일치가 발생할 수 있습니다.

예를 들어, 어떤 데이터를 수정할 때 서비스 간 반정규화된 데이터를 가지고 있는 모든 서비스를 찾아서 변경하도록 처리해야 하는데, 반정규화된 데이터 리스트를 관리하지 않는 이상 데이터의 일관성을 유지하기 어렵습니다.

분산 트랜잭션 처리의 어려움

이것도 일맥상통하는 이야기인데요. MSA에서는 각 서비스가 독립적인 데이터베이스를 가지기 때문에, 데이터베이스 트랜잭션이 안전하게 수행되도록 보장하는 ACID 트랜잭션을 사용하기 어렵습니다.

  • ACID 트랜잭션: 데이터베이스 트랜잭션이 안전하게 수행되도록 보장하는 4가지 속성입니다. Atomicity(원자성), Consistency(일관성), Isolation(격리성), Durability(지속성)의 앞글자를 따서 ACID입니다.

MSA에서는 여러 서비스에 걸친 트랜잭션을 일관되게 처리하기 어렵고, 한 서비스에서 실패하면 다른 서비스의 작업을 롤백해야 하는데 복잡합니다.


이러한 문제점들은 Event Driven Architecture로 해결할 수 있는데 다음 포스팅에서 알아보도록 하겠습니다.

0개의 댓글