콘텐츠로 이동

DB 슬로우 쿼리 진단 & 튜닝 — 성능 저하 원인 찾기

응답 시간이 느린 API의 80% 이상은 DB 쿼리가 원인입니다. 슬로우 쿼리를 찾아내고 개선하는 것이 성능 최적화의 첫 번째 단계입니다.

-- postgresql.conf 설정
log_min_duration_statement = 1000 -- 1초 이상 걸리는 쿼리 기록
log_statement = 'none' -- 전체 로그는 비활성 (성능 영향)
log_line_prefix = '%t [%p]: '
Terminal window
# 실시간 슬로우 쿼리 확인
tail -f /var/log/postgresql/postgresql.log | grep "duration:"
-- 슬로우 쿼리 로그 활성화
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 1초 이상
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
Terminal window
# mysqldumpslow으로 요약
mysqldumpslow -s t -t 20 /var/log/mysql/slow.log
# -s t: 실행 시간 기준 정렬 / -t 20: 상위 20개

쿼리가 왜 느린지 확인하는 핵심 도구입니다.

-- EXPLAIN ANALYZE: 실제 실행 + 시간 측정
EXPLAIN ANALYZE
SELECT o.id, o.total, u.name
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'pending'
ORDER BY o.created_at DESC
LIMIT 50;
Seq Scan on orders (cost=0.00..45231.00 rows=12345 ...)
↑ 풀 테이블 스캔 — 인덱스 없음, 위험 신호
Index Scan using idx_orders_status on orders ...
↑ 인덱스 사용 — 정상
실행 계획 키워드의미조치
Seq Scan풀 테이블 스캔인덱스 추가 검토
Hash Join조인 시 해시 사용대부분 정상, rows 수 확인
Nested Loop중첩 루프 조인rows가 많으면 N+1 의심
Sort정렬 연산정렬 기준 컬럼 인덱스 확인
-- WHERE, JOIN ON, ORDER BY에 쓰이는 컬럼이 대상
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_user_created ON orders(user_id, created_at DESC);
복합 인덱스 (a, b, c)는:
- WHERE a = ? → 사용
- WHERE a = ? AND b = ? → 사용
- WHERE b = ? → ❌ 사용 안 됨 (선두 컬럼 없음)

N+1은 DB 슬로우 쿼리 중 가장 흔하고 치명적인 패턴입니다.

# N+1 발생 코드 (Django ORM)
orders = Order.objects.filter(status='pending')
for order in orders:
print(order.user.name) # 매 반복마다 SELECT users WHERE id=?
# 주문 100건이면 DB 쿼리 101번
# 해결: select_related (JOIN)
orders = Order.objects.filter(status='pending').select_related('user')
# 쿼리 1번으로 해결
// N+1 발생 코드 (JPA)
List<Order> orders = orderRepository.findByStatus("pending");
orders.forEach(o -> System.out.println(o.getUser().getName()));
// 해결: JPQL fetch join
@Query("SELECT o FROM Order o JOIN FETCH o.user WHERE o.status = :status")
List<Order> findByStatusWithUser(@Param("status") String status);

pg_stat_statements로 Top 슬로우 쿼리 찾기

섹션 제목: “pg_stat_statements로 Top 슬로우 쿼리 찾기”
-- pg_stat_statements 확장 활성화 (postgresql.conf)
-- shared_preload_libraries = 'pg_stat_statements'
-- 평균 실행 시간 기준 상위 10개 쿼리
SELECT
query,
calls,
round(mean_exec_time::numeric, 2) AS avg_ms,
round(total_exec_time::numeric, 2) AS total_ms
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
visitor count

이 가이드를 내 서비스에 직접 적용해 보세요.

TestForge 무료 스캔 시작 →