2 AM and the “Find the Nearest Driver” Problem
My phone buzzed incessantly. A food delivery client’s system was reporting constant timeout errors on the driver search API. Checking the logs, I saw a SQL query struggling to calculate distances using the Haversine formula across over 2 million rows. The server’s CPU hit 100% with just a few dozen concurrent requests, a situation where advanced MySQL and PostgreSQL monitoring becomes essential. Checking the logs, I saw a SQL query struggling to calculate distances using the Haversine formula across over 2 million rows.
Many developers starting out with map apps treat coordinates (Lat, Long) as two Float columns and use high school math for calculations. This works fine for a few hundred records. However, once the data reaches hundreds of thousands, or even tens of billions of records, it’s a performance nightmare. The only solution at that time was an emergency migration to PostGIS.
PostGIS isn’t just an extension; it transforms PostgreSQL into a true spatial database. It understands the nature of Points, Lines, or Polygons on a sphere. Instead of manual calculations, you let the database handle it with low-level optimized algorithms.
Geometry vs Geography: Don’t Make the Wrong Choice Early On
Before diving into the code, you need to distinguish between these two data types to avoid errors when calculating area or distance.
- Geometry: Used for flat surfaces (Cartesian). It treats the Earth as flat as a sheet of paper. This type is extremely fast to calculate, suitable for building floor plans or small areas.
- Geography: Used for spheres. It accounts for the Earth’s curvature. This is a mandatory choice for apps like Uber or DoorDash to calculate accurate GPS distances.
Don’t forget the SRID (Spatial Reference System Identifier). For global GPS data, always use SRID 4326 (the WGS 84 standard). With the wrong SRID, all your calculations will be meaningless.
Hands-on: Installing and Configuring PostGIS
1. Installing the Extension
If you’re using Ubuntu, you can install it directly via apt. This process takes only about 30 seconds:
sudo apt-get update
sudo apt-get install postgis postgresql-15-postgis-3
After installing the package at the OS level, you need to enable it within the database. Note: This extension must be enabled individually for each database you use.
-- Connect to the database
CREATE EXTENSION postgis;
-- Check the version
SELECT postgis_full_version();
2. Setting Up the Storage Table
Instead of separating lat and lng, combine them into a single GEOGRAPHY column. This keeps the data clean and allows you to leverage built-in functions.
CREATE TABLE cafes (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
location GEOGRAPHY(POINT, 4326)
);
3. Adding Coordinate Data
When inserting data, the order is crucial: Longitude first, then Latitude. If you swap them, your cafe might “fly” from Hanoi all the way to Antarctica.
INSERT INTO cafes (name, location)
VALUES ('Cong Caphe', ST_SetSRID(ST_MakePoint(105.8523, 21.0285), 4326));
To quickly process CSV lists of locations into JSON format before importing, I often use the converter at toolcraft.app. This tool runs client-side, so customer data remains completely secure.
Radius Search in the Blink of an Eye
This is where PostGIS shines. Suppose you need to find cafes within a 2km radius of a user’s location:
SELECT name,
ST_Distance(location, ST_SetSRID(ST_MakePoint(105.8500, 21.0300), 4326)::geography) as distance
FROM cafes
WHERE ST_DWithin(location, ST_SetSRID(ST_MakePoint(105.8500, 21.0300), 4326)::geography, 2000)
ORDER BY distance;
The ST_DWithin function isn’t just for filtering data. It’s designed to work seamlessly with indexes, allowing the system to immediately ignore irrelevant spatial areas.
Performance Optimization with GiST Indexes
If you query over 1 million rows without an index, the server will “scream” again. For geospatial data, a standard B-Tree is useless. You must use a GiST (Generalized Search Tree) index.
CREATE INDEX idx_cafes_location ON cafes USING GIST(location);
This index divides space into small squares (R-Tree). In practice, I once helped a system reduce query time from 8 seconds down to 45ms just by using this command, an optimization comparable to speeding up queries 100x with materialized views.
Hard-Won Lessons from the Field
After years of operating large-scale mapping systems, I’ve gathered three important takeaways:
- Consider performance: Geography is more accurate but more resource-intensive than Geometry. If your app only runs within a single district or a small city, Geometry is a more optimal choice.
- Units of measurement:
ST_DWithinon Geography uses meters. Meanwhile, on Geometry, it uses the units of the SRID (usually degrees). Many developers have encountered “no results found” bugs because of this confusion. - Database cleanup: Tables containing constantly updating driver coordinates will generate many dead tuples. Ensure
Autovacuumis configured aggressively enough to prevent system slowdowns.
Conclusion
PostGIS is the gold standard if you’re building location-based apps. Don’t try to reinvent mathematical formulas in your application code. Push that burden down to the database. Getting used to SRIDs or GiST might be a bit challenging at first, but much like finding and optimizing resource-hungry SQL queries, the stability and speed it provides are absolutely worth it.

