Hermes Wiki
Developer/Geospatial/GeospatialIndexing/Fundamentals/r-trees-and-spatial-database-indexing

R-Trees and Spatial Database Indexing

Concept

An R-tree indexes spatial objects (points, lines, polygons) by grouping them into a hierarchy of minimum bounding rectangles (MBRs) — the smallest axis-aligned rectangle that fully contains a given object or a group of child nodes. Each internal node stores the MBR of everything beneath it; each leaf stores the MBR of an actual record plus a pointer to it. A spatial query — "what intersects this box" or "what's near this point" — descends the tree only into branches whose MBR could possibly contain a qualifying result, pruning everything else without touching the underlying rows.

This is the structure PostgreSQL's PostGIS extension builds on: spatial columns (geometry/geography) are indexed with GiST (Generalized Search Tree), a pluggable indexing framework whose spatial operator class implements R-tree semantics — a GiST spatial index behaves like an R-tree for query planning even though GiST itself is a generic tree framework, not an R-tree-specific implementation.

Unlike a geohash (a single flattened string keyable by an ordinary B-tree) or a quadtree (a fixed recursive subdivision of space independent of the data itself), an R-tree's rectangles are built directly from the data's actual bounding boxes and are allowed to overlap. That overlap is the tradeoff at the center of the structure.

Tradeoffs

Aspect R-tree / GiST Geohash Quadtree
What it indexes Any geometry (points, lines, polygons), exact bounds Points only, approximated to a grid cell Points/regions, approximated to a fixed recursive grid
Build cost Higher — bounding-rectangle clustering and node-splitting on insert Low — pure string encoding, no clustering Moderate — recursive subdivision as density grows
Query cost Fast for range/polygon/KNN queries once built Fast prefix/range scan, but boundary effects near cell edges Fast for "what's in this region", weaker for exact-distance KNN
Storage mechanism Native spatial index type (GiST/R-tree) Any ordinary B-tree/KV index on a string column Custom in-memory or application-level tree, rarely a native DB index type
Best fit A real spatial database (PostGIS) with polygons, precise geometry, mixed query types Systems with no spatial index support, sharding-by-region, coarse bucketing In-memory/client-side spatial lookups (e.g. clustering map pins), not the primary server-side index

The overlap in R-tree rectangles is the structure's central cost: a query point that falls inside two sibling MBRs forces the search to descend both subtrees, since either could contain the actual nearest object. Node-splitting algorithms (Quadratic Split, Linear Split, or the R*-tree's topological split) exist specifically to minimize that overlap and the "dead space" — empty area inside an MBR that contains no actual data — when an overflowing node divides; the quality of the splitting heuristic directly affects how much unnecessary subtree descent later queries pay for.

When to use / when not to

  • Use when the data itself is genuinely geometric — polygons (delivery zones, service areas), lines (roads, routes), or a mix of geometry types — not just points. Geohash and quadtree approaches both approximate everything to a grid; an R-tree indexes exact bounds.
  • Use when the query workload is mixed: range queries, polygon containment ("is this point inside this delivery zone"), and nearest-neighbor queries all need to be fast against the same index. PostGIS's GiST-backed R-tree handles all three without a separate structure for each.
  • Prefer a flattened key (geohash — see Geohashing for Proximity Search) instead when the data is overwhelmingly points that move constantly (live driver/user locations) and the system doesn't already have a spatial-index-capable database — a geohash needs nothing but an ordinary indexed string column.
  • Avoid standing up PostGIS/GiST purely for a single "nearby" feature with no polygon or line data and no existing Postgres deployment — that's added operational surface for a query pattern a plain geohash column could already serve.

Common pitfall

Adding a geometry/geography column without actually creating the GIST index on it, then wondering why "nearby" queries get slower as the table grows. Without the index, PostGIS still executes spatial functions correctly — ST_Distance, ST_Contains, and friends all return correct results — but every query falls back to a full sequential scan, computing the function against every row instead of pruning by MBR. This is a commonly cited PostGIS performance mistake precisely because it's silent: a query with no index looks identical to a fast one at small table sizes, and only starts timing out once the table is large enough to expose the missing prune step.

Engineering Lens

The R-tree's overlapping-rectangle design is one instance of a general indexing tradeoff: an index built from the data's real shape (R-tree) is more precise but costlier to build and maintain than one built from a fixed, data-independent partition of the space (geohash, quadtree). That tradeoff reappears any time a system chooses between a content-aware index and a content-agnostic one — content-aware wins on precision and mixed-query flexibility, content-agnostic wins on build simplicity and predictable performance. Choosing between them for a "nearby" feature is really a question about the shape of the underlying data (points vs. polygons) and the query mix, not just raw query volume.

Sources

Hermes Wiki