DB Internals AtlasPostgreSQL 18.6 Home About

Storage

Index types

PostgreSQL ships six index access methods. B-tree covers most needs; the others exist for data that is not a simple sorted list: arrays and documents, geometry and ranges, or huge tables in natural order.

In plain words

An index is a side structure that lets PostgreSQL find rows without reading the whole table. Different data needs different shapes of index, so there are several kinds. Choose one in the selector to see how it finds data.

  • B-tree: the default. Sorted values; equality, ranges, sorting.
  • Hash: equality only, by hashing the value.
  • GIN: "which rows contain this word / element / key?" for arrays, JSON and full-text search.
  • GiST: nested boxes for shapes, ranges and "nearest" searches.
  • SP-GiST: space split into non-overlapping parts (quadtrees, prefix trees).
  • BRIN: a tiny summary (min/max) per block range, for huge tables whose order follows the column.

If you just write CREATE INDEX you get a B-tree. The others need USING gin, USING brin and so on.

For DBAs and operators

Capabilities

AMUniqueMulti-columnOrdered outputIndex-only scansTypical operators
btreeyesyesyesyes= < <= > >= BETWEEN IN, LIKE 'abc%' (C collation or pattern ops)
hashnononono=
ginnoyesnono@> <@ && ? ?| ?& @@, trigram % / LIKE '%x%'
gistnoyesdistance <->some opclasses&& @> <@ << >> ~=, exclusion constraints
spgistnonodistance <->some opclassespoints, ranges, inet, text prefixes ^@
brinnoyesnono= < > (minmax), && (inclusion), bloom
SELECT a.amname, count(*) FROM pg_class c JOIN pg_am a ON a.oid = c.relam
 WHERE c.relkind = 'i' GROUP BY 1;

SELECT amname, pg_indexam_has_property(oid, 'can_unique') FROM pg_am WHERE amtype = 'i';

Notes per type

  • Hash has been WAL-logged and crash-safe since PostgreSQL 10. It stores only the 32-bit hash code, so it can be smaller than a B-tree on long keys, but B-tree is usually just as fast.
  • GIN keeps an inverted index: key → posting list/tree of TIDs. With fastupdate (on by default) new entries go to a pending list first, merged by VACUUM, autoanalyze, gin_clean_pending_list() or when it exceeds gin_pending_list_limit (4MB). A big pending list makes searches slow and occasional inserts very slow. jsonb_path_ops is smaller than the default jsonb_ops but supports fewer operators (@> @? @@, not the key-exists operators ? ?| ?&). Parallel GIN builds arrived in PostgreSQL 18.
  • GiST is a framework for balanced trees of "predicates" (often bounding boxes). Boxes may overlap, so a search may descend several branches. It powers PostGIS, range types, EXCLUDE USING gist constraints and KNN ordering (ORDER BY geom <-> point LIMIT 10).
  • SP-GiST partitions space without overlap (quadtree, k-d tree, radix tree), so each search follows one path. Good for points, IP ranges and text with common prefixes.
  • BRIN stores one summary per pages_per_range (128) heap pages. Tiny and cheap to maintain, but only useful when the column correlates with physical order (append-only timestamps, sequences). Check pg_stats.correlation. New ranges are summarised by VACUUM or with autosummarize = on. Parallel BRIN builds exist since PostgreSQL 17.

More indexes means slower writes and fewer HOT updates. Check pg_stat_user_indexes.idx_scan = 0 regularly and drop indexes nobody uses (but keep ones that enforce constraints).

For developers: inside the source

Where it lives

Each access method (AM) is one directory under src/backend/access/: nbtree, hash, gin, gist, spgist and brin. Every one of them has a README describing its page layout, locking and WAL rules; read those first. The AM-independent glue is amapi.h (the API struct) and indexam.c (index_insert(), index_beginscan(), index_getnext_tid(), index_getbitmap(), index_bulk_delete()). The SGML chapter indexam.sgml documents the interface.

The handler and IndexAmRoutine

pg_am.amhandler names a C function that returns a filled-in IndexAmRoutine: bthandler() (nbtree.c), hashhandler() (hash.c), ginhandler() (ginutil.c), gisthandler() (gist.c), spghandler() (spgutils.c) and brinhandler() (brin.c). GetIndexAmRoutine() calls it and the relcache keeps the result in rd_indam. The capability table above is literally these boolean fields (amcanorder, amcanorderbyop, amcanunique, amcanmulticol, amcanbuildparallel, amsummarizing …) plus callbacks such as ambuild, aminsert, ambulkdelete, amcostestimate, amgettuple and amgetbitmap. GIN and BRIN leave amgettuple NULL, which is why they only ever appear under a Bitmap Index Scan:

/* ginhandler(), ginutil.c */
amroutine->amcanbuildparallel = true;
amroutine->amgettuple = NULL;
amroutine->amgetbitmap = gingetbitmap;

Per-AM entry points worth reading

  • B-tree: _bt_doinsert() in nbtinsert.c.
  • Hash: _hash_doinsert() in hashinsert.c, hashgettuple().
  • GIN: ginHeapTupleFastInsert() appends to the pending list and ginInsertCleanup() merges it (both in ginfast.c); gingetbitmap() in ginget.c.
  • GiST: gistdoinsert(), and gistgettuple() / gistgetbitmap() in gistget.c.
  • SP-GiST: spgdoinsert() in spgdoinsert.c, spggettuple() in spgscan.c.
  • BRIN: brininsert(), bringetbitmap(), brin_summarize_new_values(); with autosummarize, brininsert() queues AutoVacuumRequestWork(AVW_BRINSummarizeRange, …) for autovacuum.

On the planner side, get_relation_info() (plancat.c) copies the AM flags into IndexOptInfo, and the per-AM cost functions (btcostestimate(), gincostestimate(), brincostestimate() …) live in selfuncs.c.

Observing and experimenting

  • pageinspect has page decoders for every AM: bt_page_items(), hash_page_items(), gin_leafpage_items(), gist_page_items(), brin_page_items().
  • amcheck: bt_index_check(), bt_index_parent_check() and, new in 18, gin_index_check() (verify_gin.c).
  • Regression tests: create_index.sql, gin.sql, gist.sql, spgist.sql, brin.sql, hash_index.sql, amutils.sql in src/test/regress/sql/.
  • Writing your own AM: contrib/bloom (blhandler()) is a complete, WAL-logged example, and src/test/modules/dummy_index_am is a minimal skeleton. Register it with CREATE ACCESS METHOD … TYPE INDEX HANDLER ….

Parameters that shape this

enable_indexscan enable_bitmapscan enable_indexonlyscan gin_pending_list_limit gin_fuzzy_search_limit maintenance_work_mem

Related topics

B-tree indexes Scan methods Page layout Planner cost model VACUUM Free space map and visibility map