Open Zomato. Order biryani. Watch the little bike icon crawl towards your apartment in real time.
That icon updates every second or two. Sometimes faster. Your rider turns a corner and the map reflects it almost immediately. It feels smooth, responsive, alive.
Now think about this from a systems perspective. Zomato has hundreds of thousands of active delivery riders at any given time during peak hours. Each one is sending GPS coordinates every 1–3 seconds. That's potentially millions of location pings every minute.
If every single ping wrote to a PostgreSQL row, the database would be on fire within seconds. Literally. The write throughput alone would melt any traditional relational database.
So how does it actually work?
The Obvious Approach (That Doesn't Work)
If you're building a toy project, you'd probably do something like this: the rider's phone sends a GPS coordinate, your API receives it, and you run an UPDATE riders SET lat = X, lng = Y WHERE rider_id = 123 query.
For 10 riders, this is fine. For 100, still manageable. For 300,000 concurrent riders each pinging every 2 seconds? That's 150,000 writes per second to a single table. PostgreSQL would start falling behind, locks would pile up, and your read queries from customer apps would timeout because the database is choking on writes.
This is the fundamental problem: GPS data is hot, fast, and disposable. It changes constantly and nobody cares what a rider's location was 5 seconds ago. The only thing that matters is where they are right now. Traditional databases are designed for data you want to keep. Location data is designed to be overwritten.
What Actually Happens: Memory, Not Disk
The real answer is that rider locations live in memory. Not on disk. Not in a database table. In-memory stores like Redis or purpose-built geospatial caches.
Here's the mental model. When a rider's phone sends a GPS ping, it doesn't go to a database. It goes to a fast in-memory layer that basically says: "Rider 456 is currently at latitude 19.076, longitude 72.877." That's it. The old value is overwritten. No history. No append. Just the latest position.
Redis is absurdly good at this. A single Redis instance can handle hundreds of thousands of writes per second. And because we're just overwriting one key per rider, the memory footprint stays constant regardless of how many pings have come in. Rider 456's key always holds exactly one location.
But How Does the Customer's Map Update?
This is the part that trips people up. The rider's phone is pushing coordinates into the system. The customer's phone needs to pull the latest position to render the map. But you definitely don't want the customer app polling your servers every second asking "where is my rider now?" — that would be millions of read requests on top of the millions of writes.
There are two common patterns here.
Pattern 1: Smart polling with short TTLs. The customer app polls every 2–3 seconds, but hits a CDN or edge cache rather than the origin server. The cache is refreshed from Redis. This is simpler to build and surprisingly effective when your caching layer is well-designed.
Pattern 2: Push via WebSockets or SSE. The backend subscribes to rider location changes and pushes updates directly to the customer's open connection. No polling at all. The server tells the app "your rider moved" the instant it happens. This is more complex but gives you sub-second updates.
Most large food delivery platforms use a mix of both. WebSockets for active tracking screens, and smart polling as a fallback when WebSocket connections drop (which happens a lot on mobile networks).
The Ingestion Pipeline: Handling the Firehose
Now zoom out a bit. We're talking about hundreds of thousands of riders, each pinging every couple of seconds. That's a firehose of data hitting your backend. You can't just have a single API server receiving all of this.
The standard pattern is to put a message broker in front. Something like Kafka or AWS Kinesis. The rider's phone sends its GPS ping to a lightweight ingestion endpoint. That endpoint doesn't process anything. It just drops the message into a Kafka topic and immediately returns a 200 OK.
Downstream, a pool of consumers reads from that Kafka topic and updates Redis. If Kafka is briefly backed up, messages buffer. If a consumer crashes, another one picks up the partition. If Redis has a blip, the consumer retries. The ingestion endpoint itself stays fast and never blocks.
This separation is critical. The thing receiving data and the thing storing data are not the same thing. They scale independently and fail independently.
Geospatial Queries: "Show Me Riders Near This Restaurant"
There's another dimension to this. When you place an order, Zomato needs to find available riders near the restaurant. That's a geospatial query: give me all riders within a 3 km radius of this point.
Redis has a built-in data structure for this called GeoSets. Under the hood it uses a Sorted Set with geohash encoding. You can add a rider's position with GEOADD, and then query with GEOSEARCH to find all riders within a given radius. It's fast, it's in-memory, and it handles the math for you.
This is why the assignment algorithm can find a rider and assign them to your order within seconds of you placing it. It's not scanning a database table and calculating haversine distances. It's querying an in-memory geospatial index that's being updated in real time.
What About History? ETAs? Analytics?
If GPS data never touches a database, how do they calculate ETAs? How do they analyze delivery routes after the fact?
The answer is that the real-time path and the analytics path are completely separate pipelines.
The real-time path goes: phone → ingestion API → Kafka → Redis. Fast. Disposable. Overwritten constantly.
The analytics path forks off from the same Kafka topic but goes somewhere else entirely. A separate set of consumers reads the same GPS pings and writes them into a data warehouse — something like ClickHouse, BigQuery, or S3 as raw event logs. This is the historical record. It's append-only, batched, and doesn't need to be fast. It just needs to be complete.
ETA calculations are their own beast. They're typically powered by ML models that consume historical route data, current traffic conditions, restaurant preparation times, and the rider's current speed. The live GPS position feeds into this, but the ETA model itself is a separate service.
Why This Design Is Elegant
Step back and look at what's happening here. A single GPS ping from a rider's phone fans out into multiple systems, each optimized for a different job:
- Redis holds the current position — fast reads, fast writes, always fresh
- Kafka absorbs the firehose — buffering, fault tolerance, decoupling
- WebSockets push updates to the customer — real-time, no polling
- Data warehouse stores history — analytics, ETAs, route optimization
No single system does everything. Each one does one thing well. And the GPS ping never has to wait for a slow database write before the next one arrives.
That's the core insight: real-time location tracking is a streaming problem, not a storage problem. The moment you treat it like a storage problem and reach for a database, you've already lost.
The Numbers That Make This Real
Let's do rough back-of-envelope math for a platform like Zomato during a Friday dinner rush.
300,000 active riders. Each pinging every 2 seconds. That's 150,000 GPS events per second flowing through the system. Each event is maybe 200 bytes (rider ID, lat, lng, timestamp, speed, bearing). That's about 30 MB/s of raw location data.
For Redis, 150K writes per second is completely comfortable — a well-configured instance handles 500K+ operations per second. For Kafka, 30 MB/s is a light workload.
But for PostgreSQL? 150K row updates per second on a single table? With indexes? With concurrent reads from customer apps? That's a nightmare. The database would spend more time managing locks and WAL writes than actually serving useful queries.
This is why the architecture exists. Not because engineers love complexity, but because the numbers demand it.
Wrapping Up
Next time you're watching that little bike icon inch closer to your apartment, remember: none of those movements are being written to a traditional database. They're flowing through an in-memory pipeline that's been designed from the ground up to handle the kind of throughput that would destroy a conventional backend.
The rider's GPS coordinates live in memory. They're pushed, not polled. They're overwritten, not appended. And the database only gets involved long after your food has arrived, when some analytics job quietly processes the route data in batch.
It's one of those systems that looks simple from the outside but has some genuinely beautiful engineering underneath. The best infrastructure usually does.
