마이크로서비스 아키텍처로의 전환

행복을 찾기 위한 쿠팡의 여정, 마이크로서비스 구현하기 — Part 1

쿠팡 엔지니어링
Coupang Engineering Blog
6 min readAug 3, 2022

--

By 정재훈

본 포스트는 영문으로도 제공됩니다.

설립 초기에, 쿠팡은 단 한 개의 서비스 안에 모든 컴포넌트가 존재하는 모놀리식 아키텍처(Monolithic Architecture)로 구성되어 있었습니다. 모놀리식 아키텍처는 쿠팡의 빠른 성장을 뒷받침하기에는 한계를 가지고 있었을 뿐만 아니라 풀기 어려운 다양한 문제들을 야기했습니다.

이를 해결하기 위해 쿠팡은 2013년에 모놀리식 아키텍처로 구성된 서비스를 마이크로서비스 아키텍쳐(Microservices Architecture, MSA)로 전환하는 프로젝트를 진행하였습니다. 이러한 전환을 위해 쿠팡이 취한 전략이 무엇이고, 그 과정에서 나타난 문제를 어떻게 풀어나갔는지 소개하고자 합니다.

모놀리식 아키텍처

쿠팡 모놀리식 아키텍처
그림1. 2013년까지 쿠팡에서 사용한 모놀리식 아키텍처

모놀리식 아키텍처는 전통적인 소프트웨어 프로그램 디자인 방법론으로, 한 개의 서비스 안에서 개별 컴포넌트 모두 서로 결합되어 하나의 큰 모듈로 존재하고 컴포넌트들은 내부적으로 잘 정의된 인터페이스를 통해 상호 연결되어 있는 아키텍처입니다. 빠르고 쉽게 서비스를 구성할 수 있어 적은 비용으로 서비스를 출시할 수 있으며 고객의 피드백도 빠르게 받아 볼 수 있는 장점이 있습니다.

이러한 장점으로 쿠팡 서비스도 Apache와 Tomcat으로 구성된 모놀리식 아키텍처로 시작하였습니다. 이 시점에 쿠팡은 단 하나의 Git 리포지토리에 모든 서비스 구성을 저장하고 있었습니다. 이는 모놀리식 아키텍처의 일반적인 모습입니다. 리포지토리 구성은 아래와 같았습니다.

Release/Coupang
├── coupang-api
├── coupang-batch
├── coupang-calculate
├── coupang-common
├── coupang-front
├── coupang-login
├── coupang-mobile-api
├── coupang-order
└── coupang-wing

모놀리식 아키텍처는 당시 크지 않은 스타트업이었던 쿠팡의 비지니스 니즈를 충족시키긴 했지만 서비스 규모가 커지면서 한계를 보이기 시작했습니다. 당시 쿠팡의 사례를 통해 모놀리식 아키텍처의 주요 5가지 문제에 대해 알아보겠습니다.

부분 장애가 서비스 전체 장애로 확대

쿠팡 모놀리식 아키텍처에서 “주문” 서비스에 문제가 생기면 모든 서비스에 장애로 이어졌음
그림 2. 모놀리식 아키텍처에서 “주문” 서비스에 문제가 생기면 서비스 전체의 장애로 이어졌습니다.

모놀리식 아키텍처 내부 컴포넌트들의 강한 결합도로 인해, 개발자가 잘못된 코드를 배포하거나, 트래픽 증가로 인해 서비스 성능에 문제가 있을 때, 서비스 전체의 장애로 확대되는 경우가 빈번히 발생하였습니다. 예를 들면, 위 그림과 같이 주문 서비스 내 메모리 누수를 일으키는 코드가 전체 서버의 메모리를 고갈시켜, 전체 장애로 이어졌습니다.

관심사 분리의 어려움

관리 용이성을 위해 여러 팀에서 사용하는 공통 모듈들을 모아 Git 리포지토리의 coupang-common 브랜치에서 관리했습니다. 그러나 시간이 흐르고 엔지니어링 조직이 커지게 되자 coupang-common은 점점 레거시화 되었습니다. 실사용 중인 코드 블록을 레거시 코드로부터 구분해내는 것이 점점 어려워졌고, 이를 관리할 제대로 된 규칙도 없었습니다. 함수 한개를 수정하기 위해 회사 전체로 메일을 보내 수정에 따른 영향을 파악하고 판단해야 했습니다. 이러한 이유로 기존의 함수를 수정하지 않고, 그대로 복사하여 변경하는 경우가 빈번해지면서 코드베이스(codebase)는 점점 커지고 혼란스러워졌습니다. 여러 컴포넌트들이 하나의 서비스 안에 강하게 결합되어 있어 서비스의 변경이 매우 어려웠고, 수정 시 어떤 장애를 일으켜 영향을 미칠지 파악하기도 힘들었습니다.

부족한 확장성

모놀리식 아키텍처는 하나의 서비스이기 때문에 반드시 서비스 전체에 대해 서버를 확장(scale-out)해야만 했습니다. 비용이 발생하더라도 서버를 투입해 서비스를 확장하고 문제를 해결할 수 있다면 다행이지만, 어떤 병목 지점은 구조적인 문제로 전체적인 아키텍처를 개선하지 않는 한 해결하기 어려운 문제도 있습니다. 병목 지점에 대한 예시는 다음 섹션에서 자세히 알아보겠습니다.

테스트 비용 증가

서비스의 안정성을 위해 일부의 기능을 수정하고 배포할 때마다, 쿠팡 엔지니어들은 단위 테스트(Unit Test), 회귀 테스트(Regression Test)를 수행합니다. 코드 변경이 크든 작든 항상 코드베이스 전체를 실행하면서 코드를 테스트했습니다. 그러나 코드베이스가 커지고 피처들의 수가 많아지면서, 아주 작은 코드 변경마저도 테스트 비용이 상당히 커져버렸습니다.

배포 대기 시간 증가

배포 과정에서도 같은 이유로 병목 지점이 발생하였습니다. 다수의 팀이 같은 Git 리포지토리를 사용하는 상황에서, 의도치 않은 코드 또는 테스트되지 않은 코드가 배포되는 것을 막기 위해 배포 플래그(flag)를 이용하였습니다. 오직 배포 플래그를 가진 팀만이 코드를 수정하고, 배포를 진행할 수 있었습니다. 이러한 프로세스는 100명 미만의 엔지니어로 구성된 조직 안에서는 잘 동작했습니다. 그러나 회사가 빠르게 성장함에 따라 우수한 엔지니어들이 많이 합류해 함께 일하게 되었고, 수많은 피처(Feature)들이 빠르게 개발되기 시작하자 배포 대기 시간이 폭발적으로 증가했습니다. 5줄 정도 수정된 코드를 배포하기 위해 2~3일 정도 대기하는 경우도 종종 발생하였습니다. 조직이 성장하면 할수록, 모놀리식 아키텍처 기반의 서비스에서는 배포 대기 시간이 비약적으로 증가할 수 밖에 없었습니다.

쿠팡 모놀리식 아키텍처에서 발생한 배포 병목 현상
그림 3. 쿠팡 모놀리식 아키텍처에서 발생한 배포 병목 현상

마이크로서비스 아키텍처

쿠팡 서비스가 빠르게 성장하는 상황에서 기존의 모놀리식 아키텍처는 서비스의 성장을 막는 가장 큰 장애물이 되어버렸습니다. 2013년, 쿠팡은 위에 나열된 문제들을 해결하고, 서비스 성장을 가속화하기 위해 모놀리식 아키텍처를 마이크로서비스 아키텍처로 전환하는 비타민 프로젝트를 시작했습니다.

마이크로서비스 아키텍처는 모놀리식 아키텍처와 달리 자유도가 높은 소프트웨어 프로그램 디자인 방법론으로, 복잡성이 낮은 기능들을 제공하는 독립적인 서비스 여러 개를 느슨하게 결합(loose coupling)하고 네트워크를 통해 서비스들끼리 통신하게 만들어 하나의 서비스를 구성해내는 아키텍처입니다. 구조적으로 모놀리식 아키텍처보다 더 복잡하지만, 각 서비스를 독립적으로 관리하고 테스트할 수 있다는 장점을 가지고 있습니다.

거대해진 레거시 시스템을 쿠팡의 Platform 팀이 어떤 전략을 바탕으로 어떻게 서비스 중단 없이 빠르게 마이크로서비스 아키텍처로 전환했는지는, 다음 섹션에서 설명드리겠습니다.

프레임워크

비타민 프레임워크는 마이크로서비스 아키텍처를 운영하기 위해 개발되었습니다. 비타민 프레임워크에는 쿠팡에서 개발한 표준 라이브러리 및 마이크로서비스 아키텍처를 위한 표준 스켈레톤 코드 템플릿이 있습니다. 표준 스켈레톤 코드 템플릿에는 도메인 단위에서 프런트엔드, API, 백엔드 서비스의 개발을 돕는 기본 코드들이 있습니다. 템플릿을 활용하면 도메인 팀에서 큰 노력 없이 테스트, 배포, 모니터링, 자동 복구가 가능할 뿐만 아니라, 메시지 큐(Message Queue), 캐시(Cache) 등 모든 내부 플랫폼 서비스와 쉽게 연동할 수 있습니다. 즉 비타민 프레임워크는 도메인 팀에 공통적으로 필요한 기술적 토대를 제공하고, 도메인 팀이 비지니스 로직 구현에만 집중할 수 있게 지원합니다.

클라이언트용 헬퍼 라이브러리 (Client-side helper library)

쿠팡 API-adapter의 아키텍처
그림 4. API-adapter의 아키텍처

일반적인 마이크로서비스 아키텍처의 경우, 분리되어 있는 도메인들끼리 RESTful API를 통해 상호 통신합니다. 특정 API를 사용하기 위해 모든 클라이언트들은 HTTP 통신을 하는 모듈을 만들고, JSON 형태의 API 요청을 만든 후, 요청을 오브젝트(object)로 매핑해야만 합니다. 예를 들어, 만약 상품 API를 사용하는 도메인 서비스가 10개 있다면, 10개의 팀 각자 이를 구현해야만 합니다. 이런 낭비를 줄이기 위해 저희는 API를 쉽게 사용할 수 있는 API 콜 모듈 라이브러리를 개발했습니다. 이 라이브러리는 다양한 API 버전에 종속적이다보니, 버전이 많아지면 많아질수록 모놀리식 시스템으로 변해버릴 수는 있습니다. 하지만 이런 단점에도 불구하고, 마이크로서비스 아키텍처로 빠르게 전환하는 쿠팡에서 API 콜 모듈 라이브러리는 많은 도움이 되었습니다.

메시지 큐(Message Queue)

쿠팡 강결합 형태로 이어진 모놀리식 아키텍처 (위) 및 독립적으로 분리된 마이크로 서비스 아키텍처의 Message Queue (아래)
그림 5. 강결합 형태로 이어진 모놀리식 아키텍처 (위) 및 독립적으로 분리된 마이크로서비스 아키텍처의 Message Queue (아래)

모놀리식 아키텍처에서는 모든 컴포넌트가 강결합 형태로 존재합니다. 예를 들면 고객이 주문하면 결제 요청, 배송 요청이 각각 이루어집니다. 모놀리식 아키텍처에서는 이런 여러 과정이 하나의 트랜잭션으로 강하게 결합되어 함께 처리됩니다. 이렇게 강결합된 형태의 트랜잭션들을 별개의 서비스로 분리하는 것은 쉽지 않습니다.

이 과제를 풀기 위해, 쿠팡은 인하우스 메시지 큐(Message Queue, MQ)인 비타민 MQ를 개발했습니다. 비타민 MQ는 안전하고 실수를 방지할(error-proof) 수 있는 방식으로 트랜잭션 모두를 마이크로서비스에서 처리가능한 메시지 형태로 변환합니다. 주문이 발생하면 비타민 MQ는 결제 요청, 배송 요청 등을 모두 메시지 또는 이벤트로 생성하여 트랜잭션을 분리합니다. 비타민 MQ를 이용해 도메인 팀은 코드 운영에서 벗어나 비즈니스 도메인에 집중할 수 있게 되었고 마이크로서비스 아키텍처에 빠르게 적응해 갈 수 있었습니다. 비타민 MQ를 사용하면서 얻는 또 하나의 큰 장점은 서비스 장애가 발생하더라도 메시지가 수신자(consumer)로 전달되는 것을 보장한다는 점입니다. 만약 배송 서비스에 장애가 나면 트랜잭션 무결성의 보장을 위해 해당 이벤트 메시지들은 자동으로 배달 못한 편지 큐(Dead Letter Queue)로 전달되고 서비스가 정상 복구되면 전달에 실패했던 이벤트 메시지들을 재처리할 수 있도록 구성하였습니다.

결론

이번 포스트를 통해, 쿠팡의 모놀리식 아키텍처에서 경험했던 5개의 문제점과 마이크로서비스 아키텍처로 전환하는 과정에서 경험했던 것들을 공유했습니다. 쿠팡은 비타민 프레임워크, 클라이언트용 헬퍼 라이브러리, 비타민 메시지 큐(MQ)를 구축하여 비즈니스의 성장을 가속화할 수 있었고, 서비스의 확장성과 안정성을 적은 비용으로 빠르게 확보할 수 있었습니다.

모놀리식 아키텍처 서비스로는 성장의 한계에 부딪힐 수 밖에 없습니다. 쿠팡과 같이 급속도로 성장하고 비슷한 문제를 갖고 있는 기업에게 마이크로서비스 아키텍처는 선택이 아니라 필수라고 생각합니다.

시리즈 목차

해당 포스트는 쿠팡의 마이크로서비스 아키텍처에 대한 첫 번째 포스트입니다.

Part 1 — 마이크로서비스 아키텍처로의 전환

Part 2— 마이크로서비스 아키텍처 환경에서 플랫폼 서비스 개발하기

쿠팡의 마이크로서비스 아키텍처를 개발하고 개선하고 싶으신 분들은 채용 중인 공고를 확인해 보세요!

--

--

쿠팡 엔지니어링
Coupang Engineering Blog

쿠팡의 엔지니어들은 매일 쿠팡 이커머스, 이츠, 플레이 서비스를 만들고 발전시켜 나갑니다. 그 과정과 결과를 이곳에 기록하고 공유합니다.