Real-Time Order Tracking with AWS AppSync: How We Cut Update Latency by 94%
How we replaced REST polling with AWS AppSync subscriptions at Rafeeq: live order tracking and partner alerts in under a second instead of 45.

At Rafeeq we process over 16 million orders a year, and each order goes through roughly 12 status transitions: placed, accepted, preparing, picked up, on route, delivered. That's about 190 million status events a year, and every single one of them matters to three people at once: the customer watching the map, the partner preparing the order, and the driver on the road.
For years we did what most apps do: the mobile clients polled a REST endpoint every 10 seconds. It worked, and it quietly hurt us everywhere.
The problem with polling
- It's slow when it matters. With a 10-second interval plus network and processing time, the median delay between a real status change and the user seeing it was 7.5 seconds, and p95 was 14.2 seconds. A customer watching a "preparing" screen while their food is already on the road is a support ticket waiting to happen.
- It's wasteful. At peak we handled ~6,000 concurrent tracking sessions per city. Polling generated 182 million status-check requests per day, and our logs showed 94% of them returned no change at all.
- Partners reacted late. New-order alerts reached partner tablets on the next poll cycle. Average time from order placement to partner acceptance was 45 seconds. In quick-commerce, that delay compounds into late deliveries.
Why AppSync
We evaluated three options: raw WebSockets on our own infrastructure (API Gateway + Lambda), Firebase Cloud Messaging as a signal channel, and AWS AppSync. AppSync won for three reasons:
- Managed WebSocket fan-out. One GraphQL mutation fans out to every subscribed client. No connection registry, no heartbeat handling, no scaling WebSocket servers ourselves. AppSync handles hundreds of thousands of concurrent connections natively.
- Subscription filtering. A driver subscribes to their orders, a partner to their store, a customer to their single order. Server-side filtering means clients only receive what they're authorized to see, enforced with Cognito claims.
- It fit our stack. Resolvers connect directly to DynamoDB and Lambda, and IAM handles service-to-service auth. We were fully on AWS already; there was no new operational surface to own.
The architecture
The flow is deliberately boring:
- The order service writes a status change and calls a GraphQL mutation on AppSync (server-to-server, IAM-signed).
- AppSync evaluates subscription filters: order ID for customers, store ID for partners, driver ID for drivers.
- Every matching client gets the update pushed over the existing WebSocket: no new request, no polling loop.
subscription OnOrderUpdate($storeId: ID!) {
onOrderUpdate(storeId: $storeId) {
orderId
status
etaMinutes
courierLocation { lat lng }
}
}
The results
Latency was the headline win. Median propagation dropped from 7.5 seconds to under half a second:
And because updates are pushed instead of pulled, the request volume on our API tier collapsed:
The numbers that mattered to the business:
| Metric | Before (polling) | After (AppSync) |
|---|---|---|
| Median update latency | 7.5 s | 0.4 s |
| p95 update latency | 14.2 s | 1.1 s |
| Status API calls/day | 182M | 11M (−94%) |
| Partner order acceptance time | 45 s avg | 8 s avg |
| Mobile battery drain (tracking screen, 30 min) | 6.1% | 2.3% |
The partner acceptance number is the one I care about most. Reaching the partner's app in under a second means the kitchen starts 37 seconds earlier on every order. At 16M orders a year, that's measurable in delivery times and customer ratings, not just dashboards.
Lessons from production
- Design for reconnects from day one. Mobile networks drop constantly. On reconnect, clients fetch a snapshot via a normal query then resubscribe. Otherwise you miss events that happened during the gap.
- Keep a polling fallback. Roughly 2% of our sessions run behind networks that break WebSockets. The app silently degrades to 30-second polling; users never know.
- Watch your subscription auth as carefully as your API auth. Filtering is your tenant isolation. We test subscription rules in CI the same way we test REST authorization.
- Cost scales with connections and messages, not requests. For us that meant roughly 38% lower cost on this path than the polling fleet it replaced. But model your own message volumes first; a chatty schema can flip that math.
Real-time stopped being a "nice to have" the day we saw partners accepting orders in 8 seconds. If your product has anyone staring at a screen waiting for state to change, push it. Don't make them ask.
Recommended reading

Per-Team Cost Allocation in Shared Kubernetes Clusters: From Chaos to Clarity
Implementing accurate per-namespace cost allocation in multi-tenant Kubernetes clusters, covering request vs. usage attribution, shared resource amortization, and building showback dashboards that drive accountability.

Measuring and Eliminating Toil: From 40% to 12% of Engineering Time
A systematic approach to identifying, measuring, and automating toil—the repetitive operational work that scales linearly with service growth and prevents engineers from doing creative work.

Serverless Postgres in Production: Branching, Scale-to-Zero, and the End of Database Provisioning
Running Neon serverless Postgres in production for 8 months — covering database branching workflows, scale-to-zero economics, connection pooling, and migration from RDS.

Comments
No comments yet. Be the first to share your thoughts.