백엔드 데이터 인프라 35편 — 인덱스 종류 6가지 B-Tree·Hash·GIN·GiST·BRIN·SP-GiST

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 35편. PG 인덱스 6가지 종류 — B-Tree·Hash·GIN·GiST·BRIN·SP-GiST의 의미와 언제 무엇 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 35편 — 인덱스 종류 6가지 B-Tree·Hash·GIN·GiST·BRIN·SP-GiST

이 글은 백엔드 데이터 인프라 시리즈 70편 중 35편이에요. 34편 인덱스 소개"왜·언제" 였다면, 이번 35편은 "PG가 가진 6가지 인덱스 종류" — 각자 다른 상황에 정답.

PG 인덱스 6종 — 한눈에

인덱스 용도
B-Tree (기본) 정렬 가능한 모든 타입 =·<·>·BETWEEN·ORDER BY
Hash 정확 비교만 = (B-Tree 가 거의 항상 우수)
GIN 다중 값 (배열·JSONB·전문 검색) @>·?·tsvector
GiST 기하·범위·전문 검색 &&·<<·@>
BRIN 대용량 정렬 데이터 created_at 시계열
SP-GiST 공간 분할 트리 텍스트·접두사

거의 99% = B-Tree + GIN. 나머지는 특수 상황.

(1) B-Tree — 기본 표준

CREATE INDEX idx ON users(email);
-- 또는 명시
CREATE INDEX idx ON users USING BTREE (email);

지원 연산자:

WHERE col = ?
WHERE col < ? / > ? / <= ? / >= ?
WHERE col BETWEEN ? AND ?
WHERE col IS NULL / IS NOT NULL
WHERE col LIKE 'prefix%'     -- 앞 와일드카드 없을 때
ORDER BY col

복합 인덱스도 B-Tree.

CREATE INDEX idx ON orders(user_id, status, created_at DESC);

정렬 옵션

CREATE INDEX idx ON orders(created_at DESC NULLS LAST);

ORDER BY 와 같은 방향이면 — 정렬 비용 0.

복합 인덱스 순서 — 매우 중요

복합 인덱스 (a, b, c) 의 활용 규칙:

-- ✅ 활용 (왼쪽 prefix)
WHERE a = ?
WHERE a = ? AND b = ?
WHERE a = ? AND b = ? AND c = ?
WHERE a = ? AND b > ?    -- a 정확 + b 범위

-- ⚠️ 부분 활용
WHERE a = ? AND c = ?    -- a 만 인덱스 활용

-- ❌ 비활용
WHERE b = ?              -- a 빠짐
WHERE c = ?              -- a, b 빠짐

: "가장 자주 사용·정확 비교" 컬럼을 앞쪽.

인덱스 컬럼 순서 결정 — 3가지 기준

  1. 선별성(Selectivity) — 값 분포 다양한 컬럼 앞
  2. 정확 비교(=) 자주 나오는 컬럼
  3. 범위(<·>) 컬럼은 뒤

예 — 주문 검색:

-- WHERE user_id = 1 AND status = 'PAID' AND created_at >= '2026-01-01'
CREATE INDEX idx ON orders(user_id, status, created_at);

user_id 정확 (선별성 높음) → status 정확 → created_at 범위. 표준 순서.

(2) Hash 인덱스

CREATE INDEX idx ON users USING HASH (email);

= 만 지원. B-Tree 도 = 빠른데 — Hash 가 "이론적으로 약간 더 빠를 수 있지만" 실무 차이 거의 없음. 거의 안 씀.

PG 10+ 에서 WAL 지원 — 옛 "Hash 인덱스는 복제 안 됨" 문제 해결. 다만 여전히 B-Tree 우세.

(3) GIN — 다중 값의 강자

-- 배열
CREATE INDEX idx ON posts USING GIN (tags);

-- JSONB
CREATE INDEX idx ON events USING GIN (payload);
CREATE INDEX idx ON events USING GIN (payload jsonb_path_ops);

-- 전문 검색 (tsvector)
CREATE INDEX idx ON articles USING GIN (to_tsvector('english', body));

지원 연산자: - 배열 = @>·<@·&&·= ANY - JSONB = @>·?·?|·?&·jsonb_path_ops - 전문 검색 = @@

GIN 의 함정 — 쓰기 비용

GIN 인덱스 = "읽기 빠름, 쓰기 느림". 배열·JSON 의 모든 요소를 인덱스에 박음. 한 행 INSERT 가 N개 인덱스 항목 생성.

대용량 쓰기 시 — fastupdate = on (기본) 이 "임시 보관소(pending list)" 에 모았다가 일괄 처리. 빠른 INSERT.

(4) GiST — 기하·범위·다양

-- 기하
CREATE INDEX idx ON locations USING GiST (point);
-- 범위
CREATE INDEX idx ON bookings USING GIST (period);
-- EXCLUDE 제약과 짝
CREATE TABLE bookings (
    period TSTZRANGE,
    EXCLUDE USING GIST (period WITH &&)
);

23편 EXCLUDE 의 핵심.

GIN vs GiST: - GIN = 다중 값에 강 (배열·JSONB) - GiST = 범위·기하·"커스텀 데이터 구조"

btree_gist 확장

CREATE EXTENSION btree_gist;

EXCLUDE USING GIST (room_id WITH =, period WITH &&)

GiST 가 = 연산자도 지원하도록.

(5) BRIN — 대용량 시계열

CREATE INDEX idx ON logs USING BRIN (created_at);

Block Range Index = 페이지 범위별 "최소·최대" 만 기록. B-Tree 의 1/100 크기·1/100 메모리.

특징: - ✓ 매우 작은 크기 - ✓ 큰 테이블에 빠른 빌드 - ✗ B-Tree 보다 조회 약간 느림 - 데이터가 물리 순서대로 박혀 있어야 효율적

완벽한 시나리오 — 로그·시계열 (시간 순서로 자연 정렬).

-- 1억 행 로그 테이블
CREATE INDEX idx_logs_created_brin ON logs USING BRIN (created_at);
-- 크기 = 100KB (B-Tree 는 2GB)

SELECT * FROM logs WHERE created_at >= '2026-05-17';
-- BRIN 활용 — 적절한 페이지만 스캔

(6) SP-GiST — 공간 분할

CREATE INDEX idx ON ip_logs USING SPGIST (ip);

비균등 데이터 구조 (Quad-tree·KD-tree). 텍스트 접두사·IP 등. 잘 안 씀.

어느 인덱스 — 룰

일반 컬럼 → B-Tree
정확 비교만 → B-Tree (Hash X)
배열·JSONB → GIN
범위 타입 → GiST
기하 (PostGIS) → GiST
대용량 시계열 (created_at) → BRIN
전문 검색 → GIN (tsvector)

커버링 인덱스 — INCLUDE

CREATE INDEX idx_orders ON orders(user_id) INCLUDE (status, amount);

INCLUDE = 인덱스에 "검색 키는 아니지만 함께 저장". 인덱스만으로 결과 답 가능 (Index Only Scan) → 테이블 안 봐도 됨.

SELECT status, amount FROM orders WHERE user_id = 1;
-- Index Only Scan — 매우 빠름

자주 함께 조회되는 컬럼 INCLUDE.

Index Only Scan

EXPLAIN 출력의 좋은 신호.

Index Only Scan using idx_orders on orders
  Heap Fetches: 0    ← 테이블 안 봄

Heap Fetches: 0 = 인덱스만으로 답. 매우 빠름. INCLUDE 또는 "모든 결과 컬럼이 인덱스에" 일 때.

함정 5가지

(1) 복합 인덱스 순서 잘못

선별성 낮은 컬럼 앞에 박으면 — 인덱스 효율 낮음.

(2) Hash 인덱스 신규 사용

B-Tree 가 거의 항상 우수. Hash 는 거의 안 씀.

(3) GIN 쓰기 비용 무시

배열·JSONB 가 자주 변경되면 GIN 비용 큼. fastupdate = on 의존.

(4) BRIN 을 정렬 안 된 데이터에

행이 무작위 박힌 컬럼에 BRIN = 효율 0. created_at 같은 자연 정렬만.

(5) ORDER BY 인덱스 방향 불일치

CREATE INDEX idx ON orders(created_at ASC);
SELECT ... ORDER BY created_at DESC;
-- 역방향 스캔 (가능하지만 일부 느림)

자주 쓰는 방향으로.

🎯 인덱스 선택 5룰

(1) 일반 = B-Tree (기본). (2) 배열·JSONB = GIN. (3) 범위·기하 = GiST. (4) 대용량 시계열 = BRIN. (5) 복합 인덱스 순서 = 선별성 + 정확 비교 + 범위. INCLUDE 로 Index Only Scan.

한 줄 정리 — 인덱스 6종. 실무 99% = B-Tree·GIN. 복합 인덱스 순서 = 선별성·정확 비교·범위. GIN = 배열·JSONB·전문 검색. GiST = 범위·기하·EXCLUDE. BRIN = 시계열. INCLUDE 로 Index Only Scan.

시험 직전 한 번 더 — 인덱스 종류 입문자가 매번 헷갈리는 것

  • 인덱스 6종 = B-Tree·Hash·GIN·GiST·BRIN·SP-GiST
  • 실무 99% = B-Tree + GIN
  • B-Tree = 정렬 가능 타입 기본
  • =·<·>·BETWEEN·LIKE 'prefix%'·ORDER BY
  • 복합 인덱스 = (a, b, c) 왼쪽 prefix
  • WHERE b = ? = 인덱스 X (a 빠짐)
  • WHERE a + b = 인덱스 OK
  • 순서 결정 = 선별성·정확 비교·범위
  • 정확 비교 컬럼 앞, 범위 뒤
  • Hash = = 만, 거의 안 씀
  • GIN = 배열·JSONB·전문 검색
  • @>·?·?|·?&·@@
  • GIN 쓰기 비용 = fastupdate
  • GiST = 범위·기하·EXCLUDE
  • btree_gist = GiST + =
  • TSTZRANGE·POINT
  • BRIN = 대용량 정렬 데이터
  • 크기 1/100, 속도 약간 느림
  • 시계열 created_at 표준
  • 데이터가 물리 정렬되어야 효율
  • SP-GiST = 비균등 (잘 안 씀)
  • INCLUDE = 커버링 인덱스
  • Index Only Scan = 인덱스만으로 (테이블 X)
  • Heap Fetches: 0 = 좋은 신호
  • ORDER BY 인덱스 방향 일치 권장
  • 운영 = CONCURRENTLY 무조건
  • 한국 백엔드 = B-Tree 복합 + GIN JSONB + BRIN 로그

시리즈 다른 편

시리즈 다음 글

다음 글(36편)에서는 인덱스 종합 — 운영 전략·튜닝·함정 모음.

공식 문서: PostgreSQL 18 — Indexes: Types에서 더 자세한 사양을 확인할 수 있어요.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!