Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

Uber System Design

Uber System Design. Design Ride-Hailing Application 영상에 대한 분석이다. 영상내용을 기반으로 좀 더 깊이 들어가보려 한다.

다룰 내용

  1. Uber의 Ride-Haling Application을 개발하기 위한 요구사항 분석
  2. 하이레벨에서 컴포넌트 다이어그램 작성
  3. 각 컴포넌트의 시퀀스 다이어그램 작성
  4. 잠재적인 병목구간의 확인과 최적화
애플리케이션을 개발하기 위한 일반적인 절차들이다. 개발 요구사항을 분석하면서, 해당 요구사항을 만족하기 위한 주요 컴포넌트들을 식별할 수 있을 것이다. 이제 각 컴포넌트들의 작동방식을 시퀀스 다이어그램으로 만들면 기본적인 개발 분석이 끝난다. 이후에 이루어지는 작업은 요구사항과 다이어그램을 유저스토리로 만들고, 유저스토리를 feature 단위로 쪼개서 구현하는 것이다.

컴포넌트 다이어그램은 시스템을 구성하는 임의의 물리적인 요소과 기능을 식별하고, 각 물리적인 요소들의 연결을 묘사한다. 컴포넌트 다이어그램을 이용하면 시스템의 소프트웨어 아키텍처를 보다 쉽게 조망할 수 있다. 이 다이어그램은 개발자와 시스템 이해 관계자 간의 커뮤니케이션 도구로도 사용 할 수 있다. 프로그래머는 다이어그램을 사용하여 구현을 위한 로드맵을 공식화하여, 필요한 기술, 작업할당에 대한 의사결정을 내릴 수 있다. 시스템 관리자는 구성요소 다이어그램을 사용하여 논리적인 소프트웨어의 구성을 확인하고, 이들 소프트웨어를 배치하기 위한 인프라 계획을 수립할 수 있다.

 Component Diagram

시퀀스 다이어그램은 소프트웨어 컴포넌트들이 시나리오(기능)을 수행하기 위해서 필요한 메시지의 순서를 묘사한다. 특정 시나리오에 대한 실행 순서와 시스템간에 발생하는 이벤트들을 보여주기 때문에, 컴포넌트들이 수행하는 기능이 무엇인지, 성공하는 시나리오를 위해서 어떤 순서로 작동해야 하는지 등을 검토 할 수 있다. 시퀀스 다이어그램은 소프트웨어의 상세 구현을 위한 전 단계로 사용한다.

 Sequence Diagram

여기에서는 구현을 다루지 않고 설계 레벨에서 끝낼 것이다. 설계를 마무리하기 전에 병목구간이 발생할 만한 구간을 찾아내서 최적화하기 위한 솔류션을 찾아보도록 하겠다.

Lifecycle of a Trip

우버를 이용해서 목적지까지 도달하기 까지를 추적하는 시스템을 디자인 할 것이다. 우버 차량을 호출하고, 목적지까지 무사히 도착하기 위해서 필요한 것들이 무엇인지, 해당 필요 요구조건을 충족하기 위해서 어떤 시스템이 필요한지를 식별해야 한다. 가장 좋은 방법은 시각화하는 것이다. 아래와 같이 시각화 했다.

 Lifecycle of a Trip

내가 모르는 어떤 것을 분석하는 각자의 방법론이 있을 것이다. 바닥 부터 시작하는 분해는 검증된 방법이다. 최소한의 요소로 분리하고 각 요소들간의 관계를 기술하는 방식이다. 우리는 사용자가 차량을 요청해서 목적지에 도착하기까지의 과정을 분해하여 어떤 일이 발생하는지를 검토하기로 했다.

  1. 사용자(Rider)는 B 지점으로 이동하기 위해서 우버 서비스를 이용하기로 했다. 우버앱을 실행하면, 부트스트랩핑이 수행 될 것이다. 여기에서는 지도를 실행하고 사용자의 위치, 교통량 등을 파악하는 작업을 시작할 것이다. 백앤드에서는 사용자가 앱을 실행했음을 이벤트 형식으로 전송 할 것이다.(사용자가 언제, 어느 위치에서 앱을 실행 했는지, 실행 후 실제 차량을 호출하기 위한 활동을 했는지 등도 데이터가 될 것이기 때문이다.)
  2. 부트스트랩이 끝나면 앱은 사용자에게 근처의 드라이버 상황을 보여준다.
  3. 사용자는 목적지를 입력한다. 목적지를 입력하면 경로, 대략적인 시간, 사용할 우버 서비스(UberX, Comfort, UberPool 등)를 선택한다.
  4. 배차요청이 전송된다.
  5. 근처의 드라이버가 수락하면 배차가 된다.
  6. 잠시 후 승차한다.
  7. 승객에게 차량이 도착할 ETA(예상도착시간) 이 출력된다.
  8. 차량 이동경로가 추적된다. 이 데이터는 우버 서버에 쌓일 것이다.
  9. 사용자는 목적지까지 도착하기 전에, 중간애 내리기 위해서 "취소" 할 수 있다. 취소도 이동완료로 처리한다.
  10. 이동이 완료되면 우버앱에 등록한 카드로 계산을하고 영수증이 출력된다.
세부적으로 추가되어야 할 것들이 있겠으나, 전체적인 흐름은 완료 됐다. 이 기능을 구현하면 작동하는 애플리케이션을 개발 할 수 있을 것이다.

요구사항

앞서 작성한 다이어그램으로 부터 기능 요구사항을 정리 할 수 있다.
  • 사용자는 근처의 드라이버를 확인 할 수 있어야 한다.
  • 사용자는 출발지와 목적지를 포함해서 요청을 만들 수 있어야 한다.
  • 드라이버는 사용자의 요청을 수신할 수 있어야 한다.
  • 라이더는 드라이버의 ETA를 볼 수 있어야 한다.
  • 라이더와 드라이버는 영수증을 받을 수 있어야 한다. 영수증에는 출발지와 목적지, 이동 경로, 시간등을 포함한다.
이들 기능 요구사항으로 부터 숨겨진 추가 요구사항을 발견할 수 있다.
  • 사용자와 드라이버를 매칭시켜야 한다.
  • 사용자가 청구금액을 결제 할 수 있는 결제 시스템을 구성해야 한다.
이 요구 사항들은 Lifecycle of a Trip 의 다이어그램에 포함된 모든 작업을 수행 할 수 있도록 구성됐다.

컴포넌트 다이어그램

컴포넌트 다이어그램이란 시스템을 구성하는 소프트웨어 컴포넌트의 인터페이스와 종속성을 기술하는 다이어그램으로 소프트웨어 시스템의 구조를 보여준다. 컴포넌트 다이어그램은 하이레벨(High-Level)에서 소프트웨어 구성요소를 간단히 모델링 하거나 패키지 레벨의 낮은 수준에서 컴포넌트를 표시 할 수 있다. 여기에서는 하이레벨로 작성할 것이다.

컴포넌트 다이어그램을 그리는 이유는 아래와 같다.
  • 소프트웨어 시스템의 실행 가능 및 재사용 가능 측면을 정의한다.
  • 종속 관계를 통해서 소프트웨어 구성 문제를 식별할 수 있다.
  • 소프트웨어를 변경 혹은 개선하기 전에 소프트웨어의 전체를 정확히 묘사 할 수 있다.
현재 우리는 Lifecycle of a Trip 과 기능 요구사항을 정리했다. 이들의 목적은 "분해"이다. 분해된 원자적 요소들을 범주화 하면 컴포넌트가 된다. 컴포넌트 다이어그램은 분해된 요소를 분류해서 종속관계를 포함하도록 범주/계층화 하는 작업이다. 우리는 "승차 전단계", "승차 후 운행 단계", "하차 단계"로 범주화 하기로 했다.

 컴포넌트 다이어그램

Driver Location Manager : 드라이버의 위치를 관리하는 컴포넌트다. 이 기능이 작동하기 위해서는 드라이버의 위치를 수집해서 전송하는 Driver 앱이 있어야 할 것이다. 드라이버 앱은 On-Trip 단계의 Trip Recorder를 위해서라도 위치전송 기능을 내장하고 있을테니, 여기에 손님을 태우고 있는지에 대한 정보만 추가적으로 얻을 수 있으면 된다.

Trip Dispatcher : 사용자 앱이 우버 서비스를 요청하면, Driver Location Manager는 사용자 위치를 기준으로 근거리에서 요청을 받을 수 있는 드라이버를 조회해서 배차한다.

ETA Calculator : 배차가 완료되면, ETA를 계산한다.

Trip Recorder : 운행을 시작하면 운행 과정이 기록된다.

Price Calculator : 운행거리와 운행시간을 이용해서 비용을 계산하여 청구하고 영수증으로 발급한다.

Payment System : 앱애 등록한 지불 수단을 이용해서 비용을 지불한다.

간단해 보이는 한장짜리 다이어그램에서 우리는 많은 것을 알아낼 수 있다.

대략적인 팀 구성을 그려볼 수 있을 것이다. 이 컴포넌트는 각 도메인 지식을 가진 개발리더들이 검토 하게 될테고, 어떤 기술을 사용할지, 기술적 도전과제가 무언지, 무엇을 아웃소싱 할지, 무엇을 먼저 개발해야 할지, 기술 부채로 가져가야 할 것들를 식별 할 수 있다.

Payment System은 아웃소싱 하는게 편할 것이다. 관련 도메인 전문가가 있다면 좋겠지만, 없다고 하더라도 크게 문제되지 않을 것이다. 온라인 Payment, 카드사, VAN 사(미국은 좀 다르겠는데, 비슷한 서비스를 제공하는 기업들이 있을 것이다.)를 찾아서 비즈니스&기술 협력 관계를 맺어야 할 것이다. 중요 정보를 처리해야 하기 때문에 망구성이 중요할 것이다. 우리는 Payment Client를 개발하는 입장이기 때문에, Payment에 대한 깊은 이해를 가진 엔지니어가 필요하지는 않다. 어떤 기술적인 이슈가 있을지를 확인 할 수 있는 개발자가 지원하면 된다. 굳이 풀타임일 필요도 없을 것이다.

위치와 데이터에 관련된 도전 과제 식별을 할 수 있다. 이 서비스는 위치를 기반으로 한다. 특히 드라이버의 위치는 계속해서 추적해야 하기 때문에 상당히 많은 양의 데이터가 발생 할 수 있다. 우리는 위치 데이터를 색인해야 하고, 거의 실시간으로 위치 데이터를 조회 할 수 있어야 한다. 데이터베이스, 스토리지, 캐시 문제를 해결 할 수 있는 개발자가 필요하다.

Map Matcher. 애플리케이션에 기록된 좌표를 실제 세계의 논리적 모델에 일치시킬 수 있어야한다. 지리 정보 시스템을 알고 있어야하며(혹은 학습 할 수 있어야 하며), 실시간&오프라인 알고리즘을 이용해서 위치를 도로에 연결시키고, 드로잉 하는 알고리즘을 개발해야 한다. Map Matcher는 점진적으로 개선해야 하며, 테스트에 많은 시간이 걸리는 작업이므로 이러한 작업을 잘 할 수 있는 독립적인 팀을 구성해야 할 것이다.

Trip Dispatcher, ETA Calculator, Price Calculator 등도 중요한 기술이지만 도전적인 과제로 보이지는 않는다. 안적으로 개발을 끌고 갈 수 있는 팀을 만들어야 할 것이다.

시퀀스 다이어그램

팀은 다양한 수준의 개발자로 이루어지고, 멤버가 바뀔 수도 있는데 누가 들어오든지 내가 개발해야 할 컴포넌트가 하는 일을 이해 할 수 있어야 한다. 시퀀스 다이어그램은 각 컴포넌트 객체들이 서로 상호작용하는지를 표현하는 댜이어그램이다. 시퀀스 다이어그램은 시스템이 어떤 시나리오로 움직이고 있는지를 나타내는 장점을 가지고 있다. 객체간 상호작용을 표현하기 때문에 인터페이스를 설계하는데에도 도움을 준다.

시퀀스 다이어그램과 상호작용하는 각 단계의 API 문서 혹은 gRPC 스펙등이 공유되면, 협업할 준비까지 마칠 수 있다.

Pre-Trip 시퀀스 다이어그램

Pre-Trip 시퀀스 다이어그램을 그려보자. 이 다이어그램은 앱이 실행되어서 주변 차량을 확인 한 후, 차량을 신청해서 ETA를 확인하는 단계까지다. 이 시퀀스 다이어그램 그램에 참여하는 컴포넌트 객체는 Driver Location Manager, Trip Dispatcher, ETA Calculator 그리고 Driver Locations을 색인 저장하고 있는 Car Location Index 가 있다.

시퀀스 다이어그램은 plantUML로 그리기로 했다. vs code의 plantuml 확장을 사용하면 편하게 그릴 수 있다.

 vscode를 이용한 plantUML

plantUML로 그린 다이어그램이다.

 PlantUML 다이어그램

드라이버 위치 업데이트. Uber는 위치기반의 매칭 시스템이다. 따라서 우버 드라이버는 드라이버 앱을 이용해서 자신의 위치를 계속해서 알려줘야 한다. Driver Location Manager 컴포넌트는 우버 드라이버가 전송하는 위치정보를 데이터베이스에 저장한다. 위치 정보는 빠르게 검색할 수 있어야 하기 때문에 실시간으로 색인된다.

주변차량 목록 요청. 사용자가 배차 요청을 하면, Trip Dispatcher 요청이 전송된다. 이 요청은 사용자의 위치 정보를 포함하고 있다. Trip Dispatcher는 Car Location index에 사용자 근처에 있는 드라이버 목록을 조회 해서 리턴한다.

배차 요청. 사용자는 주변의 차량을 확인하고 배차를 요청할 것이다. Trip Dispatcher는 주변 드라이버를 검색해서 드라이버에게 사용자 요청이 있음을 전송한다. 드라이버 중 한명이 배차를 허락하면, ETA Calculator 컴포넌트에 고객이 드라이버를 기다리는 시간을 요청한다. ETA가 고객에게 전송되는 것으로 Pre-Trip 단계가 종료 된다.

주요 기술 Deep Dive

Car Location Index

Car Location Index는 이 서비스에서 가장 중요한 컴포넌트이다. 또한 대량의 위치데이터를 저장하고 색인하고 쿼리하는 것은 상당한 기술적 도전이 될 수 있다.

먼저 우리는 위치정보를 색인&저장&조회 하기 위한 적당한 데이터베이스 시스템을 구축해야 한다. 데이터베이스 시스템이라고 한 이유는 하나 이상의 애플리케이션으로 시스템이 구성되기 때문이다. 이 시스템의 요구사항은 아래와 같다.
  • 대량의 데이터를 저장하기에 충분히 빨라야 한다.
  • 데이터처리를 위해서 빠르게 읽을 수 있어야 한다.
  • 사용자가 요청할 때 "실시간"으로 위치기반으로 조회 할 수 있어야 한다.
대량 저장, 데이터 분석, 실시간 조회를 동시에 (효과적으로) 만족하는 단일한 데이터베이스 솔류션은 없다. 그래서 데이터 모델을 분리하기로 했다.

데이터 스토어는 Cassandara를 사용하기로 했다. 카산드라는 고가용성, 확장성을 제공하는 NoSQL 데이터베이스 시스템으로 아래의 상황에서 적합하다.
  • 쓰기가 읽기를 크게 초과 한다.
  • 데이터가 거의 업데이트되지 않으며 업데이트가 수행되면 멱등성이 된다.
  • Join 이나 집계를 필요 하지 않다.
아래의 로직에 적합하다.
  • 거래 로그
  • 시계열 데이터 저장
  • 주문상태 추적
  • IoT 기기의 상태 및 이벤트 저장
  • 텔레메틱스
카산드라는 훌륭한 데이터이 저장소이고, 대량의 데이터 분석에 사용 할 수 있으나 온라인 실시간 서비스를 위한 데이터베이스는 아니다.

 Uber system

위치 색인을 위해서는 전용의 데이터베이스를 사용 할 필요가 있다. 이 데이터베이스는 대량의 데이터를 저장/처리 할 필요가 없다. 드라이버를 Key로 해서 현재 상태만 업데이트 하면 된다. REDISGeoHash를 이용해서 구성하기로 했다. Spatial function을 제공하는 PostGresql, MySQL을 사용하는 방법이 있겠다. 대량의 쓰기를 처리하기에는 효율적인 데이터베이스 시스템이 아니다. DynamoDB와 같은 NoSQL을 사용하는 방법도 있는데, 비용이나 속도 측면에서 역시 Redis가 가장 나을 것 같다. 인-메모리 디비라서 데이터를 안전하게 저장할 수 없다는 단점이 있지만, 이 시나리오에서는 (현재의 상태만 알 수 있으면 된다)저장할 필요가 없기 때문에 크게 이슈될게 없다.

 Car Location Index

드라이버는 자신의 위치와 상태를 계속 전송하고, 실시간으로 색인된다. 드라이버 ID가 Key가 될 것이다. 사용자는 자신의 위치를 Center로 해서 특정 반경의 드라이버를 조회한다. AWS를 사용한다면 Cassandra 워크로드를 위한 keyspaces(Apache Cassandra 호환)을 검토해 보자.

어떠한 데이터베이스든 시스템에 문제가 생기면 서비스가 중단된다. 이경우 빠르게 복구할 수 있느지가 문제다. 따라서 ElastiCache가 중단뒤었을 때, 빠르게 복구 할 수 있느냐가 관건인데, 위치정보는 "가장 최근의 위치"만 저장하고 있으면 되므로 ElastiCache의 복구에 걸리는 시간 + Driver가 자신의 위치를 재 전송하는데 걸리는 시간의 지연이 생길 것이다. 이 복구시간도 없애거나 줄이고 싶다면, Keyspaces에서 직접 쿼리해야 할 것이다.

아래와 같은 다양한 선택지가 있을 수 있다. 어떤 기술을 사용해야 할지는 상황에 따라 다를 것이다.
  1. Cassandra vs DynamoDB : DynamoDB도 괜찮은 대안이 될 수 있다.
  2. ElastiCache : 온라인 색인 서비스에는 ElastiCache 만한게 없는 것 같다.
  3. 샤딩 : 적절한 샤딩키를 선택해서 데이터를 분산한다. 문제가 시스템 전체로 확산되는 것을 막을 수 있을 것이며, 지역별로 성능을 조절 할 수 있다.

Sharding 정책

샤딩정책에 대해서 자세히 알아보자. 샤딩은 클러스터의 여러 노드에 데이터를 분할 저장하여 용량을 향상시키며 각 노드의 데이터의 양을 줄일 수 있다. 또한 디스크 I/O의 병목현상을 제거하여 성능을 향상시킬 수 있다. 데이터에 대한 일관성있는 저장과 접근을 위해서 샤딩키를 사용한다. 샤딩키를 만드는 알고리즘에 따라서 효율적인 클러스터를 구성 할 수 있다.

품질 향상 성능 확장성
품질 저하 유효성
관련 전술 여러 노드에 걸친 샤드 데이터의 생성
가장 효율적인 샤드키 생성 알고리즘은 아마도 컨시스턴트 해싱(consistent hasing)일 것이다. 이 해싱을 이용하면, 노드가 늘어나거나 줄어들더라도 놀어난 노드의 갯수에 대해서 일정하게 분산할 수 있다. k가 키의 갯수이고 n이 노드의 갯수라면 (k*1/N) 만큼만 재분배된다. 여기에 가용성이 필요할 경우 리플리카 해시를 미리 구성 할 수 있다.

이 방식은 아래와 같은 특징을 가질 것이다.
  1. (예를들어 driver id를 해시키로 할 경우) 모든 노드에 위치정보가 분산될 것이다. 이 경우 어떤 노드에 문제가 생기더라도, 드라이버를 찾는데 문제 없을 것이다. 노드가 10개이고 이중 1개에 문제가 생길 경우 이론적으로 1/10 만큼 덜 검색이 될 것이다.
  2. 모든 노드에 쿼리를 해야 한다. 클러스터가 100개의 노드로 구성된다면, 100번 쿼리를 해야 한다. 비효율적이 될 수 밖에 없는데, 이런 문제는 특정 지역단위로 클러스터를 유지하는 방식으로 문제를 완화 할 수 있을 것이다. 논리적으로 2개의 샤드키를 가지는 방식이 될 것이다. 효율적인 샤드키를 설계하는게 핵심 과제다.

S2

Uber는 구글 S2 라이브러리를 이용해서 구체에서의 위치정보를 처리한다. S2는 전지구를 완전히 덮을 수 있는 셀로 쪼갠다. 각 쉘은 셀ID를 가지고 있다. 위도와 경도를 입력하면, 해당 위도를 포함하는 셀ID를 얻을 수 있다.

 S2

예를들어 사용자가 있는 Center를 중심으로 1KM이내에 있는 드라이버를 알고 싶다면, 위에서와 같이 반경 1km 반경을 포함하는 셀ID를 가져올 수 있다. 이 셀ID로 검색을 하면 된다.

시스템 상세 디자인

이제 시스템을 상세히 디자인 해보자. 시스템은 AWS를 기준으로 디자인한다. AWS 클라우드를 기반으로 설계한 이유는 "최근 몇년 동안 AWS 클라우드만 사용" 해 왔기 때문이다.

 시스템 디자인

Driver에서 보고하는 모든 위치정보는 로드밸런서를 통해서 kafka로 전송된다. 위치정보를 기반으로 하는 서비스들은 전부 kafka 컨슈머로 등록해서, 데이터 흐름을 관리 할 것이다. Kafka 시스템은 직접 구축하지 않고 관리형 Kafka 서비스인 MSK(Managed Streaming for Apache Kafka)를 사용하기로 했다. AWS의 경우 kinesis라는 대안도 있다. kinesis는 AWS의 다른 서비스들과의 연결성에서 장점을 가지고 있지만 kafka 보다 성능 제약이 있다. 우버 시스템의 경우 드라이버로 부터 대량의 메시지가 들어올 것으로 예상되는 바 kafka를 선택했다. kinesis는 초당 수천, kafka는 만 단위이기 때문에 유의미한 성능 차이가 있다.

Kafka는 메시지 큐이면서 메시지 버스역할을 수행하는데, 새로운 서비스가 필요 할 때마다 Kafka 컨슈머 형식으로 개발하면 되므로 유연한 데이터 처리 구조를 만들 수 있다.

Driver Location Indexer도 Kafka의 컨슈머로 작동한다. Kafka 메시지큐로 부터 드라이버의 위치정보를 읽어서, REDIS Cluster에 저장한다.

Trip Dispacher는 Location indexer로 부터, driver 목록을 조회해서 서비스 한다.

ETA는 시간에 따라서 늘어나거나 줄어들 수 있기 때문에 사용자의 위치와 드라이버의 위치를 이용해서 정보를 갱신 할 필요가 있다.

 ETA Calculator

효율적으로 ETA를 관리하기 위한 방법을 고민해 볼 필요가 있겠다. 간단한 방법은 Driver Location Index가 처리하는 방법이다. 드라이버는 자신의 위치정보와 상태 정보를 함께 전송 한다. 사용자의 요청을 수락해서 사용자의 위치로 이동할 경우, 이 상태를 함께 전송한다. Driver Location Indexer는 상태를 확인해서 별도의 테이블에 저장한다. 위치정보는 카산드라 등에 저장되고 있으므로, REDIS면 충분할 것이다.

중요한 것은 ETA 단계에서는 별도의 데이터베이스가 필요하다는 점이다. Location Indexer의 경우 색인에 사용되는 키는 위치이기 때문에, 이 데이터베이스로는 각 드라이버와 유저의 ETA를 계산할 수 없다.

ETA 단계에 들어섰을 경우 드라이버는 Kafka에 자신의 위치를 보고하는 것과 별개로 ETA Calculator로 보내는 방법을 생각해 볼 수 있다. ETA 단계로 들어설 경우 도로사정 등에 따라서 주요 변경사항이 있을 때, 빠르게 사용자에게 ETA를 알려줄 필요가 있는데 Kafka는 대응능력이 떨어질 수 밖에 없다. 이 방법은 드라이버와 사용자를 직접 연결하므로 좀 더 나은 서비스를 제공 할 수 있다.

Payment 시스템은 북미의 상황은 잘 모르겠으니, 한국 상황으로 본다면 VAN(Value Added Network)사 혹은 PG(Payment Gateway)와 연동하는 작업을 해야 할 것이다. VAN의 경우 특정 VAN사와 계약을 맺고 전문통신(요즘에는 HTTP 기반으로 이동하고 있다.)을 통해서 결제시스템을 구축할테고, PG사의 경우 PG사에서제공하는 API, SDK 등을 이용해서 결제 시스템을 구축하게 될 것이다.

서비스에서 발생하는 모든 로그는 kafka를 통해서 ElasticSearch에 색인되고 Kibana를 이용해서 모니터링 및 분석하는 식으로 구축 될 것이다.

마이크로서비스

이 시스템은 7개의 컴포넌트들을 가지고 있다. 각 컴포넌트들을 모아서 모노리식으로 구축 할 수도 있을테고, 마이크로서비스 형태로 구축 할 수 있을 것이다. 각 아키텍처는 고유의 장점과 단점을 가지고 있으므로 어떤 아키텍처를 사용 할 건지 결정한다. 이 아키텍처는 원칙이 될 것이다. 여기에서 원칙은 베이스라인을 제공하는 것으로, 조직의 구성, 구성원의 역량, 시간, 비용, 개발부채 등을 고려하여서 적절한 수준으로 구축하면 된다.

우버 시스템의 경우 마이크로서비스를 원칙으로 가져가는게 유리할 것이다. MSA는 예측 할 수 없는 시장에서 고객의 피드백을 기반으로 빠르게 적응&진화 해야 하는 서비스에 적합한 모델이다.
  • MSA는 각 서비스들을 독립적으로 개발 할 수 있게 한다. 각 서비스들은 API, 메시지 큐등으로 느슨하게 연결되기 때문에 유연하게 가치를 코드로 만들어서 배포 할 수 있다.
  • 각 서비스별로 적절한 플랫폼과 기술을 사용 할 수 있다.
  • 컴포넌트의 복잡도를 감소 시킬 수 있다. 물론 분산된 컴포넌트들의 연동과 통합의 복잡도가 증가하는 문제가 있기는 하지만, 클라우드와 Kubernetes 등에 분산시스템이 가지는 복잡도를 위임 할 수 있게 되면서, MSA로 얻는 이득이 더 커졌다. MSA는 오래된 개념이다. 이 개념이 최근에 조명 받는 이유는 이러한 기술적 발전이 뒷받침 되었기 때문이다.
 MSA

Driver Location Manager, Trip Dispatcher, ETA Calculator, Trip Recorder, Location Indexer, Payment System 등 주요 컴포넌트들이 권한과 책임을 가지는 팀들이, 필요한 가치와 기능을 탐색하고 적절한 기술을 사용하여서 개발하고 배포해서 시장에 내놓는 모습을 생각하면 되겠다.

시스템의 확장

우버에서 발생하는 데이터의 양, 크기는 에측 할 수 없다. 서비스가 실패 할지 성공 할지 알 수 없으며, 우버의 경우 시간과 공간에 따라서 트래픽이 들쭉 날쭉 할 것이다. 따라서 시스템은 유연하게 확장 혹은 축소 할 수 있어야 한다.

다행히 클라우드 환경에서는 비교적 수월하게 목표를 달성 할 수 있다.
  • EC2 를 사용한다면, ELB(로드밸런서)와 오토 스케일링(Auto scaling)그룹을 이용.
  • EKS(Kubernetes)를 사용한다면, kubernetes의 Autoscaler 컨트롤러를 이용하여 Pod를 늘이거나 줄일 수 있다. 물론 클러스터 차원에서 노드를 스케일링 할 수 있다.
  • Fargate 의 경우 Service Auto Scaling 구성을 선택하여서 서비스를 늘리고 줄일 수 있다. Fargate는 서버리스이니, 노드 관련 스케일링을 신경쓸 필요도 없다.
  • DynamoDB의 경우 용량은 무한대라고 볼 수 있다. 처리 능력에 대한 오토 스케일링 정책이 필요한데, AWSApplication Auto Scaling 서비스등을 사용하여 실제 트래픽 기반으로 동적으로 조정 할 수 있다.
  • RDBMS는 NoSQL에 비해서 오토 스케일링에 제약이 있으므로 이를 고려해서 데이터베이스 엔진을 선택해야 한다. MySQL이나 PostgreSQL을 사용하고 있다면 RDS Aurora 를 고려할 수 있다. RDS Aurora는 컴퓨팅 노드와 스토리지가 분리되므로 컴퓨팅 노드를 늘리고 줄이는 것으로 오토스케일링 정책을 원할히 적용 할 수 있다.

정리

  1. Use Case를 다이어그램으로 만든다.
  2. use Case 다이어그램으로 레이어를 나누고 각 레이어에 컴포넌트를 배치한다.
  3. 컴포넌트의 작동방식을 시퀀스 다이어그램으로 표현한다.
  4. 시퀀스 다이어그램에서 컴포넌트의 연결 관계가 표현된다. 연결은 gRPC, 메시지큐, API로 정의 할 수 있다.
  5. MSA로 개발한다. MSA가 가지는 고유의 복잡도는 클라우드, 컨테이너 시스템에 위임(떠넘기기)한다.
  6. 컴포넌트가 식별되면, 개발팀을 설정 할 수 있다. 지금까지의 만든 기획문서, 설계문서, 다이어그램을 통해서 개발팀은 전체 시스템에 대한 높은 이해도를 가지게 된다. 시퀀스 다이어그램으로 정의한 컴포넌트간 인터페이셔 영역의 개발문서(gRPC 스펙, API 문서, 데이터 형식)를 잘 만드는 것으로 원할한 커뮤니케이션 환경을 만들 수 있다.
  7. 컴포넌트들 마다 주요한 도전과제들이 있다. 이러한 도전과제는 팀단위로 혹은 팀이 협업하여서 해결 한다. 도전과제의 솔류션이 처음부터 완벽할 필요는 없다. 다른 코드와 마찬가지로 점진적으로 개선하면 된다.
  8. Kafka는 메시지 허브로 중요한 역할을 한다.

참고