모니터링과 성능 진단
운영 중인 데이터베이스는 지속적으로 모니터링해야 합니다. 문제가 터지기 전에 징후를 감지하고, 문제가 발생했을 때 원인을 빠르게 진단할 수 있어야 합니다. 모니터링의 목적은 사후 대응이 아니라 사전 예방입니다.
핵심 메트릭
| 메트릭 | 설명 | 위험 신호 |
|---|---|---|
| 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 사용량 | 로그/언두 공간 | 급격한 증가 |
리소스 메트릭
CPU, 메모리, 디스크 I/O, 네트워크
→ 인프라 수준의 병목 탐지
데이터베이스 메트릭
TPS, Active Sessions, Lock Wait, Cache Hit Ratio
→ DB 엔진 수준의 성능 파악
쿼리 메트릭
Slow Query, Full Table Scan, Sort 횟수
→ 개별 SQL 수준의 문제 탐지
비즈니스 메트릭
응답 시간(p50, p95, p99), 에러율
→ 사용자 관점의 서비스 품질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, 논리적/물리적 읽기 | 부하 추이 |
1단계: Load Profile 확인
→ DB Time, Redo size, Logical reads 등 전체 부하 확인
→ 이전 기간과 비교하여 급격한 변화가 있는지 확인
2단계: Top 5 Timed Events
→ "db file sequential read" → 인덱스/단일 블록 읽기 (정상적일 수 있음)
→ "db file scattered read" → Full Table Scan (대량 데이터)
→ "log file sync" → 커밋 빈도 과다 (배치 커밋 고려)
→ "enq: TX - row lock contention" → 행 잠금 경합
→ "latch free" → 내부 경합 (메모리 설정 확인)
3단계: SQL 분석
→ Elapsed Time 상위 SQL 확인
→ 실행 계획 변경 여부 확인
→ Buffer Gets/Rows 비율로 비효율 판단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 섹션 확인실행 시간이 가장 긴 상위 10개
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
실행 횟수가 가장 많은 상위 10개
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
pt-query-digest (Percona Toolkit)
pt-query-digest /var/log/mysql/slow.log
→ 쿼리 프로파일링, 집계, 순위 제공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 |
┌────────────────────────────────────────────┐
│ Database Dashboard │
├────────────┬───────────┬───────────────────┤
│ TPS │ Active │ Buffer Cache │
│ ██████ 450 │ Sessions │ Hit Ratio │
│ │ ██ 12/200 │ ████████ 99.7% │
├────────────┼───────────┼───────────────────┤
│ Slow Query │ Lock Wait │ Tablespace │
│ 3건/분 │ 0건 │ USERS: 65% │
│ │ │ DATA: 78% │
├────────────┼───────────┼───────────────────┤
│ CPU: 35% │ MEM: 72% │ Connections │
│ ███ │ █████ │ ██████ 45/100 │
└────────────┴───────────┴───────────────────┘ ┌──────────┐
│ Grafana │ ← 대시보드/시각화
└────┬─────┘
│ PromQL 쿼리
┌────┴─────┐
│Prometheus│ ← 메트릭 수집/저장
└────┬─────┘
┌─────────┼──────────┐
│ │ │
┌──────┴──┐ ┌────┴───┐ ┌───┴──────┐
│mysqld_ │ │postgres│ │oracle_db │
│exporter │ │exporter│ │exporter │
└────┬────┘ └───┬────┘ └────┬─────┘
│ │ │
┌────┴───┐ ┌────┴───┐ ┌────┴───┐
│ MySQL │ │ PgSQL │ │ Oracle │
└────────┘ └────────┘ └────────┘알림 설정 기준
| 항목 | 경고 (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초 이상 | 부하 분산, 네트워크 |
Level 1 — 자동 복구
* Connection Pool 부족 → 자동 확장
* 임시 테이블스페이스 부족 → 자동 정리
Level 2 — 알림 (DBA 확인)
* Slow Query 급증 → Slack/이메일 알림
* CPU 70% 초과 → 모니터링 강화
Level 3 — 긴급 대응 (즉시 조치)
* DB 장애/다운 → PagerDuty, 전화 알림
* 디스크 90% 초과 → 즉시 공간 확보
* Replication 중단 → Failover 준비장애 진단 프로세스
1단계: 현상 파악
→ "언제부터?" "어떤 증상?" "영향 범위?"
→ 메트릭 대시보드에서 이상 시점 확인
2단계: 리소스 확인
→ CPU, 메모리, 디스크 I/O 정상인지 확인
→ OS 수준: top, vmstat, iostat
→ 리소스 부족이면 → 스케일 업/아웃 검토
3단계: DB 내부 진단
→ Active Sessions 확인 (대기 이벤트)
→ Lock 경합 확인
→ Slow Query 확인
→ Oracle: AWR/ASH 분석
→ MySQL: Performance Schema, SHOW ENGINE INNODB STATUS
→ PostgreSQL: pg_stat_statements, pg_stat_activity
4단계: 원인 SQL 특정
→ 문제 SQL의 실행 계획 확인
→ 통계 정보 최신인지 확인
→ 인덱스 존재/활용 여부 확인
5단계: 조치
→ 쿼리 튜닝 (인덱스 추가, SQL 재작성)
→ 파라미터 조정 (메모리, 커넥션)
→ 구조 변경 (파티셔닝, 샤딩)
6단계: 검증 및 기록
→ 조치 후 메트릭 확인
→ 재발 방지를 위한 문서화패턴 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의 균형을 다루겠습니다.