SkillByAIOpen interactive version →

Lesson 16 / 25

GIN Indexes for JSONB, Arrays and Text Search

Indexes for "contains" queries.

Inverted indexes

GIN (generalised inverted) indexes map each element (a JSON key-value, an array item, a word) to the rows containing it, which suits containment queries such as payload @> '{"device": "ios"}', array overlaps, and full-text search. The jsonb_path_ops operator class makes smaller, faster indexes when you only need @>. GIN indexes are slower to update than B-trees, so add them where reads justify it.

Containment before and after a GIN index, run

I ran this with psql against PostgreSQL 16.2 on a fresh database with a 200,000-row events table (created with generate_series and analysed). Without an index, PostgreSQL scans the table in parallel; after creating a GIN index on payload, it uses a bitmap index scan. One in three events has device ios: 66,666 rows. For a condition matching a third of the table, the planner may still prefer a sequential scan in other situations; GIN pays off most for selective conditions.

EXPLAIN (COSTS OFF) SELECT count(*) FROM events WHERE payload @> '{"device": "ios"}';
CREATE INDEX events_payload_idx ON events USING gin (payload jsonb_path_ops);
EXPLAIN (COSTS OFF) SELECT count(*) FROM events WHERE payload @> '{"device": "ios"}';
SELECT count(*) FROM events WHERE payload @> '{"device": "ios"}';

Output:

                             QUERY PLAN                              
---------------------------------------------------------------------
 Finalize Aggregate
   ->  Gather
         Workers Planned: 1
         ->  Partial Aggregate
               ->  Parallel Seq Scan on events
                     Filter: (payload @> '{"device": "ios"}'::jsonb)
(6 rows)

                            QUERY PLAN                             
-------------------------------------------------------------------
 Aggregate
   ->  Bitmap Heap Scan on events
         Recheck Cond: (payload @> '{"device": "ios"}'::jsonb)
         ->  Bitmap Index Scan on events_payload_idx
               Index Cond: (payload @> '{"device": "ios"}'::jsonb)
(5 rows)

 count 
-------
 66666
(1 row)

Index selective conditions

Indexes help most when a condition matches a small fraction of rows; for broad conditions, scanning can be just as fast.

Quick check: Which index type suits jsonb @> containment queries?

  • Hash
  • B-tree on the whole column
  • GIN
  • No index can help
Answer

GIN — Inverted indexes for element lookups.