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
| AM | Unique | Multi-column | Ordered output | Index-only scans | Typical operators |
|---|---|---|---|---|---|
| btree | yes | yes | yes | yes | = < <= > >= BETWEEN IN, LIKE 'abc%' (C collation or pattern ops) |
| hash | no | no | no | no | = |
| gin | no | yes | no | no | @> <@ && ? ?| ?& @@, trigram % / LIKE '%x%' |
| gist | no | yes | distance <-> | some opclasses | && @> <@ << >> ~=, exclusion constraints |
| spgist | no | no | distance <-> | some opclasses | points, ranges, inet, text prefixes ^@ |
| brin | no | yes | no | no | = < > (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 exceedsgin_pending_list_limit(4MB). A big pending list makes searches slow and occasional inserts very slow.jsonb_path_opsis smaller than the defaultjsonb_opsbut 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 gistconstraints 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). Checkpg_stats.correlation. New ranges are summarised by VACUUM or withautosummarize = 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()innbtinsert.c. - Hash:
_hash_doinsert()inhashinsert.c,hashgettuple(). - GIN:
ginHeapTupleFastInsert()appends to the pending list andginInsertCleanup()merges it (both inginfast.c);gingetbitmap()inginget.c. - GiST:
gistdoinsert(), andgistgettuple()/gistgetbitmap()ingistget.c. - SP-GiST:
spgdoinsert()inspgdoinsert.c,spggettuple()inspgscan.c. - BRIN:
brininsert(),bringetbitmap(),brin_summarize_new_values(); withautosummarize,brininsert()queuesAutoVacuumRequestWork(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.sqlinsrc/test/regress/sql/. - Writing your own AM:
contrib/bloom(blhandler()) is a complete, WAL-logged example, andsrc/test/modules/dummy_index_amis a minimal skeleton. Register it withCREATE 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