Functional Requirements Link to heading
- Riders can input a start location and destination and get a fare estimate.
- Riders can request a ride based on the estimated fare.
- Upon request, riders are matched with a nearby, available driver.
- Drivers can accept or decline a request and navigate to the pickup and drop-off.
Basic High-Level Design Link to heading
Deep Dives Link to heading
1. Driver Location Updates and Proximity Search Link to heading
Driver location is the highest-throughput write in the system — every active driver pushes an update every ~4 seconds. PostgreSQL is the wrong tool here: row-level locking, WAL overhead, and B-tree indexes on lat/long don’t scale to hundreds of thousands of concurrent updates. Instead, location lives entirely in Redis.
Redis stores geospatial data as a sorted set (ZSET) where each member’s score is a 52-bit integer geohash. Latitude and longitude bits are interleaved into a single integer, so nearby coordinates produce similar scores. Proximity queries become efficient range scans on the sorted set, no spatial index to maintain separately.
# Driver sends a location update
GEOADD drivers:available 151.2093 -33.8688 "driver:42"
# Ride Matching Service finds the 10 nearest available drivers
GEOSEARCH drivers:available
FROMLONLAT 151.2100 -33.8700
BYRADIUS 5 km
ASC COUNT 10
WITHCOORD
GEOSEARCH returns results sorted by actual distance (not geohash bucket).
Data Flow Link to heading
Driver app (every 4s)
→ WebSocket → Location Service
→ Redis: GEOADD drivers:available <long> <lat> <driverId>
Ride Matching Service (on new ride request)
→ GEOSEARCH drivers:available FROMLONLAT <long> <lat> BYRADIUS 5km ASC COUNT 10
→ for each driver candidate: notify via Notification Service
2. Preventing Duplicate Ride Requests to the Same Driver Link to heading
Without coordination, two simultaneous ride requests could both run GEOSEARCH, get the same nearby driver back, and both send that driver a notification at the same time. The driver would get spammed, and whichever ride they accept would leave the other in a broken state.
The fix is a per-driver distributed lock in Redis using SET NX EX (set if not exists, with TTL):
# Try to acquire lock on driver:42 for 30 seconds
SET lock:driver:42 <rideId> NX EX 30
NX makes this atomic, only one caller wins. The lock value is the rideId so the lock holder can identify and release their own lock if needed.
Data Flow Link to heading
Ride Matching Service receives ride request
→ GEOSEARCH: get 10 nearest available drivers
→ for each driver (closest first):
SET lock:driver:<id> <rideId> NX EX 30
→ acquired: remove driver from geo set, notify driver, stop
→ failed: driver already locked, try next candidate
→ no candidates acquired: re-enqueue ride request after delay
The 30s TTL covers the driver’s response window. If the driver doesn’t respond in time, the lock expires automatically, and the driver becomes eligible again for the next match attempt.