인덱스는 읽기 빨라지지만 쓰기 느려짐. 무지성으로 만들지 말고 EXPLAIN ANALYZE 보고 결정. composite index 는 컬럼 순서가 핵심. 6개 이상 인덱스 가진 테이블은 검토 대상.
📖 핵심 개념
인덱스는 별도 자료구조 (B-tree). 매 INSERT/UPDATE 마다 갱신.
WHERE / JOIN / ORDER BY 의 등호 / 범위 / 정렬 컬럼이 인덱스 후보.
composite (a, b) 는 (a) 만 검색해도 사용 가능, (b) 만은 X.
💻 코드 패턴
EXPLAIN ANALYZE 로 결정
EXPLAIN(ANALYZE,BUFFERS)SELECT*FROMordersWHEREuser_id=123ORDERBYcreated_atDESCLIMIT10;-- Seq Scan ← 느림. user_id 인덱스 추가 후 비교.
Composite index — 순서가 중요
-- 자주 함께 검색: (user_id, status, created_at)
CREATEINDEXidx_orders_user_status_createdONorders(user_id,status,created_atDESC);-- 사용 가능:
-- WHERE user_id = ?
-- WHERE user_id = ? AND status = ?
-- WHERE user_id = ? AND status = ? AND created_at > ?
-- WHERE user_id = ? ORDER BY created_at DESC
-- 사용 불가:
-- WHERE status = ? (앞 컬럼 skip)
규칙: = 컬럼 → IN 컬럼 → 범위 컬럼 → 정렬 컬럼 순서.
Partial index — 작은 도메인만
-- 90%의 행이 status='completed' 인 경우, active 만 인덱스
CREATEINDEXidx_orders_active_userONorders(user_id)WHEREstatusIN('pending','processing');-- 인덱스 크기 작음, 갱신 비용 낮음
Covering / INCLUDE
CREATEINDEXidx_users_email_incONusers(email)INCLUDE(name,avatar_url);-- WHERE email = ? + SELECT name, avatar_url 만 → 테이블 안 읽고 인덱스로 종료
Functional / Expression
CREATEINDEXidx_users_lower_emailONusers(LOWER(email));-- WHERE LOWER(email) = LOWER(?) 사용
Index 미사용 패턴
-- ❌ 함수 호출 → 인덱스 X
WHERELOWER(email)='foo'-- 위 functional 인덱스 없으면
WHEREcreated_at::date='2025-01-01'-- → BETWEEN 로
-- ❌ leading wildcard
WHEREemailLIKE'%@example.com'-- B-tree X. trgm/GIN 필요
-- ❌ OR 가 다른 컬럼
WHEREuser_id=1ORemail='x'-- 둘 다 별도 인덱스 + Bitmap OR
🤔 의사결정 기준
컬럼
인덱스?
Primary key
자동
Foreign key
✅ — JOIN 빈번
Unique 제약
자동
자주 WHERE
✅
자주 ORDER BY + LIMIT
✅
카디널리티 낮음 (boolean)
❌ — 보통 무용. partial 가능
Text 검색 (LIKE %x%)
trgm / GIN
JSON 안 검색
GIN on jsonb
시계열 최신만
partial WHERE created_at > now() - interval '30d'
❌ 안티패턴
모든 컬럼에 단일 인덱스: write 폭증 + planner 가 못 고름. composite 가 보통 답.
composite index 컬럼 순서 무관 가정: (a, b) 와 (b, a) 다름. EXPLAIN 으로.
거대 테이블 동기 CREATE INDEX: write lock 길게. CONCURRENTLY 사용.
사용 안 되는 인덱스 청소 안 함: pg_stat_user_indexes idx_scan = 0 인 거 정기 청소.
VACUUM / ANALYZE 안 함: 통계 stale → planner 잘못된 선택.
인덱스 = 만능 가정: 작은 테이블은 Seq Scan 이 더 빠름.
timestamp 그대로 인덱스 + 매일 새 값: 인덱스 끝부분만 hot. BRIN 도 검토.