At a glance#
| Number | Value | What it drives |
|---|---|---|
| Ride trips | 30M a day (design assumption) | ~1,000 requests / s at a 3× peak |
| Location pings | ~375K / s, ~500K at peak | 1.5M online drivers × one ping every 4 s |
| Request → driver notified | < 3 s | ring search, ETA for a few dozen candidates |
| Driver offer window | 15 s | then the next best driver |
| H3 cell | res 9 · ~0.1 km² · edge ~200 m | street-block buckets, rings k = 1, 2… |
| Storage | 150 GB / day trips · 4.3 TB / day pings | ~55 TB a year of trips; pings kept 7 days ≈ 30 TB |
| Surge | per cell · every 30 s · cap 5× | smoothed with an EMA, α = 0.3 |
Architecture#
DiagramUber on one page: soft-state locations, strongly consistent trips
%% title: Uber on one page: soft-state locations, strongly consistent trips
flowchart TD
RA@{ icon: "sd:phone", label: "Rider app<br/>WebSocket + HTTP" } --> GW@{ icon: "sd:gateway", label: "API gateway<br/>auth · rate limit · routing" }
DA@{ icon: "sd:car", label: "Driver app<br/>ping every 4 s" } --> GW
GW --> LS["Location service<br/>~500K pings / s · Kalman filter"]:::core
GW ==> MS["Matching / dispatch<br/>ring search · ETA score · 15 s offers"]:::hot
GW --> TS["Trip service<br/>exactly-once create · state machine"]:::core
LS --> RD@{ icon: "tech:redis", label: "Redis · H3 cell → drivers<br/>driver hash, TTL 30 s" }
MS --> RD
TS --> SP@{ icon: "tech:spanner", label: "Spanner<br/>trips, strongly consistent" }
LS -.->|"pings"| KF@{ icon: "tech:kafka", label: "Kafka<br/>pings + every state change" }
TS -.->|"state changes"| KF
KF --> CA@{ icon: "tech:cassandra", label: "Cassandra<br/>location history · 7 days" }
KF --> FL@{ icon: "tech:flink", label: "Flink<br/>demand per cell" }
FL --> PR["Pricing<br/>surge per cell · upfront fare locked"]:::service
class RA,DA client
class GW edge
class RD cache
class SP,CA store
class KF queue
class FL service- Two kinds of state: live positions are overwritten every 4 s and may be seconds stale; trips are created once and must never be lost, duplicated or re-priced.
- Shard by city or region: matching, surge and location lookups are local, so a query stays in one shard.
DiagramMatching: ring search, score, offer one driver at a time
%% title: Matching: ring search, score, offer one driver at a time
flowchart TD
REQ["Ride request<br/>pickup → H3 cell, res 9"]:::service --> RING["Ring search · k = 1, 2… until enough<br/>available · ride type · not already pinged"]:::core
RING --> SC["Score ≤ 50 candidates<br/>0.4 ETA · 0.2 rating · 0.2 acceptance · 0.2 wait"]:::service
SC -.->|"rush hour"| BA["Batch 2 s of requests<br/>solve the assignment jointly"]:::service
SC --> OF{"Offer to best · mark PENDING<br/>wait ≤ 15 s"}
OF -->|"accept"| TR["Trip created once"]:::ok
OF -->|"decline · timeout"| NX["Clear PENDING<br/>next best, up to 10"]:::queue
OF -->|"none left"| NO["No drivers · all declined"]:::bad
NX --> OFKey flows#
DiagramRequest a ride: matched driver notified in under 3 s
%% title: Request a ride: matched driver notified in under 3 s
sequenceDiagram
autonumber
participant R as Rider app
participant M as Matching
participant L as Location index
participant D as Driver app
participant T as Trip service
R->>M: Request · pickup · destination · ride type
M->>L: Drivers in pickup cell + rings
L-->>M: Candidates
Note over M: filter · ETA each · score · pick best · mark PENDING
M->>D: Offer over WebSocket
Note over R,D: under 3 s from request to here
D-->>M: Accept (within 15 s)
M->>T: Create trip (request ID)
Note over T: once per request · fare locked at quote
T-->>R: Driver · ETA
T-->>D: Pickup · route
loop every 4 s
D-)L: Location ping
L-)R: Car moves on the map
endDiagramDriver goes silent mid-offer: time out, offer the next
%% title: Driver goes silent mid-offer: time out, offer the next
sequenceDiagram
autonumber
participant M as Matching
participant D1 as Best driver
participant D2 as Next driver
participant L as Location index
M->>D1: Offer · D1 marked PENDING
Note over D1: phone loses signal
Note over M: no answer in 15 s · treat as decline
M->>M: Clear D1's PENDING
M->>D2: Offer · D2 marked PENDING
alt D2 accepts
D2-->>M: Accept → trip created
else D2 declines too
Note over M: next best, up to 10 candidates
end
Note over L: no ping from D1 for 30 s · TTL drops D1 from searchDiagramRegion fails at rush hour: keep trips, rebuild locations
%% title: Region fails at rush hour: keep trips, rebuild locations
sequenceDiagram
autonumber
participant D as Driver app
participant B as Healthy region
participant T as Trip store
participant L as Location index
Note over D: region A down · app keeps the trip and GPS trail
D->>B: Reconnect
B->>T: Load trip (replicated active-active)
T-->>B: Rider · driver · locked fare · status
D->>L: Fresh ping (every 4 s)
Note over L: soft state rebuilt in a ping cycle or two
D->>B: Complete trip (trip ID)
D->>B: Complete trip again (retry after timeout)
alt same trip ID already completed
B-->>D: Existing result · one charge only
endData model#
DiagramCore entities
%% title: Core entities
classDiagram
class Driver {
+driverId : string
+rideTypes : X / XL / Black
+rating : float
+acceptanceRate : float
+status : online / pending / on trip
}
class DriverLocation {
+driverId : string
+lat : float
+lng : float
+h3Cell : res 9
+ts : timestamp · TTL 30 s
}
class Trip {
+tripId : string · idempotency key
+requestId : string
+riderId : string
+driverId : string
+state : TripState
+upfrontFare : locked at request
}
class CellSurge {
+cellId : string
+multiplier : 1.0 to 5.0
+demand : requests, last 5 min
+supply : cell + 6 neighbours
}
class Rider {
+riderId : string
+paymentMethods : list
}
Driver "1" --> "1" DriverLocation : latest only
Rider "1" --> "many" Trip : requests
Driver "1" --> "many" Trip : drives
Trip "many" --> "1" CellSurge : priced by pickup cell- Locations: Redis keyed by H3 cell (
cell:{id}→ driver set) and by driver, overwritten on each ping; history ~100 B a ping in Cassandra for 7 days. - Trips: ~5 KB each in Spanner, strongly consistent; everything sharded by city or region.
Lifecycle#
DiagramTrip state machine
%% title: Trip state machine stateDiagram-v2 [*] --> REQUESTED REQUESTED --> MATCHING: fare quoted MATCHING --> OFFERED: best driver, PENDING OFFERED --> MATCHING: decline or 15 s timeout MATCHING --> NO_DRIVERS: none left OFFERED --> MATCHED: driver accepts MATCHED --> EN_ROUTE: driving to pickup EN_ROUTE --> ON_TRIP: rider picked up MATCHED --> CANCELLED: rider or driver cancels EN_ROUTE --> CANCELLED: rider or driver cancels ON_TRIP --> COMPLETED: drop-off COMPLETED --> PAYMENT_RETRY: card declined PAYMENT_RETRY --> PAID: backup method COMPLETED --> PAID: charged PAID --> [*] NO_DRIVERS --> [*] CANCELLED --> [*] class PAID ok class NO_DRIVERS,CANCELLED bad class OFFERED,PAYMENT_RETRY queue
Decisions#
| Decision | Chose | Over | Because |
|---|---|---|---|
| Live locations | Redis, overwritten each ping | A DB write per ping | ~500K writes / s; a few seconds stale is fine |
| Spatial index | H3 hexagons, res 9, ring search | Squares or scan all drivers | one neighbour distance; rings ≈ circles |
| Trip store | Spanner, exactly-once create | An eventual store | a duplicate trip is a double charge or two cars |
| Ranking | Weighted score led by ETA | Nearest in a straight line | rivers, one-way streets, driver fairness |
| Dispatch | One offer at a time, PENDING flag, 15 s | Broadcast to many drivers | no double-dispatch of one driver |
| Rush hour | 2 s batches, joint assignment | Greedy one by one | lower total pickup time |
| Surge | Per cell, cell + 6 neighbours, EMA, cap 5× | One city-wide price | local, smooth, no sudden jumps |
When it breaks#
| Failure | What users see | Contain it with |
|---|---|---|
| Driver loses signal mid-offer | a slightly longer wait | 15 s timeout → next driver; TTL drops them at 30 s |
| Concert ends, 50,000 requests | surge, a queue | surge, virtual queue, pre-positioned drivers, predictive scaling |
| GPS drift | car in the wrong cell | Kalman filter, k-ring search, wider radius |
| Card declined after the trip | nothing for the driver | backup payment method, grace period, driver always paid |
| Region down at rush hour | a reconnect blip | trips replicated active-active, index rebuilt from pings, trip-ID idempotency |
| Location service slow | bad matches, wrong ETAs | critical alert at 500 ms; it sits on the live path |
Say it in the interview#
- "Locations are soft state; trips are money." Redis for pings, Spanner for trips, and never the other way round.
- Size for pings, not trips: ~500K pings a second against ~1,000 trip requests is roughly 500 to 1.
- Hexagons make "near me" a set lookup: cell ID per driver, then rings k = 1, 2 around the rider; exact ETA only for a few dozen.
- Rank by ETA, offer one at a time: PENDING blocks double-dispatch; 15 s, then the next driver.
- Surge is a local ratio, smoothed: requests ÷ drivers in the cell and its neighbours, EMA, capped, fare locked at request.
- Name the budget: < 3 s ends at driver notification; human accept time is the 15 s window, and it drives the p99.
- Failover = replicate trips, rebuild the index: fresh pings refill it in seconds; trip-ID keys stop double charges.