한양과학기술고등학교

에듀테크 프로젝트 대시보드

쿼리 튜닝

pg_stat_statements sargable N+1 통계 unused index

느린 쿼리를 찾고, 왜 인덱스를 안 타는지 진단하고, 고칩니다.

1. 느린 쿼리 찾기

# postgresql.conf
shared_preload_libraries = 'pg_stat_statements'   # 재시작 필요
pg_stat_statements.track = top
CREATE EXTENSION pg_stat_statements;

SELECT calls, round(total_exec_time) AS total_ms,
       round(mean_exec_time, 2) AS mean_ms, query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 15;
# 실행 중 느린 쿼리를 로그로
log_min_duration_statement = 500   # 0.5초 이상 걸린 쿼리 기록

2. 인덱스를 못 타는 흔한 이유

non-sargable 패턴
  • 컬럼에 함수/연산: WHERE date(ts) = ..., WHERE price * 1.1 > ...
  • 앞부분 와일드카드: LIKE '%kim'
  • 타입 불일치: WHERE varchar_col = 123 (암묵 캐스팅)
  • OR 로 서로 다른 컬럼 묶기 → UNION 으로 분리하면 각자 인덱스

3. 통계·플래너

ANALYZE students;                              -- 통계 갱신
ALTER TABLE students ALTER COLUMN class_id SET STATISTICS 500;  -- 편향 컬럼 정밀도 ↑
SHOW random_page_cost;                         -- SSD 면 1.1 권장 (기본 4)

추정 행수(EXPLAIN 의 rows)가 실제와 크게 다르면 플래너가 잘못된 계획을 고릅니다 — 먼저 ANALYZE.

4. 안 쓰는 인덱스 / N+1

-- 한 번도 안 쓰인 인덱스 (쓰기 비용만 발생)
SELECT relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0 AND indexrelname NOT LIKE '%_pkey';
N+1
  • 목록 1번 + 각 항목마다 상세 1번씩 쿼리 → JOIN 하나로 합치거나 WHERE id = ANY($1) 로 배치 조회. ORM 이면 eager loading
"복원을 테스트하기 전까지 그것은 백업이 아니다"