Inverted indexes (GIN)
One entry per word, element or key, each listing the rows that contain it: for searching inside arrays, JSON documents and text.
How they work
An inverted index turns the table around: instead of row to values, it stores value to rows. PostgreSQL’s GIN (generalized inverted index) pulls keys out of each composite value (array elements, JSON keys and values, text-search lexemes or, with pg_trgm, three-letter pieces of a string) and stores each key once with a posting list of row IDs, or a posting tree when the list grows large. That serves “contains” questions: tags @> ARRAY['red'], attrs @> '{"color": "red"}', tsv @@ query, and name ILIKE '%c4ca42%' through trigrams. On 1 million rows, a jsonb containment query took 18.7 ms with GIN against 162 ms for a sequential scan, and a substring search 0.9 ms against 669 ms.
The price is paid on writes and disk. One row can add many entries, one per key, so by default (fastupdate) GIN puts new entries in an unsorted pending list and merges them in bulk when vacuum runs or the list passes gin_pending_list_limit. Searches must scan that list too, and the insert that overflows it pays for the merge. GIN can’t do index-only scans, because entries hold only parts of values. The trigram index in the example took 114 MB for a 135 MB table; for jsonb, jsonb_path_ops is usually much smaller than the default jsonb_ops but supports fewer operators.
Search engines rest on the same structure. The Lucene library keeps for each term the documents that contain it, with frequencies and positions for ranking; the search concept covers analyzers, ranking and when a separate engine beats an index inside the database.
Technologies
What all database indexes have in common →| Also inverted indexes (gin) | Open source | PostgreSQL Global Development Group | PostgreSQL License | 2026-10-06 |
Sources
- PostgreSQL docs: GIN indexes (key and posting list pairs, posting trees, pending list)
- PostgreSQL docs: Index types (B-tree, hash, GiST, SP-GiST, GIN, BRIN and the operators each serves)
- PostgreSQL docs: JSON types, jsonb indexing (jsonb_ops and jsonb_path_ops)
- PostgreSQL docs: pg_trgm (gin_trgm_ops and gist_trgm_ops)
- PostgreSQL docs: CREATE INDEX (CONCURRENTLY, pages_per_range default 128, autosummarize, fastupdate)
- PostgreSQL docs: Index-only scans and covering indexes (secondary indexes, visibility map, INCLUDE)
- Apache Lucene 10 docs: index file formats (inverted index, term frequencies and positions)