For live driver tracking, we do not treat GPS as a stream of database records. We treat it as transport data with a clear freshness boundary.
The production split is straightforward:
- GoMobile MQTT client in the driver apps owns connection state, reconnects, telemetry serialization, and the fallback decision.
- EMQX accepts the primary MQTT stream and fans it out to the consumers that need it.
- Redis holds current driver state for matching and live maps.
- PostgreSQL owns orders, ride-state transitions, and the selected route history worth retaining.
- HTTP ingestion is a secondary transport when a device cannot keep an MQTT session alive.
Real-time location stack
A fast path for movement. A durable path for business truth.
Driver telemetry moves through the platform without making the transactional database absorb every GPS ping. Each layer keeps only the kind of state it is good at owning.
Driver app
Native GPS updates, adaptive frequency, persistent connection.
EMQX / MQTT
Authenticated telemetry ingress and targeted event fan-out.
Redis
Fresh location, availability, TTLs, proximity lookups, and locks.
PostgreSQL
Rides, state transitions, retained route history, and audit trail.
Hot path
Publish → validate → update latest state → notify only the consumers that need it.
Durable path
Persist selected trip points and final order events without coupling every map update to a database write.
Why We Used a GoMobile MQTT Client
The driver apps run on iOS and Android. We did not want two independent implementations of the most failure-prone part of the feature: connection lifecycle on unreliable mobile networks.
Instead, the MQTT transport is a shared Go module bound into the native applications through gomobile. The mobile UI still stays native; the shared module owns the behavior that must be identical across platforms:
- MQTT connect, disconnect, and reconnect state;
- telemetry envelope and topic selection;
- exponential backoff and the decision to use HTTP fallback;
- a monotonic device sequence number for every location update;
- the handoff back to MQTT after recovery.
The important win is behavioral consistency. We do not need to debug an Android-specific reconnect policy and a different iOS queue policy after a driver crosses a poor-coverage area. The state machine and event contract are shared; the native apps supply GPS updates and react to the state exposed by the module.
The Location Contract
Every update carries enough context for the backend to decide whether it can replace current state. A representative payload looks like this:
{
"driverId": "drv_4821",
"deviceSessionId": "7de0b4e2",
"sequence": 18402,
"latitude": 53.3498,
"longitude": -6.2603,
"heading": 182,
"speed": 9.4,
"accuracy": 12,
"recordedAt": "2026-08-04T10:14:32.481Z",
"status": "available"
}
deviceSessionId and sequence matter more than the JSON field names. Mobile networks can delay, duplicate, or reorder messages. The backend accepts an update only when it is newer than the last accepted event for that driver session. A delayed point cannot move a car backward on the map, and a retry cannot create a second state transition.
The MQTT topic is scoped to the authenticated driver and city/fleet context. Passenger apps never subscribe to the full fleet stream. Once an order is assigned, the passenger receives only that trip's driver updates through a separate, authorized channel.
MQTT Is the Primary Transport
The GoMobile client keeps a persistent connection to EMQX. A GPS update is compact, published once, and routed to independent consumers:
The broker moves telemetry. It does not decide whether a driver is assignable or whether an order changed state. Those are domain decisions owned by backend services.
HTTP Fallback When MQTT Drops
MQTT is efficient when the session is healthy, but a driver device can be behind a restrictive network, temporarily backgrounded, or unable to restore a broker connection quickly. In that case, we switch transport, not the location protocol.
The shared client has four meaningful states:
- MQTT connected — publish telemetry through the broker.
- Reconnecting — retry MQTT with backoff while the app continues collecting the latest GPS state.
- HTTP fallback — send the latest valid location through an authenticated HTTP ingestion endpoint.
- MQTT recovered — return to the primary path only after the broker session is usable again.
The fallback request uses the same envelope: driverId, deviceSessionId, sequence, recordedAt, accuracy, status, and coordinates. It lands in the same ingestion service as the MQTT event. The backend therefore applies the same checks regardless of transport:
- authenticate the driver and device session;
- reject impossible coordinates, expired timestamps, or a stale driver state;
- compare
sequencewith the last accepted event; - update the current Redis state only if the event is newer;
- emit the normalized update to matching, trip tracking, and dispatch consumers.
We do not replay a full offline GPS history when MQTT recovers. For live tracking, a five-minute-old coordinate is usually less useful than the latest position. The client keeps the newest valid state for fallback, sends that state when a path is available, and resumes normal MQTT publishing after recovery. Route history is a separate, controlled persistence concern.
This gives the driver app graceful degradation: lower-frequency updates may be visible during an outage, but the driver does not disappear from operations simply because the broker session was interrupted.
Redis: Current State, Not History
Redis contains the live view required for fast decisions:
- last accepted location and timestamp;
- availability and order-related status;
- city, tariff, and matching eligibility;
- expiring presence for drivers who stop reporting;
- short-lived locks while matching offers are in flight.
Every location record has a TTL. If a phone stops reporting, Redis removes its live eligibility instead of leaving a ghost car on the passenger or dispatcher map. Matching reads the nearest eligible drivers from this short-lived state, then applies its own business rules before making an offer.
Redis locks protect the contention point: the same nearby driver can be considered by multiple orders during a rush. A short atomic reservation ensures only one flow can successfully offer that driver at a time. PostgreSQL records the final assignment once the order state actually changes.
Real-world safeguards
The map stays credible when the network does not.
Mobile connectivity, GPS quality, and traffic spikes are noisy by nature. These controls keep imperfect input from becoming misleading operational state.
Freshness TTL
A disconnected driver ages out of the eligible supply set instead of becoming a ghost car.
Short-lived locks
Overlapping matching waves cannot successfully claim the same nearby driver.
Event validation
Identity, coordinate range, timestamp, and driver state are checked before shared state changes.
Ordered updates
A delayed point cannot overwrite a newer location and make the marker jump backward.
What We Persist to PostgreSQL
We do not write every location update to the transactional database. PostgreSQL stores the records that must remain auditable:
- ride and driver state transitions;
- assignment, acceptance, pickup, and completion events;
- route points selected for support, billing, dispute handling, or analytics;
- the final outcome of matching and payment flows.
That separation prevents GPS traffic from competing with booking transactions while still preserving the business history that cannot be reconstructed from a last-known Redis value.
Failure Rules That Keep the Map Trustworthy
The client and backend are deliberately conservative about what counts as live:
- old or inaccurate coordinates do not replace fresher accepted state;
- a device that stops publishing expires from matching eligibility;
- HTTP retries are idempotent because the sequence is checked server-side;
- a dashboard or analytics consumer can fail without blocking matching or the passenger's assigned-trip feed;
- a passenger sees a last-known location or an updating state rather than false real-time precision.
The main operational metrics are connection state, reconnect attempts, fallback activation rate, accepted-versus-rejected location events, Redis state age, and end-to-end delay from device timestamp to passenger update. These tell us whether the problem is GPS quality, mobile connectivity, the broker, or a downstream consumer.
The Core Design Choice
The result is not “MQTT instead of HTTP.” It is a transport-agnostic location contract with MQTT as the fast path and HTTP as the continuity path.
GoMobile keeps that behavior consistent on iOS and Android. EMQX distributes the stream. Redis makes current state cheap to query. PostgreSQL keeps the durable order history. And because both transports enter the same validation boundary, a driver remains one coherent entity in the system even while the network path changes underneath them.
Need a real-time mobility platform with driver tracking, matching, and resilient mobile connectivity? Talk to us — we build the system behind the map.