실제 얘기로 시작해볼게. 네가 대형 쇼핑몰 플랫폼에서 개발자로 일한다고 상상해봐. 모든 게 완벽하게 돌아가는데, 한 가지 문제가 있어: 월간 매출 리포트가 너무 느리게 떠서, 유저들이 리포트 기다리는 동안 주문을 하나 더 할 수 있을 정도야. 너도 초조하고, 매니저도 초조하고, PostgreSQL 서버는 주전자처럼 끓고, 이거 완전 아포칼립스 분위기지. 근데 만약 한 번에 원인을 딱 찾아서 해결할 수 있다면 얼마나 좋겠어?
쿼리 성능 분석은 PostgreSQL의 건강검진 같은 거야. 어디가 "병목"(아픈 부분)인지 찾아내고, 그걸 고쳐서 유저 경험도 좋아지고 시스템 자원도 아낄 수 있지.
PostgreSQL에서 쿼리가 내부적으로 어떻게 동작할까?
예를 들어, 이런 간단한 쿼리를 쓴다고 해보자:
SELECT * FROM products WHERE price > 100;
PostgreSQL이 바로 데이터를 뽑는 게 아니야. 먼저 네 쿼리를 분석하고, 어떻게 실행할지 계획을 세운 다음에 실제로 동작해.
쿼리 실행의 주요 단계:
- 파싱. PostgreSQL이 네 쿼리에 문법 오류가 있는지 체크하고, 중간 형태로 바꿔.
- 최적화. 쿼리 옵티마이저가 여러 실행 방법을 평가해서, 시간과 자원이 제일 적게 드는 "가장 싼" 방법을 골라.
- 실행. 서버가 선택된 플랜대로 실제로 데이터를 뽑아와.
"병목"이란 뭘까?
"병목"은 쿼리에서 전체 속도를 느리게 만드는 부분이야. 예상보다 시간이 오래 걸리거나 자원을 많이 먹는 연산이 있을 수 있지. 예를 들어, PostgreSQL이 인덱스를 안 쓰고 테이블 전체를 스캔(Seq Scan)하면 쿼리가 느려져. 아니면 데이터가 생각보다 많아서 정렬, 조인, 필터링에 시간이 엄청 걸릴 수도 있고.
이런 부분들이 바로 병목이야 — 이런 것부터 먼저 찾아서 최적화하는 게 좋아.
쿼리 성능 분석 도구들
PostgreSQL에는 쿼리 문제를 찾는 데 도움 되는 강력한 도구들이 있어:
- EXPLAIN 그리고 EXPLAIN ANALYZE. 이 명령어들은 PostgreSQL이 쿼리를 어떻게 실행할지 보여주거나, 실제로 실행해서 성능을 측정해줘.
EXPLAIN: 쿼리를 실제로 실행하지 않고 실행 계획만 보여줘.EXPLAIN ANALYZE: 쿼리를 실행해서 실제 실행 계획과 시간 같은 메트릭을 보여줘.
EXPLAIN 사용 예시:
EXPLAIN SELECT * FROM products WHERE price > 100;
출력 결과:
Seq Scan on products (cost=0.00..35.50 rows=5 width=72)
Filter: (price > 100)
여기서 보면 "Seq Scan" — 테이블 전체를 스캔하고 있어서, 큰 테이블에는 비효율적이야.
- pg_stat_statements. 이건 추가 확장 기능인데, 실행된 쿼리들을 기록해줘. 이걸로 이런 걸 볼 수 있어:
- 서버에서 어떤 쿼리들이 실행되고 있는지.
- 각 쿼리에 얼마나 시간이 걸리는지.
- 쿼리가 몇 줄을 반환하고, 자원을 얼마나 쓰는지.
pg_stat_statements를 켜려면:
- 확장 기능 활성화:
CREATE EXTENSION pg_stat_statements;
- PostgreSQL 설정하기:
postgresql.conf파일에 아래를 추가해:
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all
- PostgreSQL 재시작.
이제 쿼리 분석 가능:
SELECT * FROM pg_stat_statements ORDER BY total_time DESC LIMIT 5;
이렇게 하면 전체 실행 시간 기준으로 제일 "무거운" 쿼리 5개를 볼 수 있어.
- 느린 쿼리 로그. PostgreSQL이 너무 오래 걸리는 쿼리(예: 1초 이상)를 로그 파일에 기록하게 할 수 있어.
이걸 하려면 postgresql.conf에 이렇게 설정해:
log_min_duration_statement = 1000 # 밀리초 단위 (1초)
이제 느린 쿼리들이 PostgreSQL 로그에 저장돼.
성능 분석을 위한 주요 메트릭
쿼리 성능을 분석할 때 아래 핵심 메트릭을 꼭 봐야 해:
- 실행 시간. 쿼리 실행에 걸린 시간. 빠를수록 좋지.
- 행(row) 수. 쿼리가 예상보다 많은 행을 반환하거나 스캔하면 문제가 될 수 있어.
- 인덱스 사용. 인덱스를 써야 하는데 Seq Scan(전체 스캔)만 한다면 최적화 신호야.
- 버퍼/디스크 작업. 디스크를 많이 읽는 쿼리는 메모리만 쓰는 쿼리보다 느려.
이 지식, 실제로 어떻게 써먹을까?
예시 1: 느린 쿼리
100보다 비싼 상품을 다 뽑는 쿼리를 쓴다고 해보자:
SELECT * FROM products WHERE price > 100;
이 쿼리가 너무 느리게 실행돼서
EXPLAIN을 써봤더니:
Seq Scan on products (cost=0.00..35.50 rows=5 width=72)
Filter: (price > 100)
문제: price 컬럼에 인덱스가 없어서 테이블 전체를 스캔하고 있어.
해결법:
인덱스를 만들어줘:
CREATE INDEX idx_price ON products(price);
이제 쿼리가 Index Scan을 사용해:
Index Scan using idx_price on products (cost=0.15..8.25 rows=5 width=72)
Index Cond: (price > 100)
예시 2: pg_stat_statements로 느린 쿼리 찾기
아래 명령어로:
SELECT query, total_time, calls
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 1;
실행 시간이 많이 걸리는 쿼리를 찾을 수 있어. EXPLAIN ANALYZE로 플랜을 보고, 수정해서 더 빠르게 만들 수 있지.
EXPLAIN, pg_stat_statements 등 이런 도구들을 쓰기 시작하면, 네 쿼리들이 훨씬 빨라지고 PostgreSQL 서버도 스위스 시계처럼 돌아갈 거야. 다음 강의에서는 EXPLAIN의 cost, rows, width 같은 파라미터를 파고들어서, 쿼리 실행 계획을 책처럼 읽는 법을 배울 거야.
GO TO FULL VERSION