Uber — H3 육각 격자와 실시간 배차 매칭
4초마다 쏟아지는 GPS를 64비트 셀 ID로 바꿔 Ringpop 해시링에 샤딩하고, 호출이 오면 이웃 셀을 병렬 조회해 ETA로 매칭하기까지 — 노드 크래시·운전자 끊김·DC 소실 복구를 포함해 24단계로 따라간다.
단계 목록 · 키보드 ←/→ 이동, Space 재생
출처: H3: Uber's Hexagonal Hierarchical Spatial Index · How Uber Scales Their Real-Time Market Platform (QCon 2015) · H3 공식 문서 — 해상도 표
위경도를 64비트 셀 ID로 바꾸는 순간 '반경 안의 차 찾기'라는 공간 문제가 샤딩(Ringpop)·이웃 조회(kRing)·집계(surge)가 가능한 키-값 문제로 바뀐다 — 그것이 Uber 배차의 핵심 트릭이다.
왜 헷갈리는가
'Uber 배차 = H3'로 아는 경우가 많지만 시대가 다르다. 2015년 QCon 발표 당시 디스패치(DISCO)의 지리 인덱스는 Google S2(레벨 12 셀, 3.31~6.38km²)였고, H3는 이후 Uber가 자체 개발해 2018년 오픈소스로 공개한 것으로 surge pricing과 마켓 분석에 쓰인다. 또 하나 — kRing은 정확한 반경 검색이 아니라 '중심 셀에서 k겹 이웃'이라는 셀 단위 근사다.
애니메이션이 보여주는 것
평시 루프부터 시작한다 — 운전자 앱이 약 4초마다 GPS를 보내고(인덱스는 초당 백만 건 쓰기 목표), 좌표가 셀 ID로 변환되고, 셀 ID를 샤딩 키로 Ringpop consistent hash ring이 담당 샤드를 정해 기록한다. 호출이 오면 DISCO가 승객 중심 원을 덮는 이웃 셀들을 계산하고, 담당 샤드들을 병렬 질의해 근처 차량을 모은 뒤 도로망 기반 ETA로 평가한다 — 곧 운행이 끝날 차까지 후보에 넣는다.
후반부는 세 겹의 장애다. 샤드 크래시는 SWIM gossip이 수 초 내 감지해 키 범위를 재배치하고(crash-only 철학 — 크래시 복구 경로가 곧 정상 경로), 운전자 연결이 끊기면 위치 신선도를 잃어 매칭에서 제외되며, DC 전체가 죽으면 운전자 폰이 들고 있던 암호화 State Digest를 백업 DC에 올려 진행 중 trip을 복원한다.
왜 육각형인가 — 그리고 펜타곤 12개
육각형은 모든 이웃 셀 중심까지의 거리가 한 종류다. 정사각형은 변 이웃과 대각 이웃의 거리가 다르고(2종), 삼각형은 3종이다. 거리가 균일하면 거리 기반 분석이 단순해지고, surge pricing에서 인접 셀 간 가격 그라디언트가 매끄러워진다.
다만 구를 육각형만으로 덮을 수는 없다. H3는 정이십면체 투영 위에 해상도 0의 베이스 셀 122개를 놓는데, 모든 해상도에서 정확히 12개의 펜타곤이 생기고 이들은 전부 바다에 배치돼 실사용에서 마주칠 일을 줄였다. 해상도는 0~15로, 한 단계 내려갈 때마다 면적이 1/7이 되고(aperture 7), 해상도 15는 약 569조 개 셀(평균 약 0.9m²)에 이른다. 인덱스는 64비트 정수 하나라 비트 절단만으로 부모 셀을 얻는다.
왜 R-tree가 아니라 셀 샤딩인가
초당 백만 건 쓰기를 단일 공간 인덱스가 감당할 수 없다. 셀 ID를 샤딩 키로 쓰면 쓰기가 자연스럽게 분산되고, '반경 내 검색'은 이웃 셀들의 담당 샤드에 대한 병렬 키 조회로 바뀐다. 샤드 배치는 Ringpop consistent hash ring이 정하고, 멤버십은 SWIM gossip으로 중앙 코디네이터 없이 전파된다(AP 선택).
노드 간 통신은 TChannel(Thrift 기반 양방향 RPC)로, HTTP/JSON 대비 약 20배 빠르고 파이프라이닝으로 head-of-line blocking을 피한다. 느린 서버가 만드는 꼬리 지연은 backup request with cross-server cancellation — 두 서버에 경주를 붙이고 먼저 온 응답만 취하는 방식 — 으로 누른다.
- 셀 ID = 샤딩 키(geosharding): 쓰기 자연 분산, 읽기는 복제본 확장
- Ringpop: consistent hash ring + SWIM gossip, 중앙 코디네이터 없음(AP)
- 요청은 멱등하게 설계 — 노드가 죽어도 재시도로 흡수
- crash-only: graceful shutdown 경로를 따로 두지 않는다
기억할 것
- 위치를 64비트 셀 ID로 바꾸면 공간 문제가 키-값 문제가 된다 — 샤딩·조회·집계가 전부 정수 키 연산.
- 반경 검색은 kRing 이웃 셀 근사 — 이웃 셀 담당 샤드들에 대한 병렬 질의로 풀린다.
- 매칭 기준은 직선거리가 아니라 도로망·과거 주행 기반 ETA — 곧 운행이 끝날 차까지 후보에 넣는다.
- 육각형의 균일한 이웃 거리가 surge 가격 그라디언트를 매끄럽게 만든다 — 펜타곤 12개는 바다로.
- 마지막 복구 층은 운전자 폰의 암호화 State Digest — DC 전체가 죽어도 진행 중 trip이 산다.