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
RDS Proxy in Production: What the Docs Don't Tell You
A year of RDS Proxy under 16M orders: multiplexing that works, the pinning trap that silently disables it, and the failover win nobody markets.
When the War Reached Our Cloud: Evacuating an AWS Region in 6 Hours
The attack that took down AWS Bahrain forced an emergency migration: our DR plan under real fire, and how Kiro moved 63 services in 6 hours, not 3 weeks.
Scaling Reads on RDS: Auto Scaling Groups, Redis, and CDN Caching in Front of Your Replicas
How we took our RDS read replicas from 74% CPU to 28% while traffic grew, by letting CloudFront and Redis absorb 85% of reads before they ever reach the database.

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