마이크로서비스 아키텍처. 그것이 뭣이 중헌디?

마이크로서비스 아키텍처

마이크로서비스 아키텍처는 하나의 큰 애플리케이션을 여러 개의 작은 애플리케이션으로 쪼개어 변경과 조합이 가능하도록 만든 아키텍처를 말합니다. 애플리케이션을 특화된 기능별로 나누게 되면 자연스럽게 애플리케이션의 추상화(abstraction)가 가능해집니다. 다시 말해, ‘인증’을 담당하는 서비스(예, auth.example.com)는 그 구체적인 구현 내용을 모르더라도 다른 서비스에서 약속된 인터페이스를 이용해 인증 과정을 수행할 수 있습니다. 또한, 검색창의 ‘자동완성’을 담당하는 서비스(예, autocomplete.example.com)는 사용자의 입력을 받아서 자동완성 결과만을 응답해주면 되기 때문에 해당 API를 유지한 상태에서 세부적인 구현내용을 언제든지 손쉽게 개선하고 변경할 수 있습니다.

모든 기술의 발전이 다 그렇듯이 마이크로서비스 역시 기존에 없다가 갑자기 등장한 개념은 아닙니다. 많은 기업들에서는 이미 이와 같은 방식으로 서비스를 분리하여 애플리케이션을 만들었으며 그때 그때 다양한 용어로 이름 붙여져 왔습니다. 그러나 최근 들어, REST API의 일반화, 도커(Docker)와 같은 컨테이너 기술, 클라우드 컴퓨팅 환경의 발전 등에 힘입어 마이크로서비스는 좀 더 손쉽게 구현될 수 있게 되었습니다.

마이크로서비스 아키텍처는 언제 필요한가?

모든 애플리케이션이 마이크로서비스 아키텍처 패턴으로 구성될 필요는 없습니다. 특히, 적은 인원으로 빠르게 시작해야하는 스타트업의 경우 앞으로 어떤 서비스와 컴포넌트가 필요하게 될 지 예측할 수 없는 상황에서 과도하게 시스템을 여러개의 서비스로 쪼갤 필요는 없습니다. (물론, 서비스에 대한 로드맵이 명확하여 초기에 시스템을 마이크로서비스 형태로 구성하는 것도 가능합니다). 그렇다면, 구체적으로 어느 시점에 마이크로서비스 아키텍처에 대해서 고려하는 것이 좋을까요? 일반적으로 다음의 항목들 중에서 대부분이 현재 상황에 해당한다고 생각되면 마이크로서비스 아키텍처 패턴에 대해서 고민을 시작해 보는 것도 나쁘지 않습니다.

  1. 애플리케이션의 배포에 한 시간 이상 소요된다.
  2. 단순한 기능 하나를 수정해도 전체 기능에 대한 QA가 필요하다.
  3. 단순한 버그 수정이 더 중대한 버그를 생산하는 일이 많아졌다.
  4. 현재의 애플리케이션을 기능별로 나눈다고 가정했을 때 수십개의 마이크로서비스가 가능하다.

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

마이크로서비스 아키텍처를 잘 이해하기 위해서는 먼저, 반대되는 개념인 모놀리틱(Monolithic) 아키텍처에 대해서 살펴볼 필요가 있습니다. 이해를 돕기 위해서, 아마존(Amazon.com)과 유사한 온라인 쇼핑몰을 만든다고 가정해 보겠습니다. 기본적으로 처음 설계되는 애플리케이션의 구조는 그림 1과 같이 비즈니스 로직을 담당하고 있는 애플리케이션이 존재하고, 해당 애플리케이션은 데이터베이스 등 외부 시스템과 특정 프로토콜로 통신을 하게 됩니다. 또한, 사용자에게 인터페이스를 제공하기 위해서 HTML을 렌더링하는 부분과 RESTful API를 제공하는 부분을 갖게 됩니다. 이렇게 구성된 애플리케이션의 소스코드는 하나의 프로젝트로 구성되어 있으며 단일한 패키지로 배포되게 됩니다.

그림 1. 모놀리틱 아키텍처

1

이러한 구성의 애플리케이션은 특별히 이상할 것도 없고 실제로 많은 서비스들이 이와 같은 구성으로 이루어져 있습니다. 이렇게 단순한 구성의 애플리케이션은 로컬 환경에서 개발하기에도 편리하고 통합 시나리오 테스트를 수행하기에도 가장 손쉬운 구성입니다. 또한, 모든 코드가 하나의 묶음으로 구성되어 있기 때문에 배포도 매우 간편해집니다.

하지만 이러한 단순한 애플리케이션의 아키텍처는 서비스가 지속적으로 성장하고 규모가 커질 때 한계에 부딪히게 됩니다. 예를 들어, 3명의 개발자가 몇 가지 핵심 기능을 개발할 때에는 이와 같은 모놀리틱 아키텍처가 최적의 효율성을 보장하지만 개발자의 규모가 수십에서 백명 이상이 되고 서비스의 복잡도가 증가되면 아주 간단한 기능을 하나 추가하기 위해서도 매우 많은 줄의 코드를 수정해야함은 물론, 코드의 변화가 영향을 미치는 범위가 증가되었기 때문에 간단한 변화 하나에도 통합 테스트가 필요하게 됩니다.

많은 회사에서는 이러한 문제를 해결하기 위해서 여러가지 프로세스를 도입하고 애자일 철학을 기반으로 둔 여러가지 방법론을 적용해보고자 노력합니다. 하지만 실제로 이러한 시도가 서비스 구조의 근본적인 원인을 해결하지는 못하기 때문에 좋은 성과를 거두지 못하게 됩니다. 대부분의 경우 근본적인 원인은 서비스의 구조 자체가 너무 복잡하다는 점입니다. 복잡한 구조는 서비스 초창기 부터 함께 개발을 하여 전체 히스토리를 알고 있는 소수의 개발자를 제외하고는 대부분의 개발자들이 전체적인 시스템의 구조를 알지 못하기 때문에 재활용 가능한 모듈을 무시하고 중복된 코드를 생산하게 되며 사용하지 않는 코드가 기술 부채로 계속 쌓이게 됩니다. 또한, 코드가 서로 다양한 방식으로 연관되어 있기 때문에 간단한 버그 수정이 더 큰 버그를 양산하게 되는 결과를 초래합니다.

서비스 복잡도가 증가하면서 모놀리틱 아키텍처가 가지는 문제점들은 배포 시간의 증가, 부분적 스케일 아웃의 어려움, 안정성의 감소 등 여러가지가 있습니다. 그 중에서도 굳이 한가지를 꼽자면 애플리케이션을 구성하는 프로그래밍 언어, 또는 프레임워크의 변경이 거의 불가능에 가까울 정도로 어렵다는 점 입니다. 예를 들어, 애플리케이션에서 사용자의 인증만을 담당하는 요소가 별도의 서비스로 구현되어 있다면 필요에 따라 성능과 안정성을 증가시킬 수 있는 새로운 프레임워크로 변경하는 것을 고려해볼 수 있습니다. 하지만 만약 전체 애플리케이션이 하나로 묶여 있다면 그 동안 개발된 방대한 양의 코드를 새로운 언어, 또는 프레임워크로 전환해야 하기 때문에 대부분 시도조차 할 수 없을 것 입니다.

마이크로서비스 아키텍처 (Microservice Architecture)

마이크로서비스 아키텍처 패턴은 그 이름에서도 유추할 수 있듯이 모놀리틱 아키텍처로 구성된 하나의 큰 서비스를 독립적인 역할을 수행하는 작은 단위의 서비스로 분리하여 설계하는 패턴을 말합니다. 여기에서 말하는 독립적인 역할이란 주로, ‘사용자 관리’, ‘주문 관리’, ‘결제 관리’, ‘알림 관리’와 같이 기능적인 요소를 의미합니다. 각각의 마이크로서비스는 그 크기만 작을 뿐, 자세히 살펴보면 각각이 하나의 모놀리틱 아키텍처와 유사한 구조를 갖습니다. 다만, 하나의 서비스에서 처리해야 하는 기능과 규모가 작기 때문에 이를 마이크로서비스라고 부릅니다.

그림 2 마이크로서비스 아키텍처

2

그림 2는 마이크로서비스 아키텍처의 개략적인 모습을 몇 가지 예시로 나타낸 그림이며 이 예시는 총 4개의 마이크로 서비스로 구성되어 있습니다. 사용자 서비스는 REST API를 이용해서 주문 서비스를 활용하며 API Gateway를 통해 정보를 웹브라우저 화면에 표시하거나 모바일 클라이언트에 데이터를 제공합니다. 또한, 사용자에게 알림이 필요한 경우 실제 알림이 어떤 과정을 통해서 처리되는지 신경쓸 필요없이 알림 서비스(Notification service)를 이용하여 원하는 요청을 호출할 수 있습니다. 그림에 표시된 API Gateway는 뒷쪽에서 자세히 설명하도록 하겠습니다.

마이크로서비스 아키텍처를 나타낸 그림에서 주목할 점은 사용자를 위한 데이터베이스와, 주문을 위한 데이터베이스가 따로 표시되어 있다는 점입니다. 이 부분은 실제로 마이크로서비스 아키텍처를 구현할 때 매우 중요한 부분인데, 전통적인 모놀리틱 아키텍처에서 주로 개발을 했던 경험에 비추어 보면 데이터의 트랜잭션 관리나 정규화 등의 관점에서 매우 비효율적으로 보일 수 있습니다. 물론, 하나의 데이터베이스를 각각의 개별 서비스가 공유해서 사용하는 방식도 가능하지만 마이크로서비스 아키텍처가 가지는 근본적인 장점을 최대한 활용하기 위해서는 이렇게 서비스별로 별도의 데이터베이스를 사용하는 것이 필요합니다. 또한, 데이터베이스(DBMS)의 종류 자체도 반드시 한가지 통일할 필요 없이 데이터의 특성과 서비스가 가지는 특수성에 따라 가장 효율적인 데이터베이스를 선택하여 사용하는 것도 가능합니다. 예를 들어, 어떤 서비스에서 사용하는 데이터는 변경이 적고 주로 읽기(read) 작업만 수행되는 반면, 또 다른 서비스의 데이터는 읽기 작업보다 빠른 속도로 쓰여지는(write) 작업이 대부분이라면 각각의 서비스 특성에 맞게 데이터베이스의 종류를 결정하고 설계할 수 있습니다.

마이크로서비스 아키텍처의 장점

마이크로서비스 아키텍처는 서비스의 규모가 커지고 복잡도가 증가할 수록 여러가지 장점을 갖습니다. 우선 서비스가 개별적으로 독립적인 단위의 애플리케이션이기 때문에 변경이 용이하고 그 변경이 다른 서비스에 미치는 영향이 적습니다. 또한, 개별 서비스 단위의 배포가 가능하기 때문에 하루에도 필요에 따라 여러 번 배포를 하는 것이 가능합니다. 비용적인 측면에서 보자면 마이크로서비스 아키텍처는 부하가 집중되는 특정 서비스를 위해 전체 애플리케이션을 스케일 아웃할 필요가 없기 때문에 불필요한 자원의 낭비를 줄일 수 있습니다. 특히, 서비스의 특성에 따라서 메모리 사용이 많은 서비스도 있을 수 있고, 계산 과정이 많아서 CPU 사용량이 많은 서비스가 있을 경우 서비스의 특성에 맞게 자원을 할당하여 스케일 아웃할 수 있기 때문에 효율적인 자원사용이 가능하게 됩니다.

이 외에도 마이크로서비스 아키텍처는 다양한 장점을 가지고 있지만, 여기에서 가장 강조하고 싶은 장점 중 하나는 시스템의 아키텍처가 개발 조직과 나아가서 회사의 조직 문화에 큰 영향을 미친다는 점입니다. 웹 서비스를 기반으로 하는 대부분의 회사들은 애자일의 사상을 도입하고 여러가지 방법론들을 채택하여 빠르고 유연한 개발 문화를 만들고자 노력합니다. 하지만 서비스의 규모가 커지고 시스템이 복잡해지면 사소한 변경 하나가 발생시킬 수 있는 문제(side effect)가 많아지고, 이 때문에 조직은 복잡한 시스템에 맞는 복잡한 프로세스를 필연적으로 가지게 됩니다. 마이크로서비스 아키텍처의 가장 큰 장점은 특정 서비스의 변경이 다른 서비스에 영향을 미칠 가능성이 적다는 점과 서비스 단위로 독립적인 배포가 가능하다는 점 입니다. 다시 말해, 인증과 관련된 서비스가 독립적으로 분리되어 있다면 해당 서비스의 개선과 수정 작업이 다른 서비스의 이해 당사자들과 독립적으로 진행될 수 있기 때문에 의사결정이 빠르고, 독립적인 테스트의 구축이 용이하기 때문에 품질이 증가하게 됩니다 (다른 서비스와 연계된 통합 테스트는 서비스가 분리될 수록 오히려 복잡도가 높아지게 됩니다). 이것은 다시 말해, 조직의 의사결정 프로세스와 테스트 및 배포 프로세스 등 많은 부분에 영향을 미친다는 것을 의미합니다.

마이크로서비스 아키텍처의 단점

반면, 마이크로서비스 아키텍처가 모든 면에서 장점만을 갖는 것은 아닙니다. 마이크로서비스 아키텍처가 가지는 대표적인 단점으로는 모놀리틱 아키텍처에 비해 서비스 간의 통신에 대한 처리가 추가적으로 필요하다는 점입니다. 이것은 단순히 개발해야 하는 코드의 양이 늘어난다는 점 뿐만 아니라, 사용자의 요청을 처리하기 위한 응답속도의 증가에도 영향을 미칩니다. 뿐만 아니라, 분산된 데이터베이스는 트랜잭션 관리가 용이하지 않기 때문에 데이터의 정합성을 맞추기 위한 노력이 추가적으로 필요합니다. 대부분의 모놀리틱 아키텍처에서는 하나의 데이터베이스를 사용하기 때문에 트랜잭션에 대한 처리가 크게 어렵지 않습니다. 하지만 서로다른 데이터베이스, 심지어 종류도 서로 다른 데이터베이스 내의 데이터의 정합성을 유지하기 위한 트랜잭션 처리는 대부분의 데이터베이스가 자체적으로 지원하지 않기 때문에 애플리케이션의 개발과정에서 항상 고려해야 한다는 어려움이 있습니다.

앞서 설명한 바와 같이 마이크로서비스 아키텍처는 서비스의 특성에 따라 효율적인 자원 사용이 가능하도록 스케일 아웃이 가능한 장점이 있는데 반해, 수 많은 서비스를 배포하고 관리하는 데에 어려움이 있습니다. 넷플릭스(Netflix)와 같은 대규모 서비스들은 보통 수백개 이상의 마이크로서비스로 이루어지는데 이렇게 많은 서비스들은 각각 서로 분산되어 있기 때문에 관리 포인트가 증가하고 통합해서 모니터링하고 운영하는 것이 모놀리틱 아키텍처에 비해 매우 어려워집니다. 이것은 필연적으로 매우 정교한 배포 자동화를 필요로 하며 많은 PaaS(Platform as a Service) 서비스, 또는 도커(Docker)와 같은 컨테이너 기술을 활용하여 도움을 받을 수 있습니다.

API Gateway

그림 3은 우리가 많이 사용하는 검색 포털 사이트의 메인 화면입니다. 사용자는 메인 페이지를 보기 위해서 하나의 URL을 통해 서버에 요청을 보내지만 실제 사용자가 보게 되는 화면에는 다양한 종류의 서비스 결과들 입니다. 그림에서 보여지는 붉은색 상자는 개별 서비스를 나타내며 위에서 부터 순서대로 이름을 붙여보자면, ‘사용자 서비스’, ‘뉴스 서비스’, ‘실시간 급상승 검색어 서비스’, ‘광고 서비스’, ‘날씨 서비스’ 등으로 나눌 수 있습니다.

그림 3. 검색 포털 메인화면에서의 다양한 종류의 서비스

3

만약, 모놀리틱 아키텍처를 이용해서 이와 같은 요청을 처리하는 애플리케이션을 구현하였다면 실제 요청이 도착하여 결과를 반환하기 까지 서버는 다양한 종류의 쿼리를 데이터베이스에 보내게 됩니다. 

그림 4. 모놀리틱 아키텍처에서의 요청 처리

4

이러한 방식은 애플리케이션 아키텍처가 간단하다는 장점은 있지만 특정 서비스에 변경이 있을 경우 이 서비스를 포함하고 있는 모든 코드를 찾아서 수정해 주어야만 하는 어려움이 있습니다.

그렇다면, 마이크로서비스 아키텍처를 갖는 애플리케이션에서는 이와 같은 요청에 대한 처리를 어떻게 수행하게 될까요? 그럼 5는 각각의 마이크로서비스들이 요청에서 필요한 각 영역을 담당하여 처리하는 모습을 보여줍니다.

그림 5. 마이크로서비스 아키텍처에서의 요청 처리

5

그림 5에서 볼 수 있듯이 각각의 마이크로서비스들은 각각이 담당한 내용에 대한 응답을 클라이언트에 보내주어서 임무를 완수합니다. 하지만, 이론적으로는 가능할지 몰라도 이를 실제로 구현하다 보면 몇 가지 어려움이 있습니다.

먼저, 클라이언트(Web UI 또는 모바일 앱)는 메인 페이지를 화면에 표시하기 위해서 연관된 모든 마이크로서비스에 각각 요청을 보내야만 합니다. 우리가 살펴보는 예제에서는 총 5개의 마이크로서비스가 관련되어 있지만 실제로는 이 보다 훨씬 더 많은 요청을 호출해야하는 경우도 있을 수 있습니다. 각각의 마이크로서비스에 요청을 보내기 위해서는 클라이언트가 모든 마이크로서비스의 호스트명은 물론 end_point를 알고 있어야 합니다. 즉, 마이크로서비스가 추가되거나 호스트정보가 변경되면 클라이언트가 가지고 있는 정보 역시 함께 수정해 주어야 합니다. 뿐만 아니라, 클라이언트는 서버에 요청을 보내고 응답을 받기 위해서 네트워크 지연속도(latency)가 필요한데, 요청의 회수가 증가할 수록 이 지연속도는 선형적으로 증가할 수 밖에서 없는 문제를 가지고 있습니다. 즉, 요청을 보내야하는 서비스의 개수가 증가할 수록 응답속도가 늦어진다는 점입니다.

두 번째로, 이와 같은 방식의 요청처리를 클라이언트에서 구현하려면 코드가 매우 복잡해지게 됩니다. 일반적으로 클라이언트는 요청을 보내야하는 서버의 호스트를 명시한 뒤 각각의 상황에 맞게 end_point를 변경하며 요청을 보냅니다. 그러나 이와 같이 여러개의 마이크로서비스에 요청을 보내기 위해서는 모든 마이크로서비스의 주소를 저장해 두어야하며, 요청을 보낼 때마다 해당 요청이 어떤 서비스에 보내는 것인지를 명시해 주어야 하기 때문에 필연적으로 소스코드가 복잡해지는 결과를 초래합니다.

세 번째로, 모든 마이크로서비스가 웹 통신에 적합한 프로토콜로 통신하지는 않는다는 점입니다. 많은 마이크로서비스들은 사용자에게 주는 기능적인 관점에서 나누어져 있기 때문에 HTTP 통신을 제공하지만 일부 서비스들은 서비스 자체가 가지는 특성에 따라 더 알맞는 프로토콜을 사용할 수 있습니다. 예를 들어, 사용자에게 알림을 보내주기 위한 서비스의 경우 요청을 순차적으로 빠르게 처리하기 위해서 메시지 큐와 관련된 프로토콜을 사용할 수도 있습니다. 이러한 방식의 프로토콜들은 모바일 앱 또는 웹 브라우저가 직접 통신하기에는 적합하지 않을 뿐만 아니라, 보안상으로도 방화벽(firewall) 내부에 위치하면서 외부에서 직접 접근하는 것을 차단해야만 합니다.

마지막으로, 이러한 방식의 구현은 추후 두 개 이상의 마이크로서비스가 통합되거나 하나의 마이크로서비스가 두 개 이상으로 분리되는 경우 여기에 맞추어 클라이언트 코드를 수정하는 것이 매우 어렵습니다. 앞에서도 이야기 했지만, 클라이언트는 마이크로서비스에 요청을 보내기 위해서 모든 마이크로서비스의 호스트명은 물론 end_point를 알고 있어야 하며, 호스트정보가 변경되면 클라이언트가 가지고 있는 정보 역시 함께 수정해 주어야하기 때문입니다.

API Gateway를 이용한 해결책

API Gateway는 그 이름에서도 유추할 수 있듯이 서비스로 전달되는 모든 API 요청의 관문(Gateway) 역할을 하는 서버입니다. API Gateway는 시스템의 아키텍처를 내부로 숨기고 외부의 요청에 대한 응답만을 적합한 형태로 응답합니다. 즉, 클라이언트는 시스템 내부의 아키텍처가 마이크로서비스 형태로 되어있는지 모놀리틱 아키텍처로 구현되어 있는지를 알 필요가 없으며 서로 약속한 형태의 API 요청만을 서버로 보내면 알맞는 형태의 결과를 받을 수 있습니다.

모든 사용자의 API 요청은 그림 6과 같이 제일 먼저 API Gateway에 도착하게 됩니다. API Gateway는 받은 요청을 기반으로 필요한 마이크로서비스에 개별적인 요청을 다시 보내게 됩니다. 이렇게 각각의 마이크로서비스로부터 받은 응답들을 API Gateway는 다시 취합하여 클라이언트에게 전달하는 역할을 수행합니다. 이 과정에서 API Gateway는 사용자의 HTTP 요청을 마이크로서비스가 받을 수 있는 다른 형태의 프로토콜로 전환하는 역할을 수행하기도 합니다. 앞에서 살펴본 검색 포털의 메인화면을 예로 들어 설명하자면, 사용자의 클라이언트는 메인 페이지에 대한 요청을 API Gateway에 보내고, 이 요청을 받은 API Gateway는 해당 요청의 응답에 필요한 정보들을 사용자서비스, 뉴스 서비스, 실시간 급상승 검색어 서비스, 광고 서비스, 날씨 서비스등에 해당 결과들을 하나의 응답으로 취합한 뒤 클라이언트에게 다시 전달하게 됩니다.

그림 6. API Gateway를 활용한 마이크로서비스 아키텍처에서의 요청 처리

6

API Gateway는 이처럼 클라이언트의 요청을 일괄적으로 처리하는 역할 뿐만 아니라, 전체 시스템의 부하를 분산시키는 로드 밸런서의 역할, 동일한 요청에 대한 불필요한 반복작업을 줄일 수 있는 캐싱, 시스템상을 오고가는 요청과 응답에 대한 모니터링 역할도 수행할 수 있습니다.

이렇게 API Gateway를 이용하여 서비스 요청에 대한 처리를 하게되면 특정 서비스의 변경사항이 생기거나 서비스가 통합/분리 되더라도 클라이언트는 그 사실을 인지할 필요가 없으며 API Gateway 내부의 변경사항만으로 처리가 가능하게 됩니다. 이렇게 API Gateway를 이용하여 서비스 요청에 대한 처리를 하게되면 특정 서비스의 변경사항이 생기거나 서비스가 통합/분리 되더라도 클라이언트는 그 사실을 인지할 필요가 없으며 API Gateway 내부의 변경사항만으로 처리가 가능하게 됩니다. 이렇게 시스템 내부의 아키텍처를 숨길 수 있는(encapsulate) 특성이 API Gateway가 갖는 가장 큰 장점이라고 할 수 있습니다.

API Gateway는 다른 모든 것들과 마찬가지로 장점만을 갖는 것은 아닙니다. API Gateway가 갖는 대표적인 단점으로는 구현하고 관리해야하는 요소가 하나 더 증가한다는 점입니다. 예를 들어, 특정 마이크로서비스에 기능이 추가되는 경우, API Gateway가 포함된 해당 기능을 사용자에게 전달하기 위한 내용을 API Gateway에도 반영해 주어야 합니다. 이러한 이유 때문에 API Gateway를 관리하는 절차가 최소화 되어야만 합니다.

API Gateway가 갖는 또 하나의 단점은 성능상의 병목(bottleneck)지점이 될 수 있다는 점입니다. 앞에서 설명한 바와 같이 API Gateway는 모든 요청을 수용해야 하는 창구 역할을 하기 때문에 이 부분에 병목현상이 발생하면 서비스 전체의 품질에 지대한 영향을 미치게 됩니다. 이러한 이유 때문에 API Gateway를 설계하고 구현할 때에는 항상 성능과 확장성을 고려해야만 합니다. 특히, API Gateway를 비동기(asynchronous)적이고 non-blocking I/O 처리가 가능하도록 구현하는 것이 적은 비용으로 최대의 성능을 발휘할 수 있는 관건이 됩니다.

API Gateway 구현시 고려해야할 점들

앞서 살펴본 검색 포털의 메인화면을 잘 살펴보면 각각의 서비스들이 서로 독립적으로 동작가능하다는 점을 알 수 있습니다. 이러한 경우 API Gateway는 동시에 여러개의 요청을 각각의 마이크로서비스에 전달하는 것이 가능합니다. 하지만, 모든 요청이 이와 같이 병렬로 처리될 수 있는 것은 아닙니다. 예를 들어, 검색 포털에서 카페의 메인화면을 표시하기 위해서 API Gateway는 먼저 '사용자 서비스'에 요청을 보내어 사용자 정보를 가져와야 하며, 이 정보를 기반으로 해당 사용자가 어떤 까페에 가입화여 활동중인지를 '카페 서비스'에 요청해야 합니다. 즉, 개별 마이크로서비스로 보내는 요청의 선후관계가 존재하게 되는 것입니다. 이러한 경우 일반적인 비동기처리를 위해서 콜백(callback) 함수를 이용하게됩니다. 즉, 사용자 서비스로 부터 정보를 받아온 뒤 이 요청의 콜백함수에서 카페 서비스로 다시 요청을 보내게 되는 것입니다. 이러한 방식의 애플리케이션 코드는 흔히 말하는 콜백 지옥(callback hell)을 경험하게 할 수 있으며 유지보수와 소스코드의 가독성을 현저하게 악화시키는 원인이 되곤 합니다.

이러한 문제점 때문에 많은 언어와 프레임워크들은 동일한 처리를 수행하는 코드를 좀 더 직관적이고 서술적으로 표현할 수 있는 Reactive 프로그래밍을 고안하게 되었습니다. 대표적인 reactive 프로그래밍 방식으로는 자바스크립트의 Promise, 스칼라의 Future등이 있습니다. 이러한 reactive 접근방식을 잘 활용하여 API Gateway를 구현하게 되면 비동기 처리의 성능적인 이점은 유지하면서 좀 더 직관적이고 관리하기 쉬운 코드를 작성할 수 있습니다.

API Gateway를 구현할 때 고려해야하는 또 한 가지 중요한 점은 예외에 대한 처리입니다. 마이크로서비스 아키텍처에서는 모놀리틱 아키텍처에 비하여 분산된 서버의 상태와 여러가지 변수들로 인해서 일부 서비스에 장애가 발생하거나 응답속도가 지연될 가능성이 높아지게 됩니다. 이렇게 특정서비스에 문제가 발생했을 때 API Gateway는 서비스 장애의 종류에 따라 적절한 처리가 가능하도록 설계/구현되어야 합니다. 예를 들어, 카페 메인화면에 대한 요청을 처리하는 과정에서 특정 서비스에 장애가 발생하는 상황을 예로 들어보도록 하겠습니다. 카페 메인화면에는 사용자 정보와 사용자가 가입한 카페 목록, 그리고 사용자가 관심을 가질만한 추천 카페 목록을 표시한다고 가정해 보겠습니다. 이 과정에서 '추천 카페 목록'을 제공하는 '추천 서비스'에서 장애가 발생하는 경우 일반적으로 카페 메인화면은 정상적으로 표시가 되고 추천 목록에는 임시적인 페이지로 대체되는 것이 적당할 것 입니다. 반면, 카페 목록을 제공해야 하는 '카페 서비스'에 장애가 발생하는 경우에는 가장 중요한 정보를 전달할 수 없기 때문에 오류 처리를 하는 것이 적합할 것 입니다. 이처럼 API Gateway는 특정 요청에 대하여 개별 서비스의 특수한 장애 상황에 대해서 어떻게 대처할지에 대한 고려가 구현시 포함되어야 합니다.

마지막으로 API Gateway는 모든 요청이 몰리는 지점이기 때문에 동일한 요청에 대하여 중복적으로 마이크로서비스에 요청을 보내는 것 보다 기존의 결과를 캐싱하여 재활용할 수 있도록 설계하는 것이 중요합니다. 예를 들어, 검색 포털의 메인화면에서 '날씨 서비스'와 같은 경우, 모든 요청에 대하여 반복적으로 현재 날씨를 가져오는 것 보다 주기적인 시간 단위로 갱신을 하며 한번 가져온 결과를 해당 주기 내에서 재활용하는 것이 불필요한 자원사용을 막는 현명한 방법이라고 할 수 있겠습니다.


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.