PostgreSQL Performance & Query Optimization · 강의

JSONB에서 GIN 인덱스와 표현식 인덱스 비교

쿼리 형태에 맞춰 jsonb_path_ops GIN 인덱스와 대상 지정 표현식 인덱스 중 적절한 것을 선택하는 방법을 배웁니다.

레슨 2/413개 단계

JSONB에서 GIN 인덱스와 표현식 인덱스 비교은(는) CoddyKit의 무료 PostgreSQL Performance & Query Optimization 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 PostgreSQL Performance & Query Optimization 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. PostgreSQL Performance & Query Optimization 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Two Ways to Index JSONB

When you store data in a jsonb column, an unindexed query forces PostgreSQL to read and parse every row. There are two very different tools to fix this:

  • GIN index — a general inverted index over the whole document, great for flexible containment and key/value lookups.
  • Expression (B-tree) index — a targeted index on one extracted scalar, great for a specific known query shape.

This lesson is about choosing the right one for your query patterns.

The Sample Table

Imagine an events table where each row carries a flexible JSON payload. We will index its data column.

Notice the payload mixes a few common keys (type, user_id) with arbitrary extras.

CREATE TABLE events (
  id     bigserial PRIMARY KEY,
  data   jsonb NOT NULL
);

INSERT INTO events (data) VALUES
  ('{"type": "login",  "user_id": 42, "ip": "10.0.0.1"}'),
  ('{"type": "logout", "user_id": 42}'),
  ('{"type": "login",  "user_id": 99, "mfa": true}');

The Default GIN: jsonb_ops

A plain GIN index uses the default jsonb_ops operator class. It indexes every key AND every value as separate entries.

This supports the widest set of operators: containment @>, key existence ?, ?|, and ?&.

The cost: it is larger on disk and slower to build/update because it stores far more entries per row.

CREATE INDEX idx_events_data_gin
  ON events USING gin (data);

-- Supports key existence AND containment:
-- WHERE data ? 'mfa'
-- WHERE data @> '{"type":"login"}'

The Leaner GIN: jsonb_path_ops

If you only ever use the containment operator @> (and the JSONPath operators @? / @@), prefer the jsonb_path_ops operator class.

  • It hashes whole key→value paths into single entries.
  • Result: noticeably smaller index and faster containment lookups.
  • Trade-off: it does NOT support the key-existence operators ?, ?|, ?&.
CREATE INDEX idx_events_data_pathops
  ON events USING gin (data jsonb_path_ops);

-- Great for:
SELECT id FROM events
WHERE data @> '{"type": "login"}';

How Containment Uses the GIN Index

The @> operator asks "does the left document contain the right one?" Both GIN operator classes accelerate it.

Containment is structural: it matches nested keys and values, not just top-level ones. This is why a single GIN index can serve many different filter combinations.

-- Match by one key:
SELECT * FROM events WHERE data @> '{"user_id": 42}';

-- Match by two keys at once (same index):
SELECT * FROM events
WHERE data @> '{"type": "login", "user_id": 42}';

-- Match a nested shape:
SELECT * FROM events WHERE data @> '{"flags": {"beta": true}}';

When GIN Falls Short: Range & Sort

GIN is built for equality-style containment. It canNOT help with:

  • Range comparisons on an extracted value (>, <, BETWEEN).
  • Ordering by a JSON field (ORDER BY ... LIMIT).
  • Prefix / pattern matching on a text value.

For these shapes you want a B-tree, and on JSONB that means an expression index.

-- GIN can't accelerate this range filter on an inner number:
SELECT * FROM events
WHERE (data ->> 'user_id')::int > 50
ORDER BY (data ->> 'user_id')::int
LIMIT 10;

Building an Expression Index

An expression index stores the result of an expression, not the raw column. You extract one scalar from the JSON and index that as a normal B-tree.

Two operators matter here:

  • -> returns jsonb.
  • ->> returns text — usually what you cast and index.
-- B-tree on user_id extracted as an integer:
CREATE INDEX idx_events_user_id
  ON events (((data ->> 'user_id')::int));

-- Now ranges, sorts and equality all use it:
SELECT * FROM events
WHERE (data ->> 'user_id')::int BETWEEN 40 AND 99
ORDER BY (data ->> 'user_id')::int;

Match the Index Expression Exactly

The planner only uses an expression index when the query expression matches the indexed expression token for token, including the cast.

If you index (data ->> 'user_id')::int but query (data ->> 'user_id') as plain text, the index is ignored.

Keep the extraction + cast identical everywhere.

-- Indexed expression:
--   ((data ->> 'user_id')::int)

-- USES the index:
WHERE (data ->> 'user_id')::int = 42

-- IGNORES the index (text vs int mismatch):
WHERE (data ->> 'user_id') = '42'

Reading EXPLAIN to Confirm

Never guess which index wins — ask the planner. Use EXPLAIN (ANALYZE, BUFFERS) and look at the node type:

  • Bitmap Heap Scan + Bitmap Index Scan on ...gin → your GIN index is serving containment.
  • Index Scan / Index Only Scan on the expression index → your B-tree is serving the range/sort.
  • Seq Scan → nothing matched; revisit the expression or operator.
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM events
WHERE data @> '{"type": "login"}';

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM events
WHERE (data ->> 'user_id')::int = 42;

Partial Expression Indexes

If queries only ever target a subset of rows, add a WHERE clause to the index. A partial expression index is smaller and cheaper to maintain because it only stores the rows you actually search.

Here we index user_id only for login events — perfect when that is the only query shape that needs it.

CREATE INDEX idx_events_login_user
  ON events (((data ->> 'user_id')::int))
  WHERE data @> '{"type": "login"}';

Choosing: A Quick Decision Guide

Pick by the shape of your queries, not by habit:

  • Flexible filters on many different keys, or key-existence (?) → GIN jsonb_ops.
  • Only containment @> / JSONPath, want it lean and fast → GIN jsonb_path_ops.
  • One known field with ranges, sorting, or equality on a scalar → expression B-tree index.
  • That field queried on a narrow slice of rows → partial expression index.

It is common and correct to keep BOTH a GIN and one or two expression indexes on the same column.

Quick Check

Test your understanding of the GIN vs expression decision.

Recap

You learned to choose JSONB indexes by query shape:

  • GIN jsonb_ops — widest operator support including key existence ?; largest.
  • GIN jsonb_path_ops — leaner and faster, containment @> and JSONPath only.
  • Expression B-tree — one extracted, casted scalar for ranges, sorts, and equality; the query expression must match the index expression exactly.
  • Partial expression index — same idea, scoped to a row subset for a smaller footprint.

Always confirm with EXPLAIN (ANALYZE, BUFFERS), and don't hesitate to keep a GIN and one or two expression indexes side by side.

무료로 시작

AI 튜터와 함께 SQL을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
22
레슨
88

자주 묻는 질문

“JSONB에서 GIN 인덱스와 표현식 인덱스 비교” 강의는 무료인가요?

네 — “JSONB에서 GIN 인덱스와 표현식 인덱스 비교” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 PostgreSQL Performance & Query Optimization 강의 전체를 잠금 해제할 수 있습니다. PostgreSQL Performance & Query Optimization 강의에는 총 4개의 강의가 포함되어 있습니다.

“JSONB에서 GIN 인덱스와 표현식 인덱스 비교”에서 뭘 배우나요?

쿼리 형태에 맞춰 jsonb_path_ops GIN 인덱스와 대상 지정 표현식 인덱스 중 적절한 것을 선택하는 방법을 배웁니다. 브라우저에서 직접 실행하는 실습 코드로 PostgreSQL Performance & Query Optimization을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

PostgreSQL Performance & Query Optimization을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 PostgreSQL Performance & Query Optimization은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“JSONB에서 GIN 인덱스와 표현식 인덱스 비교” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 PostgreSQL Performance & Query Optimization 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 PostgreSQL Performance & Query Optimization 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. JSONB 연산자와 포함 쿼리
  2. JSONB에서 GIN 인덱스와 표현식 인덱스 비교
  3. JSONPath로 JSONB 조회하기
  4. JSONB에서 정규화할 시점
← PostgreSQL Performance & Query Optimization(으)로 돌아가기