Moxace

( READING )

Real-Time Order Tracking: Architecture for Peak Hours

Event pipelines, geo-update throttling and graceful degradation: the design decisions that keep live order tracking honest when the dinner rush arrives.

August 12, 2026Aditya Bhimrajka7 min read

Share this article

What Peak Hours Actually Break

Order tracking looks like a solved problem: a status label, a map pin, an ETA. The difficulty stays invisible until the busiest hour of the week, when a few thousand concurrent orders each emit status changes, location pings and assignment events, while every customer keeps the tracking screen open from checkout to doorstep. Tracking is also the most politically sensitive screen in a delivery product, because it is the one the user stares at whenever something feels slow. Almost every failure we have debugged in this area was a load-shape problem, not a feature problem: the code was right, and the system drowned at exactly the predictable moment.

The load is spiky and correlated in the worst way. Orders cluster around dinner service, so the peak is not a gradual climb but a step function at seven o'clock. Reads amplify: one order is watched by the customer, refreshed by the courier's own app, and occasionally opened by a support agent, so every order produces several simultaneous viewers of the same state. Meanwhile the write side is a firehose, since a single courier moving through traffic can generate a location update every few seconds, multiplied by every courier on the road. Design for the average and the peak will simply delete the feature.

The discipline that holds is deciding what real-time means per data type, because the streams have different tolerance for staleness. Order status, placed, accepted, picked up, delivered, must arrive within a second or two but changes rarely. Courier position can be five to ten seconds stale and nobody will notice, provided the interface says so honestly. An ETA is a computed estimate that only deserves recalculation on meaningful new information. Architectures fail when they treat all three identically, either spending real-time infrastructure on the ETA or, worse, polling the status endpoint at courier-location frequency.

WebSockets, Polling, and the Hybrid That Usually Wins

Polling is underrated and too often skipped on principle. A client that fetches order status every ten to fifteen seconds hits an endpoint that is trivially cacheable, stateless, and indifferent to flaky mobile networks, and at moderate scale that is a perfectly respectable architecture. Its weakness is arithmetic: read volume equals users times refresh rate, and the load lands hardest precisely when the most people are staring at screens. Polling also cannot do sub-second freshness cheaply, because keeping the interval short enough to feel live multiplies the read load past what the cache tier wants to serve.

WebSockets solve the amplification problem but import a stateful tier into a world that would rather be stateless. Every connection is a file descriptor, session memory, and a route through the load balancer, and mobile clients churn constantly through cell handoffs and app backgrounding. During a network blip, thousands of devices reconnect simultaneously, and an unprepared backend experiences what looks like a deliberate attack. Connection gateways therefore need to be a dedicated, deliberately dumb tier: they hold sockets, authenticate, and forward events over an internal bus, while business logic stays behind them in services you scale independently.

The hybrid we ship most often assigns a transport per stream. Status transitions ride push notifications, which the platforms deliver reliably even when the app is backgrounded. The live courier position uses a WebSocket while the tracking screen is open, and drops the connection the moment the user leaves. The order status endpoint stays pollable and CDN-cacheable as a fallback, and every stream carries a resume token so a reconnecting client can catch up without refetching the world. That last detail, resumability, is what turns three transports from complexity into resilience, and it is a standard part of our delivery platform feature set.

Throttling the Geo Firehose

Courier location is the most expensive stream in the system, and the cheapest place to fix it is the device. Raw GPS fixes can arrive every second; almost none of that resolution survives to the user's eyes. We throttle on the producer side, sending on significant displacement or on a timer, whichever comes first, and batching readings when connectivity degrades so the trail catches up later. A courier idling at a traffic light does not need to publish every fix, and the battery savings alone make device-side throttling worth the discipline.

Server side, the pattern is coalescing rather than queuing. Keep the latest position per courier in an in-memory buffer and publish that, dropping superseded readings instead of lining them up for a consumer that only ever needed the newest one. The full history still belongs in the event log, written asynchronously, because disputes, route analytics and courier-safety reviews all want the trail, but nothing in the hot path should wait on it. Separating the latest-value path from the historical path keeps the latency budget where users can actually feel it.

Downstream, resist forwarding raw coordinates to the customer's map. Snap the position to the road network, smooth it, and interpolate between updates so the pin glides instead of teleporting. Then throttle per viewer: a customer watching a courier approach does not need one update per second, and a five-to-fifteen-second cadence with client-side interpolation reads as smooth while cutting fan-out by an order of magnitude. The map should feel alive; it does not need to be scientifically accurate at human-perceivable timescales.

The Event Pipeline Underneath

Under the transports sits the part that decides whether this survives its second year: an event pipeline. Status changes and throttled position updates publish as events, order placed, courier assigned, courier moved, order delivered, and consumers derive everything else from them: the tracking view, notification triggers, analytics, the support timeline. Producers stop caring who is listening, which means the third or fourth feature that needs order events becomes a consumer rather than a new integration. Every tracking system we have retrofitted wished it had started here.

Two correctness details are non-negotiable: ordering and idempotency. At-least-once delivery means duplicates will arrive, and network routing means a courier-moved event can land after order-delivered. Key events by order, version them explicitly, and make every consumer safe to run twice, because an idempotent consumer turns a whole class of three-in-the-morning incidents into a no-op. The alternative is a tracking screen that flickers between delivered and in-transit because two consumers disagreed about the sequence of the world.

The event log is also your debugging and recovery story. When a bug corrupts derived state, you rebuild the view by replaying rather than performing surgery on live records. When a customer disputes an order, the log is the audit trail. When analytics wants tomorrow's dashboard, it consumes yesterday's events. Teams that skip the log end up rebuilding the same context three times in three systems; teams that keep it treat the tracking screen as one disposable view over a durable history.

Graceful Degradation Is a Feature

Assume components will degrade at peak and design the tiers in advance. Full live tracking with a moving pin is tier one. Tier two is a static pin with a last-updated timestamp, honest about its staleness. Tier three is status-only text, and tier four hides the map entirely. Each tier is correct, just less fresh, and the system should descend them deliberately rather than fail into showing something wrong. A map that freezes in place looks like abandonment; a timestamp that says thirty seconds ago looks like engineering.

Load shedding follows the same priority logic. Writes, courier updates and status transitions, outrank reads, because stale reads recover but lost writes corrupt state. Serve reads from cache first and let them be slightly old; put circuit breakers around the geo pipeline so its failure degrades to last-known-position instead of cascading into the status path. Consumers should carry backpressure, dropping position updates they cannot process in favor of the ones they can. The status stream is sacred; everything else is negotiable under load.

Finally, communicate degradation in the product. A banner saying updates may be delayed during high demand converts frustration into understanding, and an ETA that widens its band instead of collapsing looks confident rather than broken. Users forgive labeled staleness; they punish disguised staleness, which is what a frozen map is. The teams that survive their first few peak seasons are the ones that decided, in calm code reviews months earlier, exactly what their system would look like while drowning.