이 영상에서는 우버 앱이 수백만 명의 드라이버와 라이더의 실시간 위치를 스트리밍하면서도 앱이 원활하게 작동하도록 하는 정교한 시스템에 대해 자세히 설명합니다. 우버는 초기 폴링 방식의 문제점을 극복하고 푸시 기반 통신과 헥사고날 공간 인덱스(H3)를 도입하여 효율성을 극대화했습니다. 또한, 에지 서버와 데드 레코닝(Dead Reckoning) 기술을 통해 모바일 앱의 네트워크 지연을 최소화하고 사용자 경험을 향상시킨 점이 핵심입니다.
1. 우버의 초기 시스템: 폴링 방식의 한계 📉
우버 앱은 라이더가 탑승 요청을 하면 라이더의 위치를 우버에 공유하고, 동시에 주변 드라이버의 위치를 라이더에게 공유해 줍니다. 이렇게 간단해 보이는 과정 뒤에는 수억 명의 사용자를 위해 시스템을 원활하게 작동시키기 위한 엄청난 복잡성이 숨어있어요. 초기 우버는 폴링(Polling) 방식을 사용했는데요, 이는 앱(클라이언트)이 서버에 주기적으로 새로운 데이터가 있는지 계속해서 물어보는 방식이었죠. 마치 "근처 드라이버의 새 위치가 있나요? 있나요? 있나요?" 하고 반복해서 묻는 것과 같았어요.
"Hey, are there new locations for nearby drivers? Are there new locations? Are there new locations?"
하지만 이 방식은 곧 큰 문제에 부딪혔습니다.
- 불필요한 서버 부하: 새로운 데이터가 없는데도 클라이언트가 계속 요청을 보내면 서버에는 불필요한 부하가 쌓였어요.
- 배터리 소모 및 오버헤드: 매번 요청을 보낼 때마다 추가 헤더가 붙어 배터리 소모와 네트워크 오버헤드가 커졌죠.
- 네트워크 요청의 80%가 폴링: 결국 우버 서버로 들어오는 네트워크 요청의 80%가 폴링 호출이었다고 해요.
- 느린 앱 시작 시간: 여러 폴링 호출이 동시에 경쟁하면서 앱의 UI 렌더링을 방해하여 앱의 초기 로딩 시간(cold startup time)이 급증했습니다.
이러한 문제들 때문에 우버는 결국 폴링 방식으로는 시스템을 확장할 수 없다는 것을 깨달았고, 새로운 해결책을 모색하기 시작했습니다.
2. 푸시 기반 통신으로의 전환: Ramen의 등장 🍜
폴링 방식의 문제점을 해결하기 위해 우버는 푸시 기반(Push-based) 통신 방식으로 전환했습니다. 이제는 클라이언트가 데이터를 요청하는 대신, 서버에 새로운 데이터가 생기면 서버가 직접 클라이언트에게 데이터를 '푸시'해주는 방식이죠. 이를 위해 우버는 Ramen이라는 시스템을 개발했어요.
"Ramen stands for realtime asynchronous messaging network."
Ramen은 "Realtime Asynchronous Messaging Network"의 약자로, 실시간 비동기 메시징 네트워크를 의미해요. 이론적으로는 훨씬 효율적이지만, 여전히 몇 가지 도전 과제가 남아 있었어요. 바로 '언제, 무엇을, 어떻게' 클라이언트에게 푸시할지 결정하는 로직이 필요했죠. 우버는 이러한 책임을 세 가지 마이크로 서비스로 분리하여 효율적인 확장을 도모했습니다.
2.1. Firewall: 푸시 결정의 주체 🛡️
Firewall은 언제(when) 푸시할지를 결정하는 마이크로 서비스예요. 이 서비스는 모든 종류의 이벤트를 수신하고, 업데이트를 클라이언트에게 푸시할 가치가 있는지 판단합니다.
- 주요 이벤트: 라이더의 탑승 요청, 드라이버의 수락, 라이더나 드라이버의 새로운 위치 등이 여기에 해당돼요.
- 스마트한 결정: 위치 정보는 매우 자주 변할 수 있지만, 모든 작은 변화를 다 보낼 필요는 없어요. 예를 들어, Fireball은 새로운 위치와 이전 위치를 비교해서 '충분히' 변경되었을 때만 업데이트를 보내는 식이죠.
Fireball이 푸시가 필요하다고 판단하면, 해당 정보를 우버의 API 게이트웨이로 전송합니다.
2.2. API Gateway: 데이터 통합 🔄
API 게이트웨이는 Fireball에서 받은 최소한의 푸시 데이터를 바탕으로 클라이언트에 필요한 모든 데이터를 수집하는 역할을 해요. 예를 들어, 사용자의 지역 설정(locale)이나 사용 중인 운영체제(OS) 같은 정보를 추가로 가져오는 거죠.
2.3. Ramen Server: 페이로드 푸시 🚀
마지막으로, 클라이언트는 Ramen 서버에 직접 연결되고, Ramen 서버는 API 게이트웨이를 통해 결정된 최종 페이로드(payload)를 클라이언트에게 푸시합니다. 초기 Ramen은 SSE(Server-Sent Events) 위에 구축되어 '최소 한 번 전달 보장(at least once delivery)'을 지원했어요. 즉, 클라이언트에게 푸시된 이벤트는 적어도 한 번은 확실히 도착하도록 보장하는 거죠.
하지만 현재는 gRPC로 마이그레이션 되어 클라이언트와 서버 양방향으로 메시지를 주고받을 수 있게 되었습니다. 이제는 서버가 클라이언트에게 데이터를 푸시할 뿐만 아니라, 클라이언트도 서버와 데이터를 공유할 수 있게 되어 더욱 유연한 통신이 가능해졌어요. 🤝
3. 효율적인 위치 정보 처리: 공간 분할(Spatial Partitioning)과 H3 📍
푸시 기반 시스템으로 데이터를 효율적으로 보낼 수는 있게 되었지만, 여전히 큰 과제가 남아있었어요. 우버는 전 세계 수백만 위치에서 실시간 업데이트를 매분 받는데, 어떻게 특정 클라이언트(라이더)에게 근처 드라이버의 위치만 효율적으로 보낼 수 있을까요? 🤔
가장 간단하게는 라이더의 위치를 기준으로 모든 드라이버의 위치까지의 거리를 계산해서 특정 반경 안에 있는 드라이버만 보여주는 방법을 생각할 수 있습니다. 하지만 수백만 명의 라이더와 드라이버에 대해 수백만 개의 거리를 계산하는 것은 서버에 엄청난 부하를 줄 수밖에 없겠죠.
그래서 우버는 훨씬 더 천재적인 접근 방식인 공간 분할(Spatial Partitioning)을 고안했습니다.
3.1. 공간 분할의 개념 🗺️
공간 분할의 핵심 아이디어는 지리적 공간을 더 작은 지역으로 나누는 것이에요. 이렇게 하면 드라이버의 위치를 특정 지역에 매핑할 수 있고, 수백만 개의 거리를 계산할 필요 없이 라이더가 있는 지역과 그 주변 지역에 있는 드라이버만 확인하면 됩니다.
하지만 단순히 세계를 사각형으로 나누는 방식은 문제가 있었어요. 사각형은 모든 이웃과의 거리가 같지 않아서 코너 편향(corner bias)이 발생할 수 있습니다. 대각선 방향의 사각형이 위나 옆에 있는 사각형보다 멀리 떨어져 있을 수 있다는 거죠. 이상적으로는 최소한 근사 반경을 기반으로 근처 드라이버를 쿼리할 수 있는 방식이 필요했습니다.
3.2. H3: 헥사고날 공간 인덱스 🐝
이러한 문제 해결을 위해 우버는 H3라는 오픈소스 헥사고날 공간 인덱스(Hexagonal Spatial Index)를 개발했습니다.
- 벌집 모양의 육각형: H3는 세계를 사각형 대신 벌집 모양의 육각형(honeycomb of hexagons)으로 나눕니다.
- 균일한 거리: 각 육각형은 모든 이웃과의 거리가 거의 동일하다는 특징을 가지고 있어요.
- H3 인덱스: 어떤 GPS 좌표가 주어지면, H3는 그 좌표가 어느 육각형에 속하는지 계산해서 해당 H3 인덱스를 반환합니다.
- K-링 쿼리: 모든 드라이버의 거리를 계산하는 대신, 라이더가 있는 육각형 셀로부터 K-링(K-ring)을 요청합니다. K-링은 라이더 셀로부터 K 단계 이내에 있는 육각형들을 의미해요.
- K=1: 첫 번째 이웃 레이어를 포함하여 총 7개의 셀을 포함합니다.
- K=2: 두 번째 이웃 레이어까지 포함합니다.
이 새로운 시스템 덕분에 시간 복잡도가 혁신적으로 개선되었습니다. 과거에는 N(수백만 명의 활성 드라이버)에 비례하는 O(N)이었지만, 이제는 O(K^2 + M)으로 줄어들었습니다. 여기서 K는 반경(k-링의 크기)이고, M은 근처 드라이버의 수입니다. 즉, 근처에 100명의 드라이버가 있다면 우버는 이제 수백만 명이 아닌 100명만 반복해서 확인하면 되는 거죠! 🤩
"if there are 100 nearby drivers, Uber now only has to iterate over those 100 drivers instead of possibly millions."
이 H3 시스템은 단순히 근처 드라이버를 찾는 것뿐만 아니라, 동적 요금제 구역 설정, 예상 도착 시간(ETA) 예측, 수요 예측 등 우버에게 중요한 다양한 용도로 활용됩니다. 이를 통해 서버 부하를 최적화하고 클라이언트에게 더 빠른 응답을 제공할 수 있게 되었어요.
4. 열악한 네트워크 환경에서의 최적화 📶
모바일 앱의 특성상 인터넷 연결이 항상 안정적일 수는 없습니다. 우버와 같은 차량 공유 앱 사용자들은 주로 집 밖에서 모바일 데이터를 사용하므로, 안정적인 Wi-Fi 대신 셀룰러 네트워크에 연결되어 있을 가능성이 높죠. 우버 엔지니어들은 이러한 열악한 네트워크 환경에서도 앱이 원활하게 작동하도록 어떻게 최적화했을까요? 여기에는 두 가지 주요 고려 사항이 있습니다.
4.1. 네트워크 지연 최소화: 에지 서버(Edge Servers) 🌐
네트워크 트래픽을 빠르게 유지하기 위해 우버는 에지 서버(Edge Servers) 방식을 채택했습니다.
- 전 세계에 분산된 서버: 에지 서버는 전 세계에 수백, 수천 대가 분산되어 있으며, 각 서버는 가까운 사용자를 담당합니다.
- 지리적 근접성: 예를 들어, 베를린의 사용자가 캘리포니아에 있는 서버와 통신하는 대신, 프랑크푸르트와 같이 지리적으로 가까운 서버와 통신하게 하는 거죠.
- 데이터 캐싱: 이 에지 서버들은 근처 드라이버 및 사용자의 요청에 대한 1차 진입점 역할을 할 뿐만 아니라, 관련 데이터를 캐싱하여 데이터 접근 속도를 더욱 빠르게 만듭니다.
일반적인 4G 연결 환경에서 이러한 변화만으로도 요청 처리 속도가 약 100밀리초 정도 빨라질 수 있다고 해요. 10,000km 떨어진 서버와 통신하는 것과 비교하면 엄청난 차이죠! 💨
4.2. 부드러운 지도 움직임: 데드 레코닝(Dead Reckoning) & 칼만 필터(Kalman Filters) 🚗
아무리 서버가 가까이 있어도 모바일 앱에서는 언제든지 연결 끊김에 대비해야 합니다. 새로운 위치 데이터가 항상 일정한 간격으로 안정적으로 도착한다는 보장이 없죠. 만약 드라이버의 위치 업데이트가 20초 동안 세 번만 온다면, 앱에서 드라이버 아이콘이 한 좌표에서 다음 좌표로 뚝뚝 끊기듯이 점프하는 것을 막아야 합니다.
이를 위해 우버는 데드 레코닝(Dead Reckoning) 기술을 사용합니다.
- 위치 예측: 데드 레코닝은 마지막으로 알려진 속도와 방향을 유지했을 때 드라이버가 어디에 있어야 할지를 예측하는 기술이에요. 즉, 새로운 위치 정보가 없어도 차량의 현재 위치를 추정하는 거죠.
- 칼만 필터와의 결합: 이 예측된 좌표는 칼만 필터(Kalman Filters)와 결합됩니다. 칼만 필터는 예측된 좌표와 실제 측정된 좌표를 함께 사용하여, 새로운 측정 좌표가 이전 예측과 많이 다르더라도 차량 마커가 화면에서 부드럽게 움직이도록 만들어 줍니다. ✨
5. 흥미로운 논란: 유령 자동차(Phantom Cars) 👻
이쯤에서 흥미로운 이야기가 하나 더 있습니다. 우버 앱에 존재하지도 않는 가짜 차량이 표시될 수 있다는 주장과 논의가 실제로 있었다고 해요. 만약 존재하지 않는 차량이라면 서버로부터 위치 업데이트를 받을 필요도 없고, 라이더에게 '차가 없는 지역'처럼 보이지 않게 하여 경쟁사 앱을 열게 만들지 않으려고 인위적으로 더 많은 드라이버가 있는 것처럼 보이게 할 수도 있겠죠? 😱
우버는 이러한 유령 자동차(phantom cars) 현상에 대해 부인하고 있지만, 이 이야기는 가장 단순해 보이는 인터페이스 뒤에 얼마나 복잡한 백엔드 시스템이 숨어 있는지, 특히 대규모 확장이 필요할 때 얼마나 많은 고민이 필요한지를 잘 보여줍니다.
마무리 👋
우버 앱의 실시간 지도 시스템은 단순한 지도 표시 기능을 넘어, 폴링 방식의 한계 극복, Ramen을 통한 푸시 기반 통신, H3를 활용한 효율적인 공간 인덱싱, 그리고 에지 서버와 데드 레코닝을 통한 모바일 환경 최적화 등 수많은 첨단 기술과 고민의 결과물입니다. 이처럼 복잡하고 정교한 시스템 덕분에 우리는 우버 앱을 통해 빠르고 원활하게 차량 서비스를 이용할 수 있는 것이죠.