Drivers keep shouting their location every few seconds into a big city map kept in memory. When you ask for a ride, the system looks at the map squares around you, picks the best nearby driver, and puts a short "reserved" tag on them so nobody else can grab them.
Design a ride-hailing service like Uber or Ola: riders request a ride, the system finds a nearby available driver, the driver accepts, and both see each other's live location until the trip ends and is paid for. The hard parts are tracking millions of moving drivers and matching quickly without assigning one driver twice.
Location updates: 5M / 4 s = ~1.25M writes/s -> in-memory, not a disk DB
Each update ~100 B -> ~125 MB/s ingest; only the LATEST location matters for matching
Ride requests: ~1K/s peak -> matching load is small; location ingest is the big number
Driver app (persistent connection, e.g. WebSocket/MQTT):
-> location { lat, lng, heading, ts } every ~4 s
<- ride_offer { ride_id, pickup, eta, expires_in: 15 }
-> accept { ride_id } | decline { ride_id }
Rider app:
POST /v1/fare-estimate { pickup, dropoff } -> { fare, eta }
POST /v1/rides { pickup, dropoff, fare_token } -> 201 { ride_id, status: REQUESTED }
GET /v1/rides/{id} (+ push updates: matched driver, live driver location)
driver apps ==> Location gateway --> Location service
- update geo index (Redis GEO / cells), TTL
- publish to "ride:{id}" channel if on a trip
rider app --> Ride service --(create ride)--> Rides DB
|
v
Matching service
1. query geo index: available drivers within r km of pickup
2. rank by ETA (routing/ETA service), rating, heading
3. atomically reserve best driver (status available -> offered)
4. send offer; wait up to 15 s
5. accepted -> ride MATCHED; declined/timeout -> release, next driver
Notification / push -> rider sees driver + live location
Option A: Redis GEO per city: GEOADD drivers:blr lng lat driver_id
nearby = GEOSEARCH drivers:blr FROMLONLAT lng lat BYRADIUS 3 km ASC COUNT 20
Option B: cell-based: key = geo cell id (geohash / S2 / H3 at ~1 km)
cell:{id} -> set of driver_ids; query pickup cell + neighbours
On each update: move the driver between cells if it changed; stale drivers
expire via TTL (an app that stops reporting disappears from matching).
Shard the index by city / region: queries never cross cities.
-- reserve a driver atomically (Redis or DB); only one matcher can win
SET driver_lock:{driver_id} {ride_id} NX PX 15000 -- offer window
success -> send offer
fail -> someone else is offering this driver; try the next candidate
on accept: ride.status = MATCHED, driver.status = ON_TRIP (transaction / conditional update)
on timeout: lock expires automatically -> driver available again
Matching can dispatch requests one at a time (nearest good driver) or batch requests over a short window (a second or two) and solve an assignment problem across riders and drivers to reduce total pickup time.
REQUESTED -> MATCHED -> DRIVER_ARRIVING -> IN_PROGRESS -> COMPLETED -> PAID
\-> CANCELLED (by rider/driver/timeout)
rides(ride_id PK, rider_id, driver_id, status, pickup, dropoff, fare,
requested_at, matched_at, completed_at, version) -- updates conditional on version
trip_path: location points during IN_PROGRESS -> appended to a time-series store (for fare & disputes)