... and how I'd do it again.
Every engineer has a few projects they love to talk about. This is one of my favorites. It’s the story of a real-world performance bottleneck that nearly brought our services to a halt, the couple of attempts we made to fix it, and the simple, elegant workaround we eventually found to keep our servers alive. Over the years, this puzzle has become my go-to systems design question when interviewing other engineers. After sketching it on whiteboards dozens of times and watching people wrestle with the exact same constraints I faced, I realized I’d never actually written the whole thing down.
While partial pieces of this puzzle are well-documented in database theory—such as using multi-dimensional indexes like R-trees or mapping coordinates to one-dimensional keys via space-filling curves—I've never actually seen this exact end-to-end problem statement, and real-world operational trade-offs, outlined in a single place.
A small note before we dive in: the system described here is hypothetical. It's inspired by something I built over six years ago, but the details have been changed enough that it carries very little resemblance to what was actually deployed, and that's intentional. Think of it as a hypothetical reconstruction, not a leak.
A long time ago in a logistics company far, far away, I had the challenge to build a real-time system to track a large fleet of drivers and orders.
Matching happened in real-time (think of it as Uber Eats), and the drivers could opt-in or out of each request.
Matching per se won't be the main topic of this tutorial, and I hope to discuss it in another one. But it does impose some additional requirements on our system: we need to frequently poll "all drivers that are available near this order" or, alternatively, solve for the best match between all unassigned drivers and orders.
In terms of requirements, you can imagine every driver has a smartphone, and they send their accurate location on average 15 times per minute. We had to design the system for a fleet of 100k drivers, totaling 25k position "writes" per second. During peak hours, we had to solve for 3k orders in parallel.
When I first joined, the company had a very standard, intuitive solution in place: a relational database (think PostgreSQL with PostGIS extensions) doing all the heavy lifting.
The architecture essentially relied on two main tables:
Every time a driver's app pinged the server (15 times a minute), the backend would run an UPDATE on
the driver_position table. Then, when a new order came in, the matching engine would run a spatial
query—something like ST_DWithin()—to find all available drivers within a certain radius of the
pickup_location.
On paper, GIS extensions are built exactly for this. In reality, it was a disaster waiting to happen at our scale. Here's why:
UPDATEs per second on a single table. Because the table had a spatial index (like an R-tree or
GiST index) to make queries fast, every location update meant the database had to constantly rebalance and
rewrite the index tree. This caused massive locking and I/O bottlenecks.
With the database crashing, the natural next thought is: let's move it to memory! Redis has built-in geospatial
commands like GEOADD and GEORADIUS, making it seem like the perfect candidate.
Redis uses Geohashes stored in Sorted Sets under the hood. It's incredibly fast. Handling 25,000 writes per
second is a walk in the park for Redis. We could simply use GEOADD to update driver locations and
use GEORADIUS to fetch nearby drivers for new orders.
While Redis solved the write contention issue, it introduced a new set of problems entirely related to how it processes commands:
GEORADIUS query over a dense city center
might take a few milliseconds. If you multiply those few milliseconds by 3,000 parallel queries, you
completely exhaust the single thread, blocking all the incoming 25k writes.The breakthrough came when we decided to stop treating the database as a spatial engine. Instead, we shifted the spatial math into the application layer using Uber's H3 (a hierarchical hexagonal spatial index) and treated Redis as a dumb, hyper-fast key-value store.
When a driver pings their location, the backend computes the H3 index for that coordinate across several
different resolutions (for example, resolutions 5 through 14). In practice, this means k ≈ 10.
We then simply add the driver's ID to a Redis Set keyed by the H3 index (e.g.,
SADD h3:892a1072b5bffff driver_123). It's exactly 10 extremely fast, non-blocking string writes to
Redis.
When an order comes in and we need drivers within a certain radius, we don't ask the database to do a radial search. Instead, we:
k' (in practice, k' ≈ 3).SMEMBERS. Let's call the number of drivers
in a hexagon d. Because we picked the right resolution, d << 100.driver_ids that are roughly within the search radius.There is a hidden superpower in this approach: we don't actually have to guess the perfect search radius or number of queries. Because H3 is hierarchical, larger parent hexagons perfectly contain smaller child hexagons.
If our matching engine wants to evaluate at least 50 nearby drivers to find the perfect match, we can start our search at a very high resolution (a tiny hexagon). If that cell only yields 5 drivers, we simply drop a resolution level (zoom out to the parent hexagon) and query again. We keep moving up the hierarchy until our bucket hits our target driver count. Since these are just blazing-fast string lookups in our key-value store, doing 3 or 4 sequential "zoom-out" queries adds practically zero latency.
Notice that so far, we've only used Redis Sets to store a list of driver_ids mapped to an H3 index.
But how do we know their exact location or their timestamp? We need this for the final precise distance
calculation.
The solution is simple: during the write path, we make one additional write to a separate Redis
key where we serialize the driver's metadata (using a python dataclass for instance) to a JSON
value.
During the search, once you've retrieved the set of nearby driver_ids from the H3 index queries,
you run one single batch query (like MGET) to fetch all their exact locations. You then calculate
the precise point-to-point distances entirely in application memory.
Filtering by status in memory is usually perfectly fine since we only ever fetch a few dozen
drivers from the database. However, if your business logic requires strict, high-performance filtering (e.g.,
you only ever want to search for AVAILABLE drivers and ignore the rest), fetching metadata
for offline drivers is a waste of network I/O.
In this case, you can push the index directly into the Redis key during the write phase. Instead of writing to
h3:{res}:{index}, you write to h3:{res}:{index}:{status}.
This adds a slight write overhead if drivers switch statuses frequently, but it guarantees your read path only ever fetches exactly what it needs. This ensures your read stays a true $O(k' \times d)$ where $d$ is strictly the number of available drivers.
If you look closely at the write path above, you might notice a missing piece: how do we prevent the Redis Sets from growing indefinitely as drivers go offline? Redis doesn't allow expiring individual elements inside a Set natively.
The elegant solution is time-dependent rolling keys. Instead of just using the H3 index as the key, we append a time bucket to it (e.g., the current minute). When writing, we write to the current minute's bucket and set an expiry on the entire key. When reading, we simply query a fixed number of recent buckets depending on our freshness requirements.
You can choose the time window size to trade off the cost of reads vs. writes. If you need stricter freshness guarantees, you can simply filter the exact timestamps in-memory since you're already calculating precise distances for a very small number of drivers anyway.
By converting a complex 2D spatial problem into simple 1D string lookups, we removed all CPU bottlenecks from the database. Furthermore, because data is accessed strictly by string keys, sharding becomes trivial. You can distribute the keys across a massive cluster without any "cross-boundary" spatial query nightmares.