DEV Community

Cover image for PostGIS for People Who Thought They Needed a Search Cluster
Amit chakraborty
Amit chakraborty

Posted on Originally published at amitchakraborty.dev

PostGIS for People Who Thought They Needed a Search Cluster

The instinct to reach for a dedicated search cluster like Elasticsearch or Algolia when a product requires location-based features is understandable. In my eight years of software engineering, I have seen teams reflexively add a second datastore the moment a stakeholder asks for "doctors near me" or "real-time delivery tracking." The logic is usually that relational databases are for structured rows, and spatial search is a specialized workload that requires a specialized engine.

However, introducing a second datastore creates a synchronization tax. You now have to manage CDC (Change Data Capture) pipelines, handle eventual consistency issues, and double your infrastructure monitoring. At Synapsis Medical Technologies, where I was the founding engineer, I owned the architecture from 0 to 1 across React Native, Next.js, and NestJS. When building HIPAA-aligned systems, every additional piece of infrastructure increases the surface area for compliance audits and potential failure points.

For the majority of applications—even those scaling rapidly—PostGIS provides the spatial indexing and query performance necessary to keep your stack simple. You can achieve sub-millisecond lookups on millions of points without leaving PostgreSQL.

The Cost of the "Search Cluster" Reflex

When you move spatial data to a search cluster, you are not just moving data; you are moving logic. A simple query like "find all available clinicians within 10 miles who are also specialized in cardiology" becomes a distributed systems problem.

If your primary record of truth is in Postgres, you must ensure that when a clinician updates their location or status, that change is reflected in the search index. If the sync lag is even a few seconds, you risk showing stale data. Furthermore, you lose the ability to perform complex relational joins. In a HIPAA-aligned environment, where I managed RAG pipelines at 99.9% uptime, data integrity is paramount. Splitting your state between two systems makes maintaining that integrity significantly harder.

PostGIS allows you to treat geography as a first-class data type. It extends PostgreSQL with spatial types like GEOMETRY and GEOGRAPHY, and more importantly, it introduces the R-Tree spatial index via GIST (Generalized Search Tree).

Indexing Beyond B-Trees

To understand why PostGIS is sufficient, you have to understand how it handles indexing differently than a standard B-Tree. A standard database index is one-dimensional; it sorts data in a linear fashion. This works for IDs, dates, or names, but it fails for spatial data because latitude and longitude are two-dimensional. You cannot easily sort points on a map in a single line without losing their spatial relationship.

PostGIS uses GIST indexes to implement R-Trees (Rectangle Trees). Instead of sorting values, an R-Tree groups objects into increasingly larger bounding boxes.

When you query for a point, the engine doesn't scan every row. It checks which top-level bounding boxes contain your coordinates and drills down. This reduces the search space from millions of rows to a handful of index pages in logarithmic time. In my experience shipping 18+ production applications across mobile and web, the bottleneck is rarely the index lookup speed; it is almost always the overhead of moving data between disparate services. By keeping the spatial logic in the database, you eliminate that network hop.

Architecture and Trade-offs: Geometry vs. Geography

One of the most critical architectural decisions when setting up PostGIS is choosing between the GEOMETRY and GEOGRAPHY types.

GEOMETRY treats the world as a flat Cartesian plane. Calculations are fast because they use simple Euclidean math. However, the earth is an oblate spheroid. If you use GEOMETRY to calculate the distance between two points in New York and London, the error margin will be significant because it doesn't account for the curvature of the earth.

GEOGRAPHY uses geodetic coordinates. It accounts for the earth's curvature, providing high accuracy over long distances. The trade-off is computational cost. Spherical math is significantly more expensive than planar math.

In my work building full-stack architectures, I generally follow this rule: if the application is local (e.g., a city-based delivery app or a regional clinic locator), use GEOMETRY with a local Spatial Reference System Identifier (SRID). If the application is global or requires high-precision distance calculations across continents, use GEOGRAPHY.

A Worked Example: The "Clinician Near Me" Query

In the HealthTech AI platforms I’ve built, we often need to filter users by proximity while simultaneously checking complex relational attributes like insurance compatibility or current availability.

Here is how you would implement a performant proximity search using a GIST index.

First, define the table with a spatial column and a GIST index:

CREATE EXTENSION postgis;

CREATE TABLE clinicians (
    id UUID PRIMARY KEY,
    name TEXT,
    specialty TEXT,
    location GEOGRAPHY(POINT, 4326)
);

CREATE INDEX idx_clinicians_location ON clinicians USING GIST (location);
Enter fullscreen mode Exit fullscreen mode

The 4326 SRID refers to the WGS 84 standard used by GPS. To find clinicians within a 5,000-meter radius of a user, the query is straightforward:

SELECT name, specialty
FROM clinicians
WHERE ST_DWithin(
    location, 
    ST_SetSRID(ST_MakePoint(-73.935242, 40.730610), 4326)::geography, 
    5000
);
Enter fullscreen mode Exit fullscreen mode

The ST_DWithin function is the key to performance here. Unlike ST_Distance, which calculates the exact distance for every row and then filters (a full table scan), ST_DWithin utilizes the GIST index. It first identifies all points whose bounding boxes intersect with the search radius and only performs the expensive distance calculation on that subset.

What it Cost to Learn: The Pitfalls of Scale

When I scaled the engineering team at Synapsis from 0 to 21 engineers in 13 months, one of the biggest challenges was maintaining velocity while the complexity of our data grew. I overhauled our CI/CD across five production systems, cutting release cycles from two days to four hours. A major part of that efficiency came from reducing the number of "moving parts" in our infrastructure.

However, PostGIS is not a silver bullet. There are two specific failure modes I have encountered:

  1. The "Kitchen Sink" Index: Creating a GIST index is more expensive than a B-Tree index. If you are bulk-loading millions of rows, the index creation will be the bottleneck. Always drop indexes before a massive data migration and rebuild them afterward.
  2. Functional Casting: A common mistake is performing a transformation on the indexed column within the WHERE clause. For example, WHERE ST_AsText(location) = '...'. This invalidates the index, forcing a sequential scan.

In our RAG and clinical AI pipelines, where we maintained 99.9% uptime, we learned that the database is almost always more resilient than the custom synchronization code written to update a search cluster. When your search cluster falls behind, your application logic starts making decisions based on old data. In healthcare, that isn't just a bug; it's a safety risk.

Practical Recommendations

If you are currently considering a search cluster for spatial needs, I suggest the following checklist before adding that complexity to your stack:

  • Benchmark PostGIS first: Unless you are dealing with hundreds of millions of points and require full-text fuzzy matching combined with spatial filtering, PostGIS will likely outperform your expectations.
  • Use KNN for "Nearest" searches: If you need to find the "10 closest" items rather than everything within a radius, use the <-> operator. This performs a K-Nearest Neighbor search using the index, which is significantly faster than sorting by ST_Distance.
  • Keep your SRIDs consistent: Mixing SRIDs (e.g., trying to compare a 4326 point with a 3857 geometry) is the most common source of "empty result set" bugs. Standardize on 4326 for storage.
  • Leverage JSONB: In my NestJS and Next.js architectures, I often combine PostGIS with Postgres’s JSONB capabilities. This allows you to store flexible spatial metadata without the rigid schema of a traditional GIS system, giving you the flexibility of a document store like MongoDB with the power of R-Tree indexing.

Conclusion

The goal of a Founding Engineer or a Systems Architect is not to build the most complex system possible, but to build the most robust system that meets the requirements. Adding a search cluster like Elasticsearch for spatial queries is often a premature optimization that introduces significant operational overhead.

By leveraging the GIST indexing and spatial functions inherent in PostGIS, you can build high-performance, location-aware applications while keeping your data architecture unified. In my experience shipping across mobile and web platforms, the simplest architecture—when tuned correctly—is the one that scales the most reliably.


Amit Chakraborty is a founding engineer and senior architect — React Native, AI/RAG systems and production architecture. Portfolio: www.amitchakraborty.dev · LinkedIn · GitHub. Open to senior and founding engineering roles, remote worldwide.

Top comments (0)