← All scenarios

Scenario · Query Performance

GIN index misuse on JSONB

A sandboxed PostgreSQL incident — investigate with your own tools, submit a fix, and get deterministic Detect / Fix / Trap scoring.

L3 · 10–15 min · runs locally in Docker

Launch

Start this scenario

Boot it in a real PostgreSQL sandbox and investigate with psql, EXPLAIN and pg_stat_statements.

ride postgres start stage-01/07-gin-index-misuse

Part of these paths

Show the postmortem & investigation hints spoilers
GIN index misuse on JSONB
Type: incident simulation · Topic: Query Performance · Level: L3 · Duration: 10–15 min
Launch: ride postgres start stage-01/07-gin-index-misuse

POSTMORTEM (root cause · how it was found · the fix · lesson)
Root cause: the query filters a JSONB column with @> (containment), but there
was no index that supports that operator. A plain B-tree on a jsonb column is
useless for @>, so the planner did a full Seq Scan on every search.

How it was found: pg_stat_statements surfaced the slow query; EXPLAIN showed a
Seq Scan with a Filter on the @> predicate.

The fix: CREATE INDEX idx_events_payload_gin
ON events USING gin (payload jsonb_path_ops);
GIN indexes are built for containment/existence operators on JSONB. jsonb_path_ops
is a compact opclass specialised for @>; the plan switches to a Bitmap Index Scan.

Lesson: an index only helps if it matches the operator and query shape. "Add a
GIN index" isn't enough — pick the right access method (GIN for JSONB @>) and
the right operator class. A B-tree on JSONB looks like a fix but the planner
ignores it.

INVESTIGATION HINTS (the staged path to diagnose and fix)
1. The slow query filters JSONB with the containment operator @>. A normal B-tree index on a jsonb column can't serve @> — EXPLAIN shows a Seq Scan.
2. JSONB containment needs a GIN index. The operator class matters: jsonb_path_ops is compact and optimised for @>.
3. CREATE INDEX ... ON events USING gin (payload jsonb_path_ops); then EXPLAIN shows a Bitmap Index Scan over the GIN index.