모니터링과 성능 진단
운영 중인 데이터베이스는 지속적으로 모니터링해야 합니다. 문제가 터지기 전에 징후를 감지하고, 문제가 발생했을 때 원인을 빠르게 진단할 수 있어야 합니다. 모니터링의 목적은 사후 대응이 아니라 사전 예방입니다.
핵심 메트릭
| 메트릭 | 설명 | 위험 신호 |
|---|---|---|
| TPS (Transactions/sec) | 초당 트랜잭션 처리량 | 급격한 하락 |
| Active Sessions | 현재 활성 세션 수 | sessions 제한에 근접 |
| Slow Query | 응답 시간이 긴 쿼리 | 1초 초과 쿼리 증가 |
| Lock Wait | 락 대기 세션 수 | 지속적 증가 |
| Buffer Cache Hit Ratio | 캐시 적중률 | 99% 미만 |
| Disk I/O | 디스크 읽기/쓰기 | 과도한 물리적 읽기 |
| Tablespace 사용률 | 저장 공간 | 80% 초과 |
| Connection Pool | 커넥션 풀 사용률 | 최대치 근접 |
| Redo/Undo 사용량 | 로그/언두 공간 | 급격한 증가 |
Oracle 성능 진단 도구
Oracle은 엔터프라이즈 급의 풍부한 내장 성능 진단 도구를 제공합니다.
AWR (Automatic Workload Repository)
AWR은 Oracle이 자동으로 수집하는 성능 통계 스냅샷입니다. 기본 1시간 간격으로 스냅샷을 생성하고 8일간 보관합니다.
-- AWR 스냅샷 목록 확인
SELECT snap_id, begin_interval_time, end_interval_time
FROM dba_hist_snapshot
ORDER BY snap_id DESC
FETCH FIRST 10 ROWS ONLY;
-- AWR 리포트 생성 (HTML)
@$ORACLE_HOME/rdbms/admin/awrrpt.sql
-- 수동 스냅샷 생성 (장애 전후 비교용)
EXEC DBMS_WORKLOAD_REPOSITORY.CREATE_SNAPSHOT();
-- 보관 주기 변경 (30일, 30분 간격)
EXEC DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS(
retention => 43200, -- 30일 (분 단위)
interval => 30 -- 30분 간격
);AWR 리포트에서 확인할 핵심 항목을 정리하면 다음과 같습니다.
| AWR 섹션 | 확인 내용 | 관점 |
|---|---|---|
| Top 5 Timed Events | 가장 많은 시간을 소비하는 대기 이벤트 | 병목 원인 |
| SQL ordered by Elapsed Time | 가장 오래 걸린 SQL | 개선 후보 |
| SQL ordered by Gets | Buffer 읽기가 많은 SQL | I/O 부하 |
| SQL ordered by Executions | 가장 자주 실행된 SQL | 캐싱 효과 |
| Instance Activity Stats | 전반적인 인스턴스 활동 통계 | 전체 현황 |
| Load Profile | DB Time, 논리적/물리적 읽기 | 부하 추이 |
ASH (Active Session History)
ASH는 활성 세션의 실시간 샘플링 데이터입니다. 1초 간격으로 활성 세션 정보를 수집합니다. AWR이 정기 보고서라면, ASH는 실시간 모니터링 도구입니다.
-- 최근 5분간 가장 많이 대기한 이벤트
SELECT event, COUNT(*) AS samples,
ROUND(COUNT(*) * 100 / SUM(COUNT(*)) OVER (), 1) AS pct
FROM v$active_session_history
WHERE sample_time > SYSDATE - 5/1440
GROUP BY event
ORDER BY COUNT(*) DESC;
-- 특정 SQL이 무엇을 대기하는지
SELECT event, COUNT(*)
FROM v$active_session_history
WHERE sql_id = 'abc123def456'
AND sample_time > SYSDATE - 1/24
GROUP BY event
ORDER BY COUNT(*) DESC;
-- 시간대별 활성 세션 추이
SELECT TO_CHAR(sample_time, 'HH24:MI') AS time_slot,
COUNT(DISTINCT session_id) AS active_sessions
FROM v$active_session_history
WHERE sample_time > SYSDATE - 1/24
GROUP BY TO_CHAR(sample_time, 'HH24:MI')
ORDER BY time_slot;ADDM (Automatic Database Diagnostic Monitor)
ADDM은 AWR 데이터를 분석하여 성능 문제의 원인과 권고안을 자동으로 생성합니다.
-- ADDM 리포트 자동 확인 (최근 스냅샷)
SELECT dbms_advisor.get_task_report(task_name) AS report
FROM dba_advisor_tasks
WHERE task_name LIKE 'ADDM%'
ORDER BY created DESC
FETCH FIRST 1 ROW ONLY;
-- ADDM 권고안 확인
SELECT f.finding_name, r.type, r.message
FROM dba_advisor_findings f
JOIN dba_advisor_recommendations r
ON f.task_id = r.task_id AND f.finding_id = r.finding_id
WHERE f.task_id = (SELECT MAX(task_id) FROM dba_advisor_tasks
WHERE advisor_name = 'ADDM');V$ 동적 성능 뷰
-- 현재 활성 세션과 실행 중인 SQL
SELECT s.sid, s.serial#, s.username, s.status,
s.sql_id, q.sql_text,
s.event, s.wait_class,
s.seconds_in_wait
FROM v$session s
LEFT JOIN v$sql q ON s.sql_id = q.sql_id AND s.sql_child_number = q.child_number
WHERE s.status = 'ACTIVE' AND s.username IS NOT NULL;
-- 락 대기 관계 (블로커 → 웨이터)
SELECT
blocker.sid AS blocker_sid,
blocker.username AS blocker_user,
waiter.sid AS waiter_sid,
waiter.username AS waiter_user,
waiter.event AS wait_event,
waiter.seconds_in_wait AS wait_seconds
FROM v$session waiter
JOIN v$session blocker ON waiter.blocking_session = blocker.sid
WHERE waiter.blocking_session IS NOT NULL;
-- Buffer Cache Hit Ratio
SELECT ROUND(
(1 - (phy.value / (cur.value + con.value))) * 100, 2
) AS hit_ratio
FROM v$sysstat phy, v$sysstat cur, v$sysstat con
WHERE phy.name = 'physical reads'
AND cur.name = 'db block gets'
AND con.name = 'consistent gets';
-- SGA 메모리 구성 확인
SELECT component, current_size/1024/1024 AS size_mb
FROM v$sga_dynamic_components
WHERE current_size > 0
ORDER BY current_size DESC;
-- Tablespace 사용률
SELECT tablespace_name,
ROUND(used_space * 8192 / 1024 / 1024) AS used_mb,
ROUND(tablespace_size * 8192 / 1024 / 1024) AS total_mb,
ROUND(used_percent, 1) AS used_pct
FROM dba_tablespace_usage_metrics
ORDER BY used_percent DESC;MySQL 성능 진단 도구
Performance Schema
MySQL 5.5+에서 제공하는 성능 모니터링 프레임워크입니다.
-- Slow Query 상위 10개
SELECT DIGEST_TEXT, COUNT_STAR,
ROUND(SUM_TIMER_WAIT/1000000000000, 3) AS total_sec,
ROUND(AVG_TIMER_WAIT/1000000000000, 3) AS avg_sec,
SUM_ROWS_EXAMINED, SUM_ROWS_SENT
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;
-- 현재 실행 중인 쿼리
SELECT thread_id, event_name, sql_text,
ROUND(timer_wait/1000000000000, 3) AS elapsed_sec
FROM performance_schema.events_statements_current
WHERE sql_text IS NOT NULL;
-- 테이블별 I/O 통계
SELECT object_schema, object_name,
count_read, count_write,
ROUND(sum_timer_read/1000000000000, 3) AS read_sec,
ROUND(sum_timer_write/1000000000000, 3) AS write_sec
FROM performance_schema.table_io_waits_summary_by_table
WHERE object_schema NOT IN ('mysql', 'performance_schema')
ORDER BY sum_timer_wait DESC
LIMIT 10;
-- 인덱스 사용 현황
SELECT object_schema, object_name, index_name,
count_star AS total_access,
count_read, count_write
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
AND object_schema NOT IN ('mysql', 'performance_schema')
ORDER BY count_star DESC;Slow Query Log
-- 현재 설정 확인
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 동적으로 Slow Query Log 활성화
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 1초 이상 쿼리 기록
SET GLOBAL log_queries_not_using_indexes = 'ON'; -- 인덱스 미사용 쿼리
-- InnoDB 상태 확인
SHOW ENGINE INNODB STATUS;
-- → TRANSACTIONS, SEMAPHORES, BUFFER POOL 섹션 확인PostgreSQL 성능 진단 도구
-- pg_stat_statements 확장 (가장 중요한 도구)
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- 가장 시간이 많이 걸리는 쿼리 상위 10개
SELECT query,
calls,
ROUND(total_exec_time::numeric, 2) AS total_ms,
ROUND(mean_exec_time::numeric, 2) AS avg_ms,
rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
-- 테이블 통계 (순차 스캔 vs 인덱스 스캔)
SELECT relname,
seq_scan, idx_scan,
CASE WHEN seq_scan + idx_scan > 0
THEN ROUND(idx_scan * 100.0 / (seq_scan + idx_scan), 1)
ELSE 0 END AS idx_scan_pct,
n_tup_ins, n_tup_upd, n_tup_del,
n_dead_tup
FROM pg_stat_user_tables
ORDER BY seq_scan DESC;
-- 인덱스 사용률이 낮은 인덱스 (삭제 후보)
SELECT schemaname, tablename, indexname,
idx_scan, idx_tup_read
FROM pg_stat_user_indexes
WHERE idx_scan < 50
ORDER BY idx_scan;
-- 캐시 적중률
SELECT
sum(heap_blks_hit) * 100.0 / (sum(heap_blks_hit) + sum(heap_blks_read))
AS cache_hit_ratio
FROM pg_statio_user_tables;
-- VACUUM 필요성 확인 (Dead Tuple 비율)
SELECT relname,
n_live_tup, n_dead_tup,
ROUND(n_dead_tup * 100.0 / NULLIF(n_live_tup, 0), 1) AS dead_pct,
last_autovacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;외부 모니터링 도구
| 도구 | 특징 | 적합한 환경 |
|---|---|---|
| Oracle Enterprise Manager (OEM) | Oracle 공식 GUI, 종합 관리 | Oracle 전용 |
| Grafana + Prometheus | 오픈소스, 대시보드 커스터마이징 | 범용 |
| Datadog | SaaS, 다양한 DB 지원, APM 통합 | 클라우드 |
| Zabbix | 오픈소스, 알림 설정, 에이전트 기반 | 온프레미스 |
| PMM (Percona Monitoring) | MySQL/PostgreSQL 전문 | MySQL/PG |
| CloudWatch | AWS RDS/Aurora 전용 | AWS |
알림 설정 기준
| 항목 | 경고 (Warning) | 위험 (Critical) | 조치 |
|---|---|---|---|
| CPU 사용률 | 70% | 90% | 쿼리 최적화, 스케일 업 |
| 메모리 사용률 | 80% | 95% | 메모리 설정 확인 |
| 디스크 사용률 | 75% | 90% | 공간 확보, 파티셔닝 |
| Active Sessions | max의 60% | max의 80% | 커넥션 풀 확인 |
| Slow Query | 10건/분 | 50건/분 | 쿼리 튜닝 |
| Lock Wait | 5건 이상 | 20건 이상 | 트랜잭션 점검 |
| Cache Hit Ratio | 97% 미만 | 95% 미만 | 버퍼 크기 증가 |
| Replication Lag | 5초 이상 | 30초 이상 | 부하 분산, 네트워크 |
장애 진단 프로세스
패턴 1: 갑작스러운 Slow Query 증가
→ 원인: 통계 정보 갱신으로 실행 계획 변경
→ 조치: 실행 계획 확인, 힌트 또는 통계 재수집
패턴 2: 특정 시간대 성능 저하
→ 원인: 배치 작업과 온라인 작업 충돌
→ 조치: 배치 시간 조정, 리소스 분리
패턴 3: 점진적 성능 저하
→ 원인: 데이터 증가, 인덱스 단편화
→ 조치: 파티셔닝, 인덱스 리빌드, 아카이빙
패턴 4: Lock 대기 급증
→ 원인: 긴 트랜잭션, 교착 상태
→ 조치: 트랜잭션 범위 축소, 접근 순서 통일
패턴 5: Connection 부족
→ 원인: 커넥션 풀 설정 부적절, 커넥션 누수
→ 조치: 풀 크기 조정, 애플리케이션 코드 점검DBMS별 핵심 진단 도구 비교
| 기능 | Oracle | MySQL | PostgreSQL |
|---|---|---|---|
| 워크로드 기록 | AWR | Performance Schema | pg_stat_statements |
| 실시간 세션 | ASH, v$session | SHOW PROCESSLIST | pg_stat_activity |
| 자동 진단 | ADDM | 없음 | 없음 (pgDiagnose 등 외부) |
| Slow Query | AWR SQL 섹션 | Slow Query Log | pg_stat_statements |
| 락 모니터링 | v$lock | information_schema.INNODB_LOCKS | pg_locks |
| 실행 계획 확인 | EXPLAIN PLAN, DBMS_XPLAN | EXPLAIN ANALYZE | EXPLAIN (ANALYZE, BUFFERS) |
| 통계 수집 | DBMS_STATS | ANALYZE TABLE | ANALYZE, VACUUM ANALYZE |
| 공간 관리 | DBA_TABLESPACE | information_schema | pg_stat_user_tables |
시험에서는 Oracle AWR 리포트의 핵심 확인 항목(Top 5 Timed Events, SQL Elapsed Time), Buffer Cache Hit Ratio 계산 공식, ASH의 샘플링 주기(1초) 등이 자주 출제됩니다. 또한 모니터링 메트릭의 경고/위험 기준치와 성능 문제 유형별 진단 접근 방법을 구분할 수 있어야 합니다.
실무에서 가장 중요한 원칙은 평상시의 메트릭 기준선(Baseline)을 확보하는 것입니다. 기준선이 없으면 현재 수치가 정상인지 비정상인지 판단할 수 없습니다. 주기적으로 AWR 리포트를 생성하고, 주요 메트릭의 추이를 Grafana 대시보드에 축적해두면 장애 시 정상 시점과의 비교가 즉시 가능합니다.
성능 진단의 핵심 순서는 리소스 확인 → 대기 이벤트 분석 → 문제 SQL 특정 → 실행 계획 확인 → 조치 및 검증입니다. 이 흐름을 숙지하면 대부분의 성능 문제를 체계적으로 해결할 수 있습니다.
Buffer Cache Hit Ratio 공식:
- Oracle: (1 - physical reads / (db block gets + consistent gets)) * 100
- MySQL: (1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) * 100
- PostgreSQL: heap_blks_hit / (heap_blks_hit + heap_blks_read) * 100
정리
데이터베이스 모니터링의 핵심은 메트릭의 기준선(Baseline)을 설정하고, 정상 범위를 벗어나는 변화를 신속하게 감지하는 것입니다. 평소의 TPS, Active Sessions, Cache Hit Ratio 등을 파악해두면 이상 징후를 즉시 인지할 수 있습니다.
각 DBMS는 고유한 진단 도구를 제공합니다. Oracle의 AWR/ASH/ADDM, MySQL의 Performance Schema와 Slow Query Log, PostgreSQL의 pg_stat_statements가 대표적입니다. 이들 도구의 사용법과 핵심 확인 항목을 숙지하는 것이 성능 진단의 기본입니다.
모니터링 체계는 수집(Exporter) → 저장(Prometheus/InfluxDB) → 시각화(Grafana) → 알림(PagerDuty/Slack)의 파이프라인으로 구성합니다. 알림은 경고/위험 2단계로 나누고, 자동 복구가 가능한 항목은 자동화합니다.
다음 절에서는 애플리케이션과 DB 사이의 ORM과 SQL의 균형을 다루겠습니다.