Uber’s Real-time Data Intelligence Platform At Scale: Improving Gairos Scalability/Reliability 번역



배경

  • 운행 요청 수(# of ride requests), 운행 가능한 운전자 수(# of drivers avaiable), 날씨, 스포츠 게임 같은 실시간 데이터는 운영팀이 서지 프라이싱(Surge Pricing), 최대 배차 ETA 범위 계산, 우버 플랫폼에서 사용자 경험을 개선하기 위한 수요 공급 예측 등을 진행할 때 데이터에 기반한 결정을 내릴 수 있도록 도와줌
  • 배치로 처리하는 데이터는 중장기 트렌드를 파악해 강력한 인사이트를 제공할 수 있지만, 우버 서비스는 스트리밍 데이터(streaming data)와 실시간 전처리(real-time processing)를 사용해 분 단위로 액션 가능한 인사이트를 만들 수 있음

  • Uber의 실시간 데이터 처리, 저장소 겸 쿼리 플랫폼인 Gairos를 구축해 규모에 맞게 간소화되고 효율적인 데이터 탐색을 지원함
  • 이를 통해 팀은 데이터 인텔리전스를 통해 우버 마켓플레이스의 효율성을 더 잘 이해하고 개선함. 사용 사례로 서지 프라이싱, 최대 ETA 범위 계산, 수요/공급 예측 등이 있음
  • Garios가 계속해서 확장/ 사용될 수 있도록 성능을 최적화할 수 있도록 확장성, 안정성, 지속 가능성을 가지는 플랫폼을 재구성함. 이런 최적화 전략 준 가장 큰 영향을 미친 두 방법은 데이터 중심 샤딩과 쿼리 라우팅, 지능형 캐싱(Intelligent caching)임

  • 데이터 기반 샤딩 및 쿼리 라우팅을 활용해 이전보다 4배 더 많은 쿼리를 동시에 실행할 수 있음. 일부 중요한 클러스터는 월 1회 운영 중단을 겪었지만 최근엔 월 0회로 안정화됨
  • 2018년 12월 플랫폼 출시 이후에, 지능형 캐싱으로 10배 이상 플랫폼을 확장해 캐시 적중률이 80%에 육박함

역자 : 캐시 적중률(Cache hit ratio)

Cache Hit : 참조하려는 데이터가 캐시에 존재할 때 캐시 히트

Cache Miss : 참조하려는 데이터가 캐시에 존재하지 않을 때 캐시 미스


왜 Gairos가 필요한가?

  • 우버 에코시스템엔 실시간 데이터를 활용하는 서비스가 있음
    • 각 팀은 자체 데이터 파이프라인을 관리하고, 팀의 목적에 맞는 쿼리를 실행함
    • 각 팀은 시스템 최적화에 집중하는 대신에 여러 요소에 집중해야 함(모니터링이나 경고 살펴보기, 스트리밍 처리 프레임워크 관리 등)
    • 이런 사례를 쉽게 온보딩할 수 있도록 통합된 실시간 데이터 처리, 저장소, 쿼리 플랫폼인 Gairos를 만들기 시작함
  • 사용자는 실시간 데이터 시스템의 일반적인 업무 대신에 비즈니스 로직에 집중할 수 있게 됨
  • 가이로스는 다음 목적을 충족함
    • 사용자가 로우 레벨의 디테일을 몰라도 쿼리할 수 있도록 해줌(로우 레벨의 디테일 : 다른 데이터 소스, 쿼리 최적화, 데이터 전처리 로직, 스키마 인덱스 등)
    • 가이로스 팀이 플랫폼 사용자에게 영향을 주지 않고 데이터 레이어를 실험하고 개선할 수 있게 해줌(도메인별로 데이터 추상화 레이어 사용)
    • 높은 처리량, 로우 레이턴시를 또는 배치 처리/모델링 기반 호출을 처리할 수 있도록 최적화됨


Gairos Overview

아래 그림 1처럼 가이로스는 아파치 카프카 토픽으로 데이터를 주입받고 Elasticsearch 클러스터에 데이터를 저장함

그림 1: 가이로스의 간단한 아키텍처와 구성 요소

  • Garios query service는 엘라스틱서치 클러스터의 데이터를 쿼리하기 위한 게이트웨이
    • 가이로스 클라이언트는 가이로스 쿼리 서비스에 쿼리를 보내고 실시간으로 데이터를 획득함
    • 데이터는 아파치 하이브와 프레스토에서 진행할 장기 분석(long-term analysis)을 위해 HDFS로 저장됨
  • 가이로스엔 다음 시스템으로 구성됨 : Apache Kafka, Gairos ingestion pipelines, Elasticsearch clusters, Gairos query service 등.
    • 이중 하나라도 문제가 발생하면 사용자는 영향을 받을 것이고, 나쁜 경험을 하게 된다.
  • 우버의 마켓플레이스 비즈니스는 점점 확장되면서 가이로스 데이터 파이프라인 사용자 수가 증가하고 있음. 새로운 비즈니스 확장을 지원하기 위해 가이로스에 더 많은 데이터 소스를 추가할 필요가 있음


Uber의 사용 사례

우버는 다음 상황에서 가이로스를 사용해 인사이트를 발견함

  • Dynamic Pricing : Surge Pricing 서비스는 수요와 공급 데이터를 육각형 기반으로(H3) 특정 위치와 시간에 서지 비율을 계산함
  • Driver Movement는 실시간 수요와 공급 데이터를 사용해 드라이버 급증과 드라이버를 위한 탄소 배출 제안을 생성함


  • 우버는 각각의 trip을 세션으로 언급하는데, 고객이 우버 앱을 열 때 시작된다. 이 동작은 드라이버가 ride를 실제로 수락한 시점부터 trip이 완료된 시점까지 데이터 이벤트를 생성함. 시스템의 복잡성과 규모를 고려할 때, 이런 데이터는 여러 이벤트 스트림에 분산됨.

역자 : ride와 trip을 혼재해서 사용하는데, 동일한 의미라고 파악해도 될 듯. 택시를 탑승해서 내리는 경우를 ride라 표현

  • 예를 들어, 드라이버가 우버 앱을 열면 드라이버 이벤트 스트림에서 이벤트를 실행함
    • 앱에선 해당 지역에서 제공되는 Trip의 종류(UberPOOL, UberX, UberBlack 등)와 서지 프라이싱 시스템에서 생성한 가격을 보여주며, 각 가격은 impression 이벤트 스트림에서 분리된 이벤트로 나타남
    • 드라이버가 trip을 수락하면 그 요청은 배차 시스템으로 전달되고, 고객과 드라이버를 매칭해 trip에 차량을 배차함
    • 드라이버/파트너가 고객을 픽업하면 앱에서 ‘pickup completed’ 이벤트를 배차 시스템에 보내고, trip을 시작함
    • 드라이버가 도착지에 도착해 고객을 앱에 내려줬다는 표시를 하면 “trip completed” 이벤트를 보냄
  • 이런 일반적인 trip 라이프사이클은 라이더 앱(고객), 드라이버 앱, 우버의 배차 서버에서 생성된 이벤트와 함께 6개의 개별 이벤트 스트림에 걸쳐있을 수 있음
  • 다양한 스트림을 처리하고 실시간으로 쿼리해 서비스가 데이터 기반 인사이트로 액션을 하도록 만드는 것은 매우 도전적임


드라이버 상태 전환

다음 그래프(그림 2)는 특정 시간에서 샌프란시스코 드라이버들의 집계된 상태 전환을 보여줌

그림 2: 배차 쿼리 서비스는 가이로스부터 샌프란시스코의 드라이버 상태 전환 데이터를 추출해서 화면에 보여줌


단일 드라이버의 상태 전환

다음 그래프(그림 3)는 특정 시간에서 샌프란시스코의 단일 드라이버의 모든 상태 전환을 보여준다. 쿼리는 지정된 드라이버 UUID 필터가 하나 더 있다는 점을 제외하고 이전 쿼리와 동일함

그림 3: 배차 서비스는 특정 드라이버의 상태 데이터를 얻고 화면에 보여줌


지역별로 그룹화된 드라이버 이용률

다음 그래프 (그림 4)는 지리적 위치별 드라이버 이용률을 보여줌

그림 4: 지역별로 그룹화된 운전자 이용률

마지막으로 가이로스의 데이터를 사용해 서지 프라이싱이 어떻게 동작하는지 살펴보자.

  • 너무 많은 고객들이 ride를 요청하는 경우, 고객을 태울 수 있는 차량이 도로에 충분하지 않는 경우가 있음(수요와 공급의 불균형).
    • 예를 들어 나쁜 날씨, 러시 아워, 특별한 이벤트가 있는 날엔 많은 수의 사람들이 동시에 우버를 타고 싶을 수 있음
    • 수요가 매우 높은 경우, 요금이 인상될 수 있음. 이 시스템을 Surge Pricing이라 부름
  • H3로 정의된 육각형에 서지 비율을 계산하기 위해 가이로스의 최신 데이터에서 요청 수(수요) 및 사용 가능한 드라이버(공급) 수를 쿼리함
    • 이런 데이터는 가격 모델에 입력되며 가격 모델은 해당 위치의 서지 비율을 생성함
    • 그림 5는 게임이 있을 때 Oakland 경기장 주변에 있는 육각형의 서지 비율을 보여줌

그림 5: 경기가 있을 때 오클랜드의 여러 육각형의 서지 비율


확장성/신뢰성 문제

가이로스의 첫 버전에는 몇 가지 기술적 문제와 예상하지 못한 문제가 동반됨.

  • 가이로스를 더 많이 활용하면서 시스템에서 스트리밍 되는 데이터 양이 증가했음
    • 가이로스에서 제공하는 쿼리 가능한 데이터의 총 크기는 1500TB고, 프러덕션 파이프라인 수는 30개 이상임
    • 총 레코드 수는 4.5조개 이상이고 총 클러스터 수는 20개 이상
    • 백만개 이상 이벤트가 매 초마다 가이로스로 들어온다. 점점 더 많은 케이스를 지원하기 위해 시스템을 더 안정적이고 확장 가능하며, 지속 가능하게 만드는 것이 더 중요해지고 있음


가이로스를 확장하기 시작했을 때 드러난 기술적 과제는 다음과 같음

  • 동일한 클러스터를 여러 곳에서 공유하는 경우 클러스터가 불안정해질 수 있음
    • 한 사용 사례에서 큰 변화가 있는 경우 해당 클러스터의 다른 사례에 영향을 미칠 수 있음
    • 예를 들어, 입력 데이터 사이즈가 2배로 증가하면 다른 사용 사례의 데이터 가용성에 영향을 미칠 수 있음
  • 수집 파이프라인 지연. 모든 실시간 파이프라인에서 발생할 수 있는 문제
    • SLA(서비스 수준 계약)는 일반적으로 몇 초에서 몇 분까지 매우 엄격함
    • 파이프라인의 구성 요소가 느려지면 지연 및 SLA 누락이 발생할 수 있음
  • 일부 클라이언트의 트래픽 급증으로 쿼리 성능이 저하됨
    • 멀티 테넌트(Multi-tenant) 시스템이므로 갑작스러운 트래픽 급증은 동일한 클러스터에서 실행되는 쿼리 일부에 영향을 줄 수 있음
  • 일부 데이터 소스는 더 이상 사용되지 않음
    • 일단 가이로스에서 시작되면, 해당 케이스의 사용량을 자동으로 확인할 수 있는 방법이 없음
    • 데이터를 더 사용하지 않으면 다른 케이스를 위해 리소스를 확보하는 것이 좋음
  • 일부 무거운 쿼리로 인해 전체 Elastsearch 클러스터가 느려짐
  • ES 클러스터 마스터 노드가 다운됨. 네트워크가 안정적이지 않거나 메타 데이터의 크기가 너무 커서 관리할 수 없는 등 다양한 이유가 있음
  • 일부 노드의 CPU 부하가 큼
    • 이런 노드는 hotspot 이슈를 겪음
    • hotspot 이슈는 리소스(CPU/Memory/Network)가 합리적으로 처리할 수 있는 것보다 더 많은 부분 또는 읽기/쓰기 트래픽을 처리하고 있는 경우를 의미함
  • 일부 노드가 충돌함
    • 디스크 고장이나 다른 하드웨어 고장 때문일 수 있음
  • 일부 조각이 손실됨
    • 여러 노드가 동시에 수행되고 이 노드들 중에서 특정 노드에서만 샫ㅡ를 할 수 있는 경우. 샤드의 데이터를 잃어버릴 수 있음
  • 우버의 On-call(당직) 엔지니어들은 자주 연락받으며 이런 파이프라인과 시스템을 유지하는데 드는 비용이 많이 듬

  • 가이로스 첫 출시할 당시엔 사용자들의 피드백 루프가 없었던 것이 핵심 문제였음
    • 지정된대로 사용되고 있는지, 변경 사항(트래픽 패턴이 변경되거나 쿼리 패턴이 변경되는 경우)을 잘 적응하는지 등을 확인하지 않음
    • 가이로스 self-optimization 프로젝트에서 그림 6처럼 루프를 만들고 사용자 쿼리가 최적화를 해서 가이로스가 더 안정적이고, 확장 가능하고 지속 가능하도록 만들었음

그림 6: 새로운 아키텍처는 빨간 화살표로 표시된 새로운 데이터 흐름으로 가이로스의 루프를 닫음

가이로스 플랫폼을 다용한 사례에서 안정적이고 확장 가능하게 만들고, 유지 비용을 줄이기 위해 시스템을 더 효율적이고 지능적으로 만들어야 했음


가이로스 최적화 아키텍처

그림 7: 이 아키텍처는 플랫폼의 데이터 흐름을 보여줌. 빨간색 화살표는 새로운 데이터 흐름을 나타내며, 녹색 화살표는 두 가지 새로운 최적화, 즉 Gairos Query Analyzer와 Optimization Engine을 나타냄

수정된 아키텍처는 그림 7과 같음. 시스템의 주요 구성 요소는 다음과 같음

  • Clients : 가이로스 클라이언트는 서비스, 대시보드, 데이터 분석가 등
  • Apache Kafka : 메세지 큐 시스템으로 서비스, RT-Gairos 쿼리, Gairos 플랫폼 메트릭/이벤트에 대한 메세지를 담음
  • Gairos-Ingestion : Gairos-Ingestion 컴포넌트는 여러 소스의 데이터를 수집하고 가이로스에게 데이터를 보냄
  • Elasticsearch Cluster : 이 클러스터들은 Gairos-Ingestion에서 보낸 데이터를 저장함
  • RT-Gairos(Real-time-Gairos) : RT-Gairos는 가이로스 쿼리 서비스. 모든 Elasticsearch 클러스터의 게이트웨이 역할을 함
  • Query Analyzer : RT-Gairos에서 수집된 쿼리를 분석하고 최적화할 수 있는 부분을 제공함
  • Optimization Engine : Gairos Optimization Engine은 쿼리 인사이트와 통계에 기초해 Gairos-Ingestion, Elasticsearch Cluster/index 설정, RT-Gairos를 최적화함
    • 예를 들어, SLA를 99% 충족하기 위한 Gairos-Ingestion 파이프라인의 최소 컨테이너 수는 얼마인가? 쓰기/읽기 트래픽을 처리할 수 있도록 몇 개의 샤드를 사용해야 하는가?

그 다음엔 각 구성 요소가 전체 Gairos 생태계에서 어떤 역할을 하는지 자세히 살펴봄


Clients

  • 클라이언트는 서비스 또는 데이터 분석가 같은 유저일 수 있음
  • 서비스 클라이언트는 Dynamcic Pricing, Trip 예측 서비스 같은 사용자 요청을 처리하는 모든 리얼타임 서비스들을 포함함
    • 이 서비스들은 다운스트림 서비스와 파이프라인에 보내기 위해 아파치 카프카에 이벤트를 보낸다
    • 서비스가 들어오면, 결정을 내리기 위해 가이로스의 데이터를 사용한다
    • 예를 들어 트래픽이 많은 이벤트 동안 수요와 공급을 예측하기 예측을 개선하기 위한 계속 데이터를 쿼리할 수 있음
    • 다이나믹 프라이싱(서지 프라이싱) 서비스는 수요, 공급 일부 예측 입력에 기초해 서지 비율을 결정하기 위해 Gairos를 활용할 수 있음


Apache Kafka

  • 아파치 카프카는 클라이언트가 이벤트 스트림을 Publish하고 Subscribe할 수 있는 분산 스트리밍 플랫폼
  • 모든 실시간 서비스는 다운스트림 서비스/파이프라인에서 사용할 수 있도록 이벤트를 보낼 수 있음
  • RT-Gairos를 통해 Gairos에서 실행되는 모든 쿼리를 수집할 때 사용함


Gairos-Ingestion(Processing Layer)

  • Gairos-Ingestion는 다양한 소스의 데이터를 처리해 이를 Gairos에 보내는 수집 프레임워크
  • Apache Spark 스트리밍이 일부 데이터 소스에서 사용됨


Elasticsearch(Gairos Storage Layer)

  • Gairois 저장소 Layer인 Elasticsearch는 Gairos-Ingestion에서 사용하는 30개 이상의 데이터 소스의 데이터를 인덱싱하고 가이로스 클라이언트가 쿼리할 수 있도록 준비함


RT-Gairos(Query Layer)

  • RT-Gairos는 Gairos의 게이트웨이 역할
  • 모든 쿼리는 Gairos Storage Layer에 저장되기 전에 Query Layer를 통과함
  • RT-Gairos는 접근 권한을 제어하고, 라우팅을 제공하고, 쿼리 결과를 캐싱함
  • RT-Gairos는 Gairos의 모든 쿼리를 수집하고 Apache Kafka Topic으로 보냄


Query Analyzer

  • Query Analyzer는 RT-Gairos에서 수집된 쿼리를 분석하고 인사이트를 생성해 Gairos Optimization Engine에 제공함
  • 우선 간단한 기술(필터링된 메트릭, 집계, 시간 범위, 샤드 수, 인덱스 수)을 활용하여 일부 쿼리 패턴을 생성함


Optimization Engine

  • Query Analyzer로 얻은 시스템 통계와 쿼리 인사이트를 바탕으로 Gairos Optimization Engine은 lifetime 지식을 사용해 일부 최적화를 권장함
    • Ingestion 파이프라인, RA-Gairos, Elasticsearch 등의 Gairos의 설정을 업데이트함
  • 일부 설정 변경은 변경을 적용하기 전에 KPI 개선되는지 확인하기 위해 벤치마킹 테스트가 필요할 수 있음
  • 예를 들어 주어진 데이터 소스에 가장 적합한 샤드 수는 얼마인가? 인덱스 벤치마킹 서비스가 나옴


Index Benchmarking Service

  • Gairos의 설정 최적화를 위해, 정해진 KPI(읽기/쓰기 처리량, 지연 시간, 메모리 사용량 등)를 기준으로 다른 설정을 비교하는 벤치마킹 도구를 통합해야 함

그림 8은 가이로스 벤치마킹 서비스의 다양한 구성 요소를 나타냄

그림 8: 벤치마킹 서비스는 테스트를 수행하고 테스트 결과를 저장함

구성 요소

  • Elasticsearch Production Clusters
    • Elasticsearch 프러덕션 클러스터에는 부하 테스트를 위해 스테이징에서 복사한 데이터가 포함되어 있다. 베이스라인을 벤치마킹의 기준으로 사용할 수 있음
  • Elasticsearch Staging Clusters
    • 이 클러스터는 시험 데이터, 즉 실험 목적으로 무작위로 생성된 데이터나 생산 데이터를 저장하는데 사용됨
  • Benchmarking Service
    • Benchmarking Service는 다른 인덱스 설정을 받고, 다른 설정 사이에서 벤치마크 테스트를 실행함
    • 테스트가 완료되면 테스트 결과는 다른 서비스에서 사용할 수 있음
  • Load Test Tool
    • 많은 읽기/쓰기 요청이 오는 경우, 로드 테스트 툴이 다른 read/write QPS를 시뮬레이션하고 KPI를 기록함
    • Read는 프러덕션의 RT-Gairos에서 수집된 쿼리임
    • Write는 프로덕션 환경 또는 카프카로 직접 퍼블리싱하는 곳에서 시뮬레이션됨
  • Gairos Benchmarking Service는 Gairos Optimization Engine의 요청을 받아 벤치마킹 테스트를 실시함. 벤치마킹 서비스는 프러덕션에서 스테이징에 이르는 전체 히스토리 대신 단일 index를 복제해서, 성능도 향상시키고 리소스 사용을 줄임.
  • 단일 index의 성능이 개선되면, 서로 다른 인덱스에 대한 쿼리가 독립적으로 실행되기 때문에 해당 데이터 소스에 대한 전반적인 성능이 개선됨
  • 최적화 엔진은 테스트 결과를 평가한 후 생산 시 인덱스 설정 변경 여부를 결정할 수 있음

그림 7에 묘사된 것처럼, 전체 시스템은 다음 단계로 진행됨

  • 가이로스 클라이언트는 데이터를 얻기 위해 RT-Gairos에 요청을 보냄
  • Gairos-ingestion은 아파치 카프카 토픽에서 데이터를 수집하고 Elasticsearch 클러스터에 데이터를 넣음
  • Gairos는 데이터를 인덱싱하고 쿼리할 준비함
  • RT-Gairos는 쿼리를 Elasticsearch 쿼리로 변환하고 Elasticsearch 클러스터에 데이터를 쿼리함
  • RT-Gairos는 데이터를 다시 클라이언트로 보냄
  • RT-Gairos는 쿼리 정보를 아파치 카프카 토픽으로 보냄
  • Elasticsearch 클러스터 데이터를 정기적으로 샘플링하고 정보를 아파치 카프카 토픽으로 보냄
  • 쿼리 분석기는 분석을 위해 아파치 카프카 토픽에서 쿼리 정보를 가져옴
  • 최적화 엔진은 분석을 위해 아파치 카프카 토픽에서 가이로스 플랫폼 통계를 가져옴
  • 최적화 엔진은 쿼리 분석기에서 가이로스 쿼리 인사이트를 가져와 수행할 작업이 있는지 확인함
  • 최적화 엔진은 최적화 계획을 Gairos 플랫폼 여러 구성 요소로 Push함


최적화 전략

실시간 인텔리전스 플랫폼을 최적화할 때 사용할 수 있는 다양한 최적화 전략을 사용함

  • 샤딩 & 쿼리 라우팅
  • 쿼리 패턴과 signature에 기반한 캐싱
  • 인덱스 Merge
  • 무겅운 쿼리 처리하기
  • 인덱싱 템플릿 최적화
  • 샤드 수 최적화
  • 인덱스 범위 바운드
  • 사용하지 않는 데이터 제거


샤딩 & 쿼리 라우팅

  • 샤딩은 같은 키를 가진 데이터를 하나의 샤드에 두도록 파티셔닝하는 방법
  • Elasticsearch 인덱스에 저장할 때, 문서를 올바른 샤드에 넣을 수 있는 키가 제공되어야 한다
  • 데이터를 쿼리할 때, 쿼리에 키가 지정되었다면 모든 샤드에 보내지 않고 특정 샤드에만 쿼리를 보낼 수 있음
    • 쿼리에 필요한 노드의 수를 줄이면 레이턴시와 복원력이 향상될 수 있음(만약 단일 노드가 다운되었지만 쿼리에 필요하지 않는 경우엔 문제가 생기지 않음)
  • 만약 샌프란시스코의 모든 기사들에게 프로모션 제안을 보내고 싶은 경우 기사들의 리스트가 필요함
    • 그림 9처럼 샌프란시스코의 모든 드라이버를 쿼리함
    • 위 이미지에는 데이터는 도시 기반으로 샤딩되어 있지 않으므로 쿼리는 4개의 샤드에서 드라이버가 avaiable한지 확인해야 함
    • 아래 이미지에는 데이터가 도시 기반으로 샤딩되어 있음
    • 쿼리는 샌프란시스코 드라이버를 포함하는 샤드에서 데이터를 확인할 수 있음
    • 쿼리를 보내는 수는 4개에서 1개로 줄어든 것을 알 수 있음

그림 9: SF의 드라이버에 대한 쿼리는 샤딩 없이 모든 샤드를 쿼리해야 하며, 샤딩은 한 샤드에 대해서만 쿼리해야 한다.

  • 샤딩의 일반적인 문제는 핫스팟 문제(일부 샤드는 다른 샤드보다 훨씬 많은 write/query 트래픽을 처리해야 함)
    • 예를 들어, 도시 ID별로 익명화된 운전자-파트너 데이터를 집계할 경우, 일부 도시(샌프란시스코 포함)는 소규모 도시보다 훨씬 더 커 특정 부분이나 노드에 과도한 부담을 줌
    • allocation decision과 load distribution을 위해 샤드의 크기와 효용성을 대략적으로 동일하게 유지하는 것이 중요함

아래 요소들은 샤딩할 때 고려해야할 요소

  • Write QPS : 샤드가 피크 트래픽을 처리할 수 있어야 함
  • Read QPS : 샤드가 최대 쿼리를 처리할 수 있어야 함
  • Filters : 쿼리에 사용되는 상위 x개의 자주 사용되는 필터. 상위 필터는 샤딩의 key 후보로 고려할 수 있음. 필터는 고유 값(distinct value)의 수가 충분히 커야 함
  • SLA: 분석용으로 사용하는지 실시간으로 사용하는지
  • Shard Size : 샤드 사이즈를 60GB 이하로 유지하는 것이 좋음

샤드의 수(The number of shards)는 write/read QPS와 shard size를 토대로 계산됨. 샤딩 Key를 찾기 위한 과정은 그림 10과 같음. 샤딩 키가 결정될 때, 과거 데이터를 사용해 샤드 분포가 Gairos가 지정한 threshold 내에 있는지 확인함.

그림 11에서 샤딩의 간단한 예시를 확인할 수 있음. 이 예제에선 각 노드에선 3000 write QPS를 처리할 수 있고, 60GB가 넘는 데이터를 저장할 수 있다고 가정함. Data size와 피크의 write QPS만 고려함

그림 11: 데이터 크기가 서로 다른 4개의 도시(즉, 플랫폼에서 더 많은 수의 사용자)와 다른 QPS는 주어진 제약 조건에 따라 4개의 샤드로 분할됨

샤딩은 다음 제약을 충족해야 함

  • 각 샤드의 피크 Write QPS <= 3,000QPS
  • 각 샤드의 Data size <= 60GB

목표 : 이 샤드들을 가능하면 균등하게 분배

각 도시의 데이터 크기에 따라 샤드 수를 다음과 같이 추정할 수 있음

  • Data size에 기반한 샤드의 수 : (30GB + 50GB + 80GB + 20GB)/60GB = 3

피크 QPS를 기준으로 샤드 수에 대한 다른 추정을 얻을 수 있음

  • 피크 QPS에 기반한 샤드의 수 : (2k + 3k + 5k + 1k)/3k = 4

두 추정치의 최대값을 구하면

MAX(3, 4) = 4

이 4개 도시는 4개 샤드로 나눠질 것임. SFO와 SD는 같은 샤드에 넣을 수 있다. LA는 한 샤드에 저장된다. NY는 두 샤드에 나눌 수 있다. 이런 방식으로 데이터는 각 노드에 데이터를 보관하고 피크 QPS를 처리할 수 있는 동안 서로 다른 샤드에 고르게 분산됨

SFO에서 드라이버에 대한 쿼리는 1번 샤드에 직접 전달할 수 있다. NY의 운전자에 대한 쿼리는 3번과 4번 샤드에 접근해야 함

Skewed된 샤드와 핫스팟 문제를 해결하기 위해, Gairos에 맞는 커스텀 샤딩 알고리즘을 개발했음. 그림 12처럼 각 샤드의 docs의 최대값과 최소값, 기존 알고리즘과 새로운 알고리즘 값을 보여줌

그림 12: 새로운 샤딩 알고리즘은 문서 번호의 차이가 작은 샤드를 생성하며 문서는 파편에 더 균등하게 분포함

  • 이 샤드 사이에 데이터가 더 고르게 분포되어 있음을 알 수 있다. 가이로스의 기본 샤딩 알고리즘은 샤드당 최대 문서와 최소 문서 비율은 2.76인 반면, 커스텀 샤딩 알고리즘의 경우 1.3임
  • 일부 벤치마킹 테스트는 레이턴시와 동시 접속자를 고려한 테스트도 가능하다. 수요 데이터를 가지고 나온 결과는 다음과 같음
    • 그림 13은 클라이언트의 수에 따른 레이턴시를 보여준다. 샤딩이 없는 경우보다 샤딩이 있는 경우가 레이턴시가 낮게 보인다. 클라이언트의 수가 증가할수록 이 차이는 커진다.

그림 13: 샤딩이 있는 레이턴시는 수요 데이터는 훨씬 낮고, 예제에서 설명한 것처럼 클라이언트 수가 증가하면 차이가 증가함

그림 14는 동시 사용자 수에 따른 QPS를 보여준다. 샤딩이 있는 최대 QPS는 샤딩이 없는 최대 QPS보다 4배 더 높은 것을 알 수 있음

그림 14: 샤딩이 있는 QPS는 샤딩이 없는 QPS의 4배

아래 이미지(그림 15)는 두번째 데이터 소스인 supply_geodriver 데이터에 대한 최적화 결과를 보여준다. 수요 데이터에 비해 데이터가 더 많고 데이터 크기가 크다.

그림 15: 샤딩이 있는 레이턴시는 더 높고 클라이언트 수가 증가함에 따라 차이가 증가함

그림 15에서 본 것처럼 샤딩을 사용한 후 평균적인 레이턴시가 나빠진다. 동시 클라이언트 수는 그림 16처럼 샤딩 없는 경우보다 4배다

그림 16: 샤딩이 있는 QPS는 샤딩이 없는 QPS의 4배

세 번째 데이터 소스는 supply_status

그림 17: 샤딩을 사용한 레이턴시는 클라이언트 수가 적을 때 더 높고 클라이언트 수가 200+로 증가할 때 더 낮다.

그림 18: 샤딩이 있는 QPS는 샤딩이 없는 QPS의 4배

그림 17은 클라이언트 수가 낮을 때 샤딩의 평균 레이턴시가 더 길다는 것을 보여준다. 고객 수가 200명 이상으로 늘어나면 레이턴시가 줄어든다. 샤딩으로 지원할 수 있는 동시 사용자 수가 가장 많은 것은 그림 18에 나온 것처럼 샤딩이 없는 것과 비교했을 때 약 4배다.

  • 요약하면, 일부 대용량 데이터는 레이턴시가 더 나빠질 수 있지만, 동시 사용자 수는 샤딩 없는 경우보다 4배 더 지원할 수 있다. 대용량 데이터에서 레이턴시와 확장성을 모두 얻으려면, 각 샤드의 파티션 크기를 조정하자

그림 19: 서지 프라이싱 클러스터의 CPU 로드는 데일리 패턴을 보여주며 시간이 지나면서 증가함. 피크 CPU 로드는 60에서 10으로 감소하고, 각 노드의 로드는 하루 동안 약간 변화함

  • 샤딩 전략의 부산물로 그림 19 같이 프라이싱 클러스터를 안정화할 수 있음
    • 프라이싱 클러스터 노드의 CPU 로드는 모든 인덱스가 일일(daily) 인덱스라 일별 패턴을 보인다
    • 하루 동안, 시간이 지나며 CPU 부하가 증가하는 것을 볼 수 있음
  • 샤딩 전략이 프라이싱 클러스터에 모두 적용된 후에 CPU 로드는 안정화된다.


쿼리 패턴 & 시그니처 기반 캐싱

  • 캐싱의 가장 간단한 솔루션은 모든 쿼리 결과를 캐시하는 것이다
    • 그러나 대규모 데이터 특성 때문에 모든 쿼리 결과를 캐싱하는 총 크기는 원본 데이터보다 더 큼
  • 또한, 일부 쿼리는 자주 실행되지 않아 캐시 적중률(cache hit rate)가 낮음
    • 캐싱을 효율적으로 만들기 위해 query signature와 query pattern을 도입함. Gairos 쿼리 예제를 확인하자.


  • 가이로스 쿼리는 JSON 객체로 다음 내용을 저장한다. Data source, granularity, by, filter, aggregations, buckeyBy, sort, limit, having 등.
  • signatue를 정의할 때, datasrouce, granularity, by, filter, aggregations, buckeyBy, sort, limit 필드만 사용됨.
  • query signature는 각 필드가 정렬된 상태에서 생성됨

  • query pattern도 동일한 필드를 사용해 정의한다.
    • 차이점은 쿼리 패턴은 필터에 사용된 연산자와 값을 고려하지 않고 사용된 열만 고려하는 점
    • query pattern과 signature를 사용하면 Gairos 쿼리들을 효과적으로 분석할 수 있음
  • 쿼리 패턴을 토대로 RT-Gairos에서 자주 사용하는 쿼리 결과를 캐싱할 수 있는 캐싱 규칙을 정의할 수 있음
    • 예를 들어 클라이언트가 최근 2주에 고정된 간격(1분, 5분, 1시간 등)으로 데이터를 가져가는 경우
    • 만약 데이터가 일자별로 캐싱한다면, index hit rate가 점점 높아져 캐시를 활용해 검색 퍼포먼스를 개선할 때도 사용할 수 있음
    • 중복된 범위를 가지는 반복된 쿼리들도 유사한 전략을 적용할 수 있고, 쿼리 패턴에 기반한 시간 세분화 규칙을 적용할 수 있음
    • cache hit rate를 높이기 위해 반드시 쿼리를 쪼개야 함
    • 쿼리를 쪼갤 수 있는 경우 각 쿼리가 쿼리 시간 범위에 따라 여러 작은 쿼리로 분할됨
    • 일부 집계는 각각의 서브쿼리 결과에서 집계 결과를 가져올 수 없음
    • 이런 쿼리는 캐시에 저장되는 대신 Elasticsearch 클러스터에 저장됨

그림 20, 21에서 샘플 데이터인 rider_sessions를 캐싱하는 벤치마크 결과를 볼 수 있음

그림 20: 캐싱의 레이턴시는 더 낮고 클라이언트 수가 증가할수록 차이가 크게 증가함

그림 21: 캐싱이 포함된 QPS는 캐싱이 없는 QPS의 10배

  • 그림 20을 보면 캐싱을 적용한 후 쿼리의 평균 레이턴시 시간이 단축된 것을 볼 수 있음
    • 지원 가능한 동시 사용자 수는 그림 21에서 보는 것처럼 훨씬 더 많음
    • ider_session의 쿼리는 대부분 무겁기 때문에 다른 데이터 소스에서 많은 테스트를 해봄

supply_stattus의 캐시 통계는 그림 22에 나와있다. supply_status의 적중률이 80%를 넘는다. Hit QPS는 약 50이고 set QPS는 약 10이다

그림 22: supply_status에 대한 캐시 적중률이 높고 QPS는 약 50, set QPS는 약 10이다

다른 데이터 소스 demand_jobs는 그림 23에 나와있다. 적중률은 80%다.

그림 23: demand_jobs의 캐시 적중률은 약 80%이며 적중률에는 일부 스파이크가 있음

그림 24: supply_geodriver의 캐시 적중률은 약 30%

그림 25: 모든 수요에 대한 캐시 적중이 전혀 없음

  • 마지막으로 중요한 것은 그림 25에서 수요 캐시 적중률은 0이다. 데이터 소스에 따라 캐시를 통한 개선이 많이 다른 것을 알 수 있음


Merge Index(인덱스 병합)

  • Elasticsearch는 검색 속도를 높이기 위해 inverted index를 사용하고 있음
    • 문서를 삭제할 때, 문서는 삭제된 것으로 표시되고 inverted index가 여전히 존재함
    • 삭제된 문서는 검색 결과에서 제외된다
    • 삭제된 문서가 많으면 인덱스 크기가 더 커진다.
  • 이렇게 삭제된 문서는 검색 성능에도 영향을 미친다
    • 예를 들어 그림 26에서 D1, D2, D3 드라이버는 여러 번 업데이트된다
    • 드라이버가 3명이지만 문서는 8개다
    • 인덱스를 합친 후, 삭제된 문서가 제거되고 인덱스 크기가 더 작아진다

역자 : 그림 26 : 인덱스 합치는 과정

  • 인덱스 성능을 향상시키는 또 다른 중요한 요인은 인덱스의 세그먼트 수다
    • 인덱스를 합치는 최적의 시기를 결정하기 위해 벤치마크 테스트가 진행된다
    • 벤치마크 테스트에 사용되는 주요 지표는 인덱스 크기(인덱스를 저장하는 스토리지의 크기)와 검색 레이턴시(데이터 쿼리에 걸리는 시간)다
    • 실시간 시스템에서 수집된 쿼리는 검색 성능 벤치마크 테스트에 사용됨
  • 각 데이터 소스마다 인덱스 병합 기준이 결정되면, 최적화 엔진은 인덱스 최적화 작업을 실행할 수 있다
    • 인덱스 병합 작업의 수는 클러스터 성능에 큰 영향을 미치지 않도록 클러스터별로 조절됨
    • 성능 저하를 방지하기 위해 인덱스 병합 작업을 최대 1개만 실행할 수 있음
    • 중대한 이슈가 관찰되면 모든 병합 작업이 강제로 중단됨


Handling Heavy Queries(무거운 쿼리 처리)

일부 무거운 쿼리는 전체 클러스터의 성능에 영향을 미칠 수 있다.

다음과 같은 전략을 사용해 클러스터를 안정적으로 만들 수 있다.

  • 1) Split Query : 여러 인덱스를 여러 개의 작은 쿼리로 분할해서 즉시 쿼리되는 샤드의 수를 제한할 수 있음
  • 2) Rate Limiting : 무거운 쿼리 패턴을 식별하고 무거운 쿼리의 비율을 제한해 클러스터 성능을 향상시킬 수 있음
  • 3) Caching or Create Rollup Table : 적중률이 높은 일부 쿼리는 성능 향상을 위해 캐싱하거나 쿼리의 경우 캐싱 또는 롤업 테이블을 사용해 성능을 향상시킬 수 있음
  • 4) Migrating to Hive/Presto : 배치로 사용하는 경우, Hive/Presto로 마이그레이션할 수 있음


Indexing Template Optimization(인덱싱 템플릿 최적화)

실행된 쿼리에서 각 데이터 원본의 각 필드에서 다음 정보를 얻을 수 있으므로 각 필드의 인덱스 설정을 결정할 수 있음.

  • 사용되는지?
  • 필터링에 사용되는지?
  • 집계에 사용되는지?
  • fuzzy 검색이 필요한지?

  • 각각의 데이터 소스에 대해 사용자가 원본 데이터를 가져와야 하는지 여부에 대한 질문에 답해야 한다
    • 이런 입력을 기반으로 각 데이터 소스의 최적의 인덱스 설정을 얻을 수 있음
    • 최적화 엔진은 데이터 소스에 저장된 템플릿을 업데이트해 디스크 공간이나 검색 성능을 향상시킬 수 있음
    • 일부 설정(소스 비활성화)은 이전 버전과 호환되지 않으며 실행하기 전에 승인이 필요함
    • 소스를 비활성화하면 업데이트 및 reindex가 불가능함. 비즈니스 로직이 계속 업데이트되야 하면, 소스를 비활성화하면 안됨
  • 데이터가 자동으로 유지되고 쉽게 재생되기 때문에 출처를 비활성화하기 전에 게시 Apache Kafka 토픽을 히트파이프 토픽으로 만드는 것이 더 쉬워지므로 Apache Kafka 토픽에서 이벤트를 다시 실행해 데이터 마이그레이션을 할 수 있음

그림 27은 각 필드의 설정을 결정하는 자세한 워크플로우를 보여줌.

그림 27: 사용량에 따라 각 필드의 설정을 확인하자


Shard # Optimization(샤드 수 최적화)

  • 각 데이터 소스에 대해 하나의 인덱스가 스테이징 클러스터에 복사되고 reindex 도구를 사용해 복사된 인덱스를 다른 샤드에 다시 인덱싱함
  • 성능 데이터를 수집하기 위해 복사된 인덱스 및 reindex된 인덱스에 대해 벤치마킹 테스트를 실행함
  • 사용된 쿼리는 과거에 수집된 사용자 쿼리다
  • 각 데이터 소스에 대한 최상의 샤드 번호가 확인되면 쿼리 최적화는 새 인덱스에 새 샤드 번호를 설정할 수 있음


Bound Index Range

  • 일부 클러스터에는 매우 작은 인덱스가 많이 생성되는 것이 관찰됨
    • 이런 인덱스의 샤드 수는 많음
  • 클러스터에서 샤드 할당 문제가 발생됨
    • 일부 노드에서 사용되지 않는 샤드가 많을 수 있지만 일부 노드에는 사용중인 샤드가 상당히 많아 노드간에 불균형이 발생하고 리소스 사용률이 낮아질 수 있음
  • 이런 작은 인덱스는 일방적으로 타임 스탬프가 범위를 벗어난 이벤트로 발생함
    • Elasticsearch 클러스터에 데이터를 저장할 때 데이터는 데이터 보존과 데이터 예측을 기반으로 데이터를 필터링함
  • 다음은 한 클러스터의 예다
    • 그림 28은 클러스터 중 하나에서 작은 인덱스를 정리한 후 샤드 수가 약 40k에서 20k로 감소함

그림 28: 작은 인덱스들을 정리하면 샤드의 수가 약 40k에서 20k로 떨어짐


사용되지 않는 데이터 정리

  • 수집된 쿼리는 지난 X일 동안 데이터 원본이 사용되었는지 여부를 확인할 수 있음
  • 이 정보를 바탕으로 가이로스 최적화 엔진은 데이터스토어 내 데이터 소스에 대한 트리거 알림 및 인덱스 삭제 등 다양한 데이터 삭제 작업을 수행할 수 있음

느낀 점

  • 실시간 데이터 파이프라인을 고도화하는 과정이 인상적이였고, 실시간으로 데이터를 파악할 수 있는 대시보드 역할도 잘 하고 있는듯
  • 사용자들이 바라보는 UX가 궁금
  • 모든 것들을 다 커버할 수는 없을텐데, 커스텀 대시보드는 어떻게 만들고 있을까? 궁금
  • 실시간 플랫폼을 만든다면 Kafka, Elasticsearch를 고려해보기!
  • 다양한 최적화 전략도 인상 깊었음



Rerference


카일스쿨 유튜브 채널을 만들었습니다. 데이터 사이언스, 성장, 리더십, BigQuery 등을 이야기할 예정이니, 관심 있으시면 구독 부탁드립니다 :)

PM을 위한 데이터 리터러시 강의를 만들었습니다. 문제 정의, 지표, 실험 설계, 문화 만들기, 로그 설계, 회고 등을 담은 강의입니다

이 글이 도움이 되셨거나 다양한 의견이 있다면 댓글 부탁드립니다 :)

Buy me a coffeeBuy me a coffee





© 2017. by Seongyun Byeon

Powered by zzsza