Specialized Index Structures: Hash, GIN, GiST, SP-GiST, BRIN, Bitmap, and Search

B-tree is the default general-purpose index for many relational workloads, but not every predicate is naturally ordered.

Different index structures are designed for different questions.

The correct starting point is:

Which operator or access pattern must be accelerated?

not:

Which exotic index type sounds fastest?

Index structure follows operator semantics

Examples:

~~~text x = 42 -> equality x BETWEEN 10 AND 20 -> ordered range array contains tag -> membership/containment document contains term -> inverted search point is near polygon -> spatial relationship timestamp on ordered table -> block/range summary ~~~

One index type does not optimize all of these.

B-tree recap

B-trees are good for data with useful ordering.

Typical operations:

~~~text

< <=

= BETWEEN ORDER BY ~~~

They are often right for identifiers, timestamps, numeric ranges, and composite equality/range access.

Hash indexes

Hash indexes map a key to a bucket.

~~~text hash(key) -> bucket -> matching entries ~~~

They are naturally suited to equality.

They do not preserve key order.

Thus:

~~~text x = ? ~~~

fits, while ordered range access does not.

PostgreSQL hash index

PostgreSQL supports hash indexes for equality operators.

~~~sql CREATE INDEX idx_sessions_token_hash ON sessions USING hash(token); ~~~

A B-tree also supports equality and is more versatile.

Choose hash only from measured workload and engine behavior.

Bitmap indexes versus bitmap scans

These are different concepts.

A persistent bitmap index stores bitmaps as an index structure. Some analytical databases support this directly.

PostgreSQL commonly performs bitmap index scans and bitmap heap scans using ordinary indexes.

~~~text index A -> bitmap of row locations index B -> bitmap of row locations | v combine | v visit table pages ~~~

Do not call every PostgreSQL bitmap scan a bitmap index.

Bitmap-style workloads

Low-cardinality analytical filters such as:

~~~text country status segment ~~~

can combine well in read-heavy systems.

Persistent bitmap structures can be expensive under frequent updates depending on the engine.

Inverted indexes

An inverted index maps terms or elements to rows/documents.

~~~text "database" -> documents 1, 5, 9 "index" -> documents 2, 5, 8 ~~~

Useful for:

  • full-text search,
  • array membership,
  • JSON containment,
  • tags.

PostgreSQL GIN

GIN is a generalized inverted index.

It is commonly useful when one indexed item contains many component values.

Examples include:

  • arrays,
  • tsvector full-text search,
  • JSONB containment/operators with suitable operator classes.

Array example:

~~~sql CREATE INDEX idx_articles_tags ON articles USING gin(tags); ~~~

Full-text example:

~~~sql CREATE INDEX idx_articles_search ON articles USING gin(search_vector); ~~~

GIN write trade-off

One row can contribute many postings.

A document with many terms can create many index entries.

Costs can include:

  • larger indexes,
  • more write work,
  • pending-list/maintenance behavior,
  • slower update-heavy workloads.

GiST

GiST is a framework for tree-structured search strategies.

It is commonly used for:

  • geometric/spatial values,
  • range types,
  • nearest-neighbor searches,
  • extension-defined data types.

It is not simply "the spatial index," though spatial workloads are a major use.

Spatial example

With suitable PostgreSQL/PostGIS types and operator classes, GiST can accelerate questions such as:

~~~text which geometries overlap this region? which points are nearest? ~~~

The query operator must match the operator class.

Range types

PostgreSQL range types can use GiST for overlap and containment relationships.

Conceptually:

~~~text reservation period overlaps requested period ~~~

This is different from indexing one scalar timestamp.

SP-GiST

SP-GiST supports space-partitioned search structures.

Depending on operator class, it can represent structures such as:

  • tries,
  • quadtrees,
  • k-d-style partitions.

Use it when the data/query type has a compatible operator class.

GIN versus GiST

Some workloads can use either family.

For PostgreSQL full text, general intuition is:

  • GIN stores detailed postings and often favors search performance,
  • GiST can be smaller/general but may require rechecks depending on operator class.

Use current engine documentation and representative tests instead of memorizing one as universally superior.

BRIN

BRIN means Block Range INdex in PostgreSQL.

Instead of one entry per row, BRIN summarizes ranges of table pages.

~~~text pages 0-127: min timestamp = Jan 1 max timestamp = Jan 3

pages 128-255: min timestamp = Jan 3 max timestamp = Jan 6 ~~~

A query for Jan 5 can skip page ranges whose summaries cannot contain Jan 5.

BRIN and physical correlation

BRIN is especially useful when the indexed value correlates with physical row order.

Examples:

  • append-only timestamp,
  • increasing sequence,
  • naturally clustered measurement time.

If values are randomly distributed across every page range, summaries become broad and less useful.

BRIN size advantage

Because BRIN summarizes page ranges rather than indexing every row, it can be much smaller than a B-tree on a huge correlated table.

Trade-off:

  • it is lossy,
  • candidate page ranges still require checking,
  • it is weaker on uncorrelated data.

Runnable BRIN exercise

This chapter adds:

~~~text scripts/indexing/postgres_brin_demo.sql ~~~

Run:

~~~bash docker exec -i postgres-local \ psql -U demo -d test \ < scripts/indexing/postgres_brin_demo.sql ~~~

The exercise creates a time-ordered event table, builds B-tree and BRIN indexes, reports their sizes, and shows EXPLAIN output for a narrow time window.

The exact plan depends on local statistics and table size.

Full-text indexing

Searching:

~~~sql WHERE body LIKE '%database%' ~~~

cannot generally use a normal B-tree for arbitrary substring search.

Full-text search tokenizes and normalizes text into searchable terms.

~~~text document | tokenize/normalize | term postings | rank/search ~~~

Use the database's full-text feature or a search engine when appropriate.

Trigram indexes

PostgreSQL's pg_trgm extension can support similarity and certain LIKE/ILIKE patterns through GiST or GIN operator classes.

This is different from linguistic full-text search.

JSON indexing

Different JSON operations need different access structures.

Scalar extraction:

~~~text payload country = DE ~~~

may fit an expression B-tree.

Containment/membership may fit GIN.

There is no universal JSON index.

Spatial indexes

Spatial predicates include:

  • intersects,
  • contains,
  • distance,
  • nearest neighbor.

A B-tree over raw latitude and longitude is not equivalent to a true spatial index for arbitrary geometry operations.

Zone maps and data skipping

Analytical systems often maintain min/max or other metadata per block.

~~~text block 1 amount: 0..100 block 2 amount: 100..200 block 3 amount: 10000..20000 ~~~

A query for amount > 5000 can skip blocks 1 and 2.

This resembles BRIN's summary idea but belongs to the analytical engine's implementation.

Bloom filters

A Bloom filter can answer:

~~~text definitely not present or possibly present ~~~

It is a probabilistic membership structure.

Bloom filters are common in LSM/storage systems and some extensions.

They do not support ordering or ranges.

Operator classes matter

In PostgreSQL, index access methods rely on operator classes defining:

  • representation,
  • supported operators,
  • search/comparison behavior.

So "use GIN" is incomplete without the data type and operator class.

Decision table

Query pattern Candidate structure
equality/range/order B-tree
equality only B-tree or engine hash
array/JSON containment GIN/inverted, depending on operator
full-text terms inverted/full-text
geometry/ranges/nearest-neighbor GiST/SP-GiST or spatial index
huge physically correlated table BRIN/data-skipping summary
low-cardinality analytical filters bitmap-style indexing where supported

Always validate against the specific engine.

Index versus external system

A database index is not always the right answer.

Advanced search may need:

  • language analyzers,
  • typo tolerance,
  • relevance ranking,
  • autocomplete,
  • distributed search.

An engine such as OpenSearch/Elasticsearch can be more suitable.

Large analytics may benefit more from columnar storage and data skipping than from many OLTP indexes.

Common mistakes

  • Assuming hash is automatically faster than B-tree for equality.
  • Assuming low cardinality automatically means bitmap index.
  • Expecting one GIN index to optimize every JSON query.
  • Treating BRIN as a tiny B-tree.
  • Treating latitude/longitude B-trees as full spatial indexing.