- What spatial indexes are
- How they work
- How to create and tune spatial indexes in CockroachDB
CockroachDB does not support indexing geospatial types in default , nor in .
What is a spatial index?
At a high level, a spatial index is just like any other . Its purpose is to improve your database’s performance by helping SQL locate data without having to look through every row of a table. Spatial indexes store information about spatial objects, but they are used for the same tasks as any other index type, i.e.,- Fast filtering of lists of shapes based on spatial predicate functions such as .
- Speeding up joins that involve spatial data columns.
- They are specialized to operate on 2-dimensional
GEOMETRYandGEOGRAPHYdata types. They are stored by CockroachDB as a special type of . For specifics, see Details. - If needed, they can be tuned to store looser or tighter coverings of the shapes being indexed, depending on the needs of your application. Tighter coverings are more expensive to generate, store, and update, but can perform better in some cases because they return fewer false positives during the initial index lookup. Tighter coverings can also lead to worse performance due to more rows in the index, and more scans at the storage layer. That’s why we recommend that most users should not need to change the default settings. For more information, see Tuning spatial indexes.
How CockroachDB’s spatial indexing works
There are two main approaches to building geospatial indexes:- One approach is to “divide the objects” by inserting the objects into a tree (usually an R-tree) whose shape depends on the data being indexed. This is the approach taken by PostGIS.
- The other approach is to “divide the space” by creating a decomposition of the space being indexed into buckets of various sizes.
- It’s easy to scale horizontally.
- It requires no balancing operations, unlike R-tree indexes.
- Inserts under this approach require no locking.
- Bulk ingest is simpler to implement than under other approaches.
- It allows advanced users to make a per-object tradeoff between index size and false positives during index creation. (See Tuning spatial indexes.)
Details
Under the hood, CockroachDB uses the S2 geometry library to divide the space being indexed into a quadtree data structure with a set number of levels and a data-independent shape. Each node in the quadtree (really, S2 cell) represents some part of the indexed space and is divided once horizontally and once vertically to produce 4 child cells in the next level. The following image shows visually how a location (marked in red) is represented using levels of a quadtree:


CREATE INDEX that tell CockroachDB how many levels of S2 cells to use. The leaf nodes of the S2 quadtree are at level 30, and for GEOGRAPHY measure 1cm across the Earth’s surface. By default, GEOGRAPHY indexes use up to level 30, and get this level of precision. We also use S2 cell coverings in a slightly different way for GEOMETRY indexes. The precision you get there is the bounding length of the GEOMETRY index divided by 4^30. For more information, see Tuning spatial indexes.
CockroachDB stores spatial indexes as a special type of . The spatial index maps from a location, which is a square cell in the quadtree, to one or more shapes whose include that location. Since a location can be used in the covering for multiple shapes, and each shape can have multiple locations in its covering, there is a many-to-many relationship between locations and shapes.
Tuning spatial indexes
The information in this section is for advanced users doing performance optimization. Most users should not need to change the default settings. Without careful testing, you can get worse performance by changing the default settings.
- Verify the coverings that will be generated meet your accuracy expectations by using the
st_s2coveringfunction as shown in View an object’s S2 covering. - Do extensive performance tests to make sure the indexes that use the non-default coverings actually provide a performance benefit.
Visualize index coverings
In what follows we will visualize how index coverings change as we change thes2_max_cells parameter, which determines how much work CockroachDB will perform to find a tight covering of a shape.
We will generate coverings for the following geometry object, which describes a shape whose vertices land on some small cities in the Northeastern US:
s2_max_cells parameter from the 1 to 30 (minimum to maximum):

s2_max_cells parameter, more work is done by CockroachDB to discover a tighter and tighter covering (that is, a covering using more and smaller cells). The covering for this particular shape reaches a reasonable level of accuracy when s2_max_cells reaches 10, and stops improving much past 12.

Index tuning parameters
The following parameters are supported by bothCREATE INDEX and the st_s2covering. The latter is useful for seeing what kinds of coverings would be generated by an index with various tuning parameters if you were to create it. For an example showing how to use these parameters with st_s2covering, see View an object’s S2 covering.
If a shape falls outside the bounding coordinates determined by the following
geometry_* settings, there will be a performance loss in that such shapes may not be eliminated by the index lookup, but the database will still return the correct answers.| Option | Default value | Description |
|---|---|---|
s2\_level\_mod | 1 | s2\_max\_level must be divisible by s2\_level\_mod. s2\_level\_mod must be between 1 and 3. |
s2\_max\_level | 30 | The maximum level of S2 cell used in the covering. Allowed values: 1-30. Setting it to less than the default means that CockroachDB will be forced to generate coverings using larger cells. |
s2\_max\_cells | 4 | The maximum number of S2 cells used in the covering. Provides a limit on how much work is done exploring the possible coverings. Allowed values: 1-30. You may want to use higher values for odd-shaped regions such as skinny rectangles. |
geometry\_min\_x | Derived from SRID bounds, else -(1 << 31) | Minimum X-value of the for the object(s) being covered. This only needs to be set if the default bounds of the SRID are too large/small for the given data, or SRID = 0 and you wish to use a smaller range (unfortunately this is currently not exposed, but is viewable on https://epsg.io/3857). By default, SRID = 0 assumes [-min int32, max int32] ranges. |
geometry\_max\_x | Derived from SRID bounds, else (1 << 31) -1 | Maximum X-value of the for the object(s) being covered. This only needs to be set if you are using a custom . |
geometry\_min\_y | Derived from SRID bounds, else -(1 << 31) | Minimum Y-value of the for the object(s) being covered. This only needs to be set if you are using a custom . |
geometry\_max\_y | Derived from SRID bounds, else (1 << 31) -1 | Maximum Y-value of the for the object(s) being covered. This only needs to be set if you are using a custom . |
Examples
View an object’s S2 covering
Here is an example showing how to pass the spatial index tuning parameters tost_s2covering. It generates GeoJSON output showing both a shape and the S2 covering that would be generated for that shape in your index, if you passed the same parameters to CREATE INDEX. You can paste this output into geojson.io to see what it looks like.
LINESTRING and its S2 covering based on the options you passed to st_s2covering.

Create a spatial index
The following example shows how to create a spatial index on aGEOMETRY object using the default settings:
Create a spatial index with non-default tuning parameters
The following examples show how to create spatial indexes with non-default settings for some of the spatial index tuning parameters:Create spatial indexes during table definition
This example shows how to with spatial indexes onGEOMETRY and GEOGRAPHY types that use non-default settings for some of the spatial index tuning parameters:
Create a spatial index that uses all of the tuning parameters
This example shows how to set all of the spatial index tuning parameters at the same time. It is extremely unlikely you will ever need to set all of the options at once; this example is being provided to show the syntax.As noted, most users should not change the default settings. There is a risk that you will get worse performance by changing the default settings.
Create a spatial index on a GEOGRAPHY object
The following example shows how to create a spatial index on a GEOGRAPHY object using the default settings:

