PrepZone Logo
PrepZone

Proximity Service (Uber)

Match riders to nearby drivers in real time using geohash grids and streaming location updates.

Why this matters

  • Ride-hailing, food delivery, and StreamHub's "join nearby watch party" all need sub-second nearest-neighbour lookup over millions of moving entities.
  • Brute-force distance calculation against every driver is O(n) per request — unacceptable at Uber scale (millions of drivers online).
  • Geohash grids or quad-trees partition space so you only scan a handful of cells, not the entire fleet.

Proximity service building blocks

  • Location ingestion — drivers push GPS updates via WebSocket or UDP every 2–4 seconds.
  • Spatial index — geohash prefix tree, Redis GEO, or custom quad-tree storing driver ID + coordinates.
  • Matching engine — finds top-K nearest available drivers, applies business rules (rating, vehicle type).
  • Dispatch queue — handles concurrent match requests; first-accept-wins or broadcast-to-top-3.
  • ETA service — routing engine estimates pickup time for each candidate.

High-level architecture

Proximity / ride-matching

WebSocketCLIENT
Driver appGPS every 4s
INTEGRATION
Kinesislocation stream
DATABASE
ElastiCache G…GEORADIUS
COMPUTE
Match APIEKS
CLIENT
Rider appWS assigned
Driver GPS → Kinesis → Redis GEO → match API → WebSocket to rider.
Java
POST /v1/location/update
Authorization: Bearer <driver_token>
Content-Type: application/json

{
  "driver_id": "drv_8f2a",
  "lat": 12.9716,
  "lng": 77.5946,
  "heading": 142,
  "timestamp_ms": 1740847200000
}
Java
POST /v1/rides/request
Content-Type: application/json

{
  "rider_id": "rdr_3c91",
  "pickup": { "lat": 12.9700, "lng": 77.5900 },
  "vehicle_type": "standard"
}

Geohash grid indexing

Geohash encodes lat/lng into a base-32 string. Shared prefixes mean geographic proximity.

Java
import geohash2

lat, lng = 12.9716, 77.5946
hash_6 = geohash2.encode(lat, lng, precision=6)  # "tdr1w3" — ~1.2 km cell
hash_7 = geohash2.encode(lat, lng, precision=7)  # "tdr1w3y" — ~150 m cell

Store drivers in Redis sorted sets keyed by geohash prefix:

Java
GEOADD drivers:tdr1w3 77.5946 12.9716 drv_8f2a
GEORADIUS drivers:tdr1w3 77.5900 12.9700 2 km ASC COUNT 10

Matching flow

AspectStrategyTrade-off
Broadcast to top-3Send ride offer to 3 nearest driversFast match; may overload popular drivers
Serial dispatchOffer to #1, wait 10s, then #2Fairer; slower average match time
Batch matchingOptimise all pending rides every 5sBetter global assignment; higher latency
Surge pricingRaise price in high-demand cellsBalances supply/demand dynamically
  • Broadcast to top-3

    StrategySend ride offer to 3 nearest drivers
    Trade-offFast match; may overload popular drivers
  • Serial dispatch

    StrategyOffer to #1, wait 10s, then #2
    Trade-offFairer; slower average match time
  • Batch matching

    StrategyOptimise all pending rides every 5s
    Trade-offBetter global assignment; higher latency
  • Surge pricing

    StrategyRaise price in high-demand cells
    Trade-offBalances supply/demand dynamically

Uber uses a mix: serial dispatch with surge multipliers per geohash cell.

Handling location staleness

Drivers go offline, lose signal, or stop updating. A location older than 30 seconds is unreliable.

Java
{
  "driver_id": "drv_8f2a",
  "lat": 12.9716,
  "lng": 77.5946,
  "updated_at": "2026-04-01T14:32:10Z",
  "status": "available",
  "ttl_seconds": 30
}

Background sweeper removes expired entries from the spatial index. On match, reject candidates whose updated_at exceeds the TTL.

Scale estimates

MetricEstimate
Active drivers globally5M peak
Location updates per second5M / 3s ≈ 1.7M writes/s
Match requests per second~50K peak
Geohash cells queried per match9 (cell + neighbours)
Redis memory per driver~100 bytes → 500 MB total
  • Active drivers globally

    Estimate5M peak
  • Location updates per second

    Estimate5M / 3s ≈ 1.7M writes/s
  • Match requests per second

    Estimate~50K peak
  • Geohash cells queried per match

    Estimate9 (cell + neighbours)
  • Redis memory per driver

    Estimate~100 bytes → 500 MB total

Shard the spatial index by city or geohash prefix to keep each Redis cluster under 1M ops/s.

WebSocket connection management

Drivers maintain persistent WebSocket connections to regional gateway servers. On disconnect, mark driver unavailable immediately.

Java
# Connection gateway scaling (Kubernetes)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: location-gateway
spec:
  replicas: 20
  template:
    spec:
      containers:
        - name: gateway
          resources:
            requests:
              cpu: "2"
              memory: 4Gi
          env:
            - name: MAX_CONNECTIONS_PER_POD
              value: "50000"

Quick recall

Everything you need if you only revisit this box.

  • Proximity = frequent location writes + fast spatial lookup, not full-table scans.
  • Geohash prefixes or Redis GEO partition space into queryable cells.
  • Match flow: query cell + neighbours → filter by status/TTL → rank by distance → dispatch.
  • Stale locations (>30s) must be evicted; disconnected drivers marked unavailable instantly.
  • Shard spatial indexes by region; 1.7M location writes/s requires horizontal partitioning.

Test yourself

Answer these before moving on — recall is what makes it stick.