PostgreSQL JSONB Query Optimization with GIN Index
Problem
Stored user settings in a JSONB column, but querying by specific keys triggers a full table scan.
-- metadata column is JSONB
SELECT * FROM users
WHERE metadata->>'role' = 'admin';
-- Seq Scan... slow
Solution
A GIN index enables indexed lookups inside JSONB data.
-- Create GIN index
CREATE INDEX idx_users_metadata ON users USING GIN (metadata);
-- Use @> containment operator (uses GIN index)
SELECT * FROM users
WHERE metadata @> '{"role": "admin"}';
If you only query specific keys, an expression index is more efficient.
-- B-tree index on a specific path
CREATE INDEX idx_users_role ON users ((metadata->>'role'));
-- This query now uses the index
SELECT * FROM users
WHERE metadata->>'role' = 'admin';
Nested JSON is accessible via path operators.
-- Access nested keys
SELECT * FROM users
WHERE metadata #>> '{address,city}' = 'Seoul';
-- jsonpath also works (PostgreSQL 12+)
SELECT * FROM users
WHERE metadata @? '$.tags[*] ? (@ == "vip")';
Key Points
- Use the
@>containment operator to leverage GIN indexes. The->>operator requires an expression index instead - GIN indexes slow down writes — best suited for read-heavy tables
- Always use JSONB over JSON. JSONB stores data in binary format, making searches significantly faster