System Design HLD Example: Ride-Sharing (Uber/Lyft)
1. Use Cases & Requirements
A ride-sharing platform is a high-velocity geospatial matching engine.
🎯 System Scenario & Requirements
System Goal
A ride-sharing platform is a high-velocity geospatial matching engine.
Actors & Components
- Mobile Real-time Clients
- Geospatial & Matching
- Location State & Dispatches
- Driver Mobile App
- Passenger App
- WebSocket Gateway
- Location Service
- Matching Engine
Functional Requirements
- TLDR: A ride-sharing platform is a high-velocity geospatial matching engine.
- Drivers stream GPS coordinates every 5 seconds into a Redis Geospatial Index .
- When a rider requests a trip, the Matching Service executes a GEORADIUS query to find the closest available drivers.
Constraints & Assumptions
- Choosing the right components requires balancing performance against consistency constraints.
- Transport Protocols: WebSockets vs SSE vs HTTP Polling WebSockets: Full-duplex transport, low overhead.
⚡ Execution Stages & Decision Trace
Stage 1 — 2. Capacity Estimations & Scale Goals
Let's compute our core storage, throughput, and memory requirements to establish concrete design scale goals: Although 50 MB is small enough to fit on a single caching instance, 100,000 writes/sec reaches the operationa…
This dictates that we must partition or shard our caching layers horizontally to scale write throughput rather than storage capacity.
Deep Dive
Scenario context
A ride-sharing platform is a high-velocity geospatial matching engine. Drivers stream GPS coordinates every 5 seconds into a Redis Geospatial Index. When a rider requests a trip, the Matching Service executes a GEORADIUS query to find the closest available drivers. A distributed Trip State Machine ensures transactional consistency (no double-matching), while WebSockets provide the real-time "car on map…
This walkthrough focuses on 1. Use Cases & Requirements.
Before making a design choice, clarify:
- Imagine it’s New Year's Eve in New York City.
- At 12:05 AM, one million people open their ride-sharing apps simultaneously.
- In the background, 50,000 drivers are moving through the city, sending GPS updates every few seconds.
Concepts in play: system-design-hld-architectures, caching, database-indexing, event-driven-architecture
Stage 1 — 2. Capacity Estimations & Scale Goals
- Let's compute our core storage, throughput, and memory requirements to establish concrete design scale goals: Although 50 MB is small enough to fit on a single caching instance, 100,000 writes/sec reaches the operationa…
- This dictates that we must partition or shard our caching layers horizontally to scale write throughput rather than storage capacity.
- Key Takeaway: Design distinct scaling models for the 100k QPS location ingestion path and the 500 QPS transaction path.
Observable outcome: Key Takeaway: Design distinct scaling models for the 100k QPS location ingestion path and the 500 QPS transaction path.
Stage 2 — 3. High-Level Design (HLD)
- The high-level design establishes the platform's architectural system boundary and separates the volatile, write-heavy driver tracking path from the durable, transactional matching and trip lifecycle database.
- System Boundary & Context Model External actors (Riders, Drivers) and third-party integrations (Maps, Payments, Notifications) connect through well-defined edge boundaries to the core system services: High-Level Subsyst…
- Location Ingestion Service: Processes 5s driver GPS location broadcasts and pushes them to Redis and Kafka.
- Redis Geospatial Cache: Maintains the real-time localized index of available drivers.
graph TD
Driver -->|GPS 5s| LS[Location Service]
LS -->|GEOADD| Redis[(Redis Geo Index)]
Rider -->|Request Ride| MS[Matching Service]
MS -->|GEORADIUS| Redis
MS -->|Lock Driver| TS[Trip Service]
TS --> DB[(Trip DB: Postgres)]
TS -->|Match Event| NS[Notification Service]
NS -->|Push| Driver
NS -->|Push| Rider
LS -->|Stream| WS[WebSocket Gateway]
WS -->|Live Update| Rider
Observable outcome: Redis Geospatial Cache: Maintains the real-time localized index of available drivers.
Stage 3 — 4. Detailed Design: Location Ingestion & Geohashing
- A traditional relational database with a B-tree index cannot execute two-dimensional range queries efficiently under heavy load.
- To solve this, the Location Service leverages Geohashing .
- The Mechanics of Geohashing Geohashing partitions the Earth's surface into a hierarchical grid of cells.
- Each grid cell is represented by a unique base-32 string.
Observable outcome: Each grid cell is represented by a unique base-32 string.
Stage 4 — 5. Detailed Design: Matching Engine & Distributed Locking
- A core correctness invariant is that a driver must never be matched to two riders at the same time .
- Double-booking is a catastrophic user experience failure.
- Candidate Discovery: The Matching Service queries GEORADIUS to find available drivers within a 2km radius.
- Lock Acquisition: For each candidate, the service attempts to acquire an atomic distributed lock in Redis: 3.
const acquired = await redis.set(`driver_lock:${driverId}`, 'locked', 'NX', 'PX', 15000);
Observable outcome: Lock Acquisition: For each candidate, the service attempts to acquire an atomic distributed lock in Redis: 3.
Stage 5 — 6. Detailed Design: WebSocket Gateway Fleet & Real-Time Updates
- Pings from active drivers must be streamed live to the matched rider's map display to provide the expected real-time tracking experience.
- Gateway Scalability At a scale of 1 million concurrent riders, maintaining WebSocket connection states on a single instance is impossible.
- We scale this horizontally: Stateless Gateways: A fleet of WebSocket gateway nodes holds open TCP connections with client apps.
- Redis Pub/Sub Backplane: When the Location Service receives a driver update, it publishes the coordinate packet to Redis Pub/Sub on channel trip:ws:${riderId} .
Observable outcome: Redis Pub/Sub Backplane: When the Location Service receives a driver update, it publishes the coordinate packet to Redis Pub/Sub on channel trip:ws:${riderId} .
Stage 6 — 7. Database Schema & Data Models
- Relational databases handle durable state transactions, while Redis sharding handles rapid geospatial index reads.
- Relational Tables (PostgreSQL) Driver Profile Table driver id (UUID, PK) status (ENUM: OFFLINE, AVAILABLE, ON TRIP) vehicle type (VARCHAR: UberX, UberXL, Black) rating (DECIMAL) city id (VARCHAR) Trip Ledger Table trip…
Observable outcome: Relational Tables (PostgreSQL) Driver Profile Table driver id (UUID, PK) status (ENUM: OFFLINE, AVAILABLE, ON TRIP) vehicle type (VARCHAR: UberX, UberXL, Black) rating (DECIMAL) city id (VARCHAR) Trip Ledger Table trip…
End-to-end outcome
After working through the stages, you should be able to explain:
- Architecture Cheat Sheet Location Ingestion: Mobile App $\rightarrow$ API Gateway $\rightarrow$ Kafka $\rightarrow$ consumer writes to Redis Geo Index.
- Matching Flow: Rider requests match $\rightarrow$ execute GEORADIUS $\rightarrow$ acquire Redis NX lock $\rightarrow$ write trip record to Postgres $\rightarrow$ notify driver.
Alternatives considered
The source focuses on one primary design. A second architecture is not compared, so no alternative is invented here.
Failure and degraded modes
Choosing the right components requires balancing performance against consistency constraints.
- Detection: Choosing the right components requires balancing performance against consistency constraints.
- User or operator impact: Choosing the right components requires balancing performance against consistency constraints.
Transport Protocols: WebSockets vs SSE vs HTTP Polling
- Detection: WebSockets: Full-duplex transport, low overhead.
- User or operator impact: WebSockets: Full-duplex transport, low overhead.
Spatial Storage: Redis vs PostGIS vs Cassandra
- Detection: Redis ZSET: In-memory, sub-millisecond lookups.
- User or operator impact: Redis ZSET: In-memory, sub-millisecond lookups.
