❯ Platform · Mobile · 2024
RideFlow
A horizontally-scalable ride-hailing dispatch platform — stateless Socket.io servers, PostGIS nearest-driver matching, and a 10-state trip lifecycle.
The problem
Ride-hailing is a live-systems problem dressed as a mobile app. Drivers move constantly, riders expect a match in under a second, and a dropped connection mid-trip becomes a support ticket at best. Small implementations keep all of that state in one process, which works until the first scale spike.
The approach
The dispatch core is stateless Node.js + Socket.io servers behind a load
balancer. Driver locations flow through Redis Pub/Sub, so any instance can
broadcast to any connection. Nearest-driver matching uses PostgreSQL + PostGIS
ST_DWithin with GIST indexes, fronted by a Redis GeoHash prefix cache for
sub-millisecond proximity lookups. The trip lifecycle is a 10-state machine
(REQUESTED, ACCEPTED, ARRIVED, IN_PROGRESS, COMPLETED, PAYMENT_PENDING, PAID,
plus the failure states around them) enforced on the server. Clients propose;
the server decides.
Architecture
- Rider App
- Driver App
- Socket.io Servers
- Redis Pub/Sub
- PostGIS Matcher
- Trip State Machine
FLOWS
- Rider Apprequest + eventsSocket.io Servers
- Driver Applocation + acceptSocket.io Servers
- Socket.io Serversbroadcast locationRedis Pub/Sub
- Socket.io Serversnearest driverPostGIS Matcher
- PostGIS Matchermatch resultSocket.io Servers
- Socket.io Serversenforce lifecycleTrip State Machine
The reconnection protocol was designed as a feature, not a patch: monotonic
message sequences, exponential backoff with jitter, server-side missed-message
replay, and a full state snapshot on reconnect. GPS polling adapts from 2
seconds on-trip to 30 seconds stationary, which made a measurable difference to
battery drain. Map rendering holds 60 FPS with 100+ markers through clustering
and viewport culling. The fare engine applies zone-based surge multipliers from
1.0× to 4.0×, cancellation fees, and per-city economics configurable at
runtime. Rider and driver apps are separate Flutter builds (Riverpod,
go_router, socket_io_client) sharing a rideflow_shared core package, with a
React admin dashboard. A multi-city simulation engine with synthetic drivers,
realistic route-following, and cancellations across five cities at roughly 200
drivers each was planned to stress-test dispatch before any real launch.
Key decisions
Server owns the state machine
If the app can guess trip state there are two sources of truth. Server-side enforcement kept cancellation and payment logic in one place.
Redis Pub/Sub instead of sticky sessions
Stateless servers scale horizontally without rebalancing, and location broadcasts reach any socket no matter which instance holds it.
GeoHash cache in front of PostGIS
The cache serves the hot path in microseconds; PostGIS stays the source of truth for geometry.
Reconnection as a protocol
Monotonic sequences and replay mean a dropped connection resumes exactly where it stopped, with no lost events.
What I'd do differently
The simulation engine came late in the project. Building it before the fare logic would have surfaced pricing edge cases earlier and given the dispatch loop a realistic workload to fail against. I would also define the trip lifecycle states in a small data-driven table from the start; a hand-rolled switch grew awkward as cancellation and payment states were added.