안동민 개발노트 아이콘

안동민 개발노트

15장 : 실무 데이터베이스 운영

모니터링과 성능 진단

운영 중인 데이터베이스는 지속적으로 관찰해야 합니다. 모니터링의 목적은 문제가 발생한 뒤 숫자를 보는 것이 아니라, 평상시 기준선(Baseline)을 알고 있다가 변화가 생겼을 때 원인을 빠르게 좁히는 것입니다.

좋은 진단은 수치 → 대기 원인 → 문제 SQL → 실행 계획 → 조치 후 검증의 순서로 진행됩니다. CPU나 캐시 적중률 같은 단일 지표만으로 결론을 내리면 오진하기 쉽습니다.


핵심 메트릭

모니터링 지표는 “높다/낮다”보다 평소보다 어떻게 달라졌는지가 중요합니다. 같은 CPU 80%도 배치 시간에는 정상일 수 있고, 평소 20%이던 시스템에서는 장애 신호일 수 있습니다.

메트릭설명진단 관점
TPS / QPS초당 트랜잭션·쿼리 처리량급락하면 병목, 장애, 잠금 대기 가능성 확인
DB Time / Query LatencyDB 안에서 소비된 시간과 응답 지연사용자 지연과 가장 직접적으로 연결
Active SessionsCPU 사용 또는 비 idle 대기 중인 세션CPU 포화인지, 락/IO 대기인지 wait class로 분해
Slow Query기준 시간보다 긴 쿼리개별 장기 쿼리와 짧지만 빈번한 쿼리를 구분
Lock Wait잠금 때문에 대기하는 세션블로커, 트랜잭션 길이, 접근 순서 확인
Logical / Physical I/O버퍼 읽기와 실제 디스크 읽기캐시 문제인지, SQL 접근 경로 문제인지 구분
Storage / Tablespace저장 공간과 증가 속도절대 사용률보다 증가 추세와 자동 확장 여유 확인
Connection Poolactive, idle, pending, timeoutDB 병목인지 애플리케이션 점유 문제인지 분리
Replication Lag복제 지연 시간읽기 정합성, 장애 전환, 백업 부하 영향 확인

Oracle 성능 진단 도구

Oracle은 AWR, ASH, ADDM, V$ 동적 성능 뷰를 통해 워크로드와 대기 이벤트를 분석할 수 있습니다. 다만 AWR/ASH/ADDM과 관련 리포트·뷰는 환경에 따라 Oracle Diagnostics Pack 라이선스 대상이므로 운영에서 사용하기 전에 라이선스 범위를 확인해야 합니다.

AWR (Automatic Workload Repository)

AWR은 Oracle이 수집하는 성능 통계 스냅샷입니다. 기본적으로 1시간 간격으로 스냅샷을 만들고 8일 동안 보관합니다. 장애 분석에서는 단일 스냅샷보다 정상 구간과 문제 구간의 차이를 비교하는 것이 핵심입니다.

AWR 스냅샷 관리 (Oracle)
-- 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 섹션확인 내용관점
Load ProfileDB Time, 실행 수, 논리/물리 읽기전체 부하 변화
Top Timed Events가장 많은 시간을 소비한 대기 이벤트CPU, I/O, 락 병목 분리
SQL ordered by Elapsed Time총 소요 시간이 큰 SQL사용자 지연 후보
SQL ordered by CPU TimeCPU를 많이 쓴 SQL연산량·함수·조인 확인
SQL ordered by Gets논리 읽기가 많은 SQL인덱스·접근 경로 확인
SQL ordered by Executions실행 횟수가 많은 SQL캐싱, 배치, N+1 의심
Instance Activity Stats인스턴스 전반 통계평소 기준선과 비교
Segment Statistics특정 테이블/인덱스의 I/O와 경합객체 단위 병목 후보

ASH (Active Session History)

ASH는 활성 세션을 샘플링한 데이터입니다. Oracle은 CPU를 쓰거나 idle이 아닌 대기 이벤트에 걸린 세션을 활성 세션으로 보고, V$ACTIVE_SESSION_HISTORY에 1초 단위 샘플을 노출합니다. AWR이 구간 보고서라면 ASH는 특정 시점의 “무엇을 기다렸는가”를 좁히는 도구에 가깝습니다.

ASH 조회 (Oracle)
-- 최근 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, wait_class, COUNT(*) AS samples
FROM v$active_session_history
WHERE sql_id = 'abc123def456'
  AND sample_time > SYSDATE - 1/24
GROUP BY event, wait_class
ORDER BY samples DESC;

-- 블로킹 세션 후보
SELECT blocking_session, COUNT(*) AS samples
FROM v$active_session_history
WHERE blocking_session IS NOT NULL
  AND sample_time > SYSDATE - 10/1440
GROUP BY blocking_session
ORDER BY samples DESC;

ADDM (Automatic Database Diagnostic Monitor)

ADDM은 AWR 데이터를 바탕으로 문제 원인과 권고안을 생성합니다. 권고안은 출발점으로 보고, 실제 변경은 SQL, 실행 계획, 업무 시간대, 애플리케이션 배포 이력을 함께 확인한 뒤 적용해야 합니다.

ADDM 리포트 (Oracle)
-- 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 / NULLIF(cur.value + con.value, 0))) * 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

Performance Schema는 MySQL 서버 내부의 실행, 대기, I/O, 락 정보를 관찰하는 프레임워크입니다. events_statements_summary_by_digest는 비슷한 SQL을 정규화해 묶어 주므로, “한 번 아주 느린 쿼리”와 “짧지만 매우 자주 실행되는 쿼리”를 함께 찾는 데 유용합니다.

Performance Schema 활용 (MySQL)
-- Slow Query 상위 10개 (정규화된 digest 기준)
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;

-- MySQL 8.0.1+ InnoDB 락 대기
SELECT waiting_engine_transaction_id AS waiting_trx,
       blocking_engine_transaction_id AS blocking_trx
FROM performance_schema.data_lock_waits;

Slow Query Log

Slow Query Log는 지정한 시간보다 오래 걸린 쿼리를 파일 또는 테이블에 남깁니다. long_query_time은 워크로드 기준선에 맞춰 정하고, log_queries_not_using_indexes는 로그가 급증할 수 있으므로 짧은 진단 구간이나 throttle과 함께 쓰는 편이 안전합니다.

Slow Query 설정 (MySQL)
-- 현재 설정 확인
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;  -- 기준선에 맞게 조정
SET GLOBAL log_queries_not_using_indexes = 'ON';

-- 미인덱스 쿼리 로그가 폭증할 때는 throttle 검토
SET GLOBAL log_throttle_queries_not_using_indexes = 60;

-- InnoDB 상태 확인
SHOW ENGINE INNODB STATUS;
-- → TRANSACTIONS, SEMAPHORES, BUFFER POOL 섹션 확인

PostgreSQL 성능 진단 도구

PostgreSQL은 누적 통계 뷰와 확장 기능을 조합해 진단합니다. pg_stat_statements는 SQL별 planning/execution 통계를 모으는 핵심 확장이지만, shared_preload_libraries에 등록하고 서버를 재시작해야 활성화됩니다.

PostgreSQL 모니터링
-- postgresql.conf
-- shared_preload_libraries = '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;

-- 현재 실행 중인 세션
SELECT pid, usename, state, wait_event_type, wait_event,
       now() - query_start AS running_time,
       query
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY running_time DESC;

-- 테이블 통계 (순차 스캔 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
    sum(heap_blks_hit) * 100.0 /
    NULLIF(sum(heap_blks_hit) + sum(heap_blks_read), 0) AS cache_hit_ratio
FROM pg_statio_user_tables;

-- VACUUM 필요성 확인
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;

-- 실행 계획 확인: DML은 트랜잭션 안에서 롤백하며 확인
BEGIN;
EXPLAIN (ANALYZE, BUFFERS) SELECT ...;
ROLLBACK;

외부 모니터링 도구

도구특징적합한 환경
Oracle Enterprise Manager (OEM)Oracle 공식 GUI, 종합 관리Oracle 전용
Grafana + Prometheus오픈소스, 대시보드 커스터마이징범용
DatadogSaaS, 다양한 DB 지원, APM 통합클라우드·혼합 환경
Zabbix오픈소스, 알림 설정, 에이전트 기반온프레미스
PMM (Percona Monitoring)MySQL/PostgreSQL 전문 대시보드MySQL/PostgreSQL
CloudWatchRDS/Aurora 지표와 로그 연계AWS

모니터링 체계는 보통 수집 → 저장 → 시각화 → 알림 → 대응 기록으로 구성합니다. 대시보드는 한 화면에 모든 지표를 넣기보다, 증상별로 “사용자 지연”, “DB 내부 대기”, “문제 SQL”, “리소스”를 빠르게 이동할 수 있게 나누는 편이 좋습니다.


알림 설정 기준

알림 기준은 고정 숫자보다 기준선과 변화율을 함께 써야 합니다. 아래 값은 예시 기준이며, 실제 운영에서는 업무 시간대, 배치 시간, 스케일 정책, 복제 구조에 맞게 조정합니다.

항목경고 예시위험 예시조치
CPU 사용률평소보다 30% 이상 상승포화 상태가 지속SQL·배치·스케줄러·스케일 정책 확인
메모리 사용률여유 메모리 급감swap 또는 OOM 위험버퍼/작업 메모리, 누수, 연결 수 확인
디스크 사용률증가 속도 급증자동 확장 불가 또는 90% 근접공간 확보, 보관 정책, 파티셔닝 검토
Active Sessions평소 상위 분위수 초과CPU 수 대비 과도하거나 대기 급증wait class, 락, SQL별 DB Time 확인
Slow Query기준선 대비 건수·시간 증가사용자 지연과 timeout 동반digest/plan/배포 변경점 확인
Lock Wait블로커가 반복 출현장기 트랜잭션이 서비스 지연 유발트랜잭션 범위 축소, 접근 순서 점검
Cache Hit Ratio평소 대비 급락물리 읽기와 latency 동시 증가SQL 접근 경로, 버퍼, working set 확인
Replication Lag읽기 지연 기준 초과장애 전환·정합성 위험복제 부하, 네트워크, 장기 트랜잭션 확인

장애 진단 프로세스

자주 발생하는 성능 문제 패턴
패턴 1: 갑작스러운 Slow Query 증가
  → 원인: 통계 정보 변화, 데이터 분포 변화, 실행 계획 변경
  → 조치: 실행 계획과 실제 row 수 비교, 통계 갱신, 인덱스 검토

패턴 2: 특정 시간대 성능 저하
  → 원인: 배치 작업과 온라인 작업 충돌
  → 조치: 배치 시간 조정, 리소스 분리, 동시 실행 수 제한

패턴 3: 점진적 성능 저하
  → 원인: 데이터 증가, bloat, working set 증가
  → 조치: 파티셔닝, 보관 정책, vacuum/analyze, SQL 재검토

패턴 4: Lock 대기 급증
  → 원인: 긴 트랜잭션, 잘못된 갱신 순서, 대량 DML
  → 조치: 블로커 특정, 트랜잭션 범위 축소, 접근 순서 통일

패턴 5: Connection 부족
  → 원인: 커넥션 풀 설정 부적절, 커넥션 누수, 긴 점유 시간
  → 조치: active/idle/pending 분리 확인, 리크 후보와 slow query 확인

DBMS별 핵심 진단 도구 비교

기능OracleMySQLPostgreSQL
워크로드 기록AWRPerformance Schema digestpg_stat_statements
실시간 세션ASH, v$sessionPerformance Schema, PROCESSLISTpg_stat_activity
자동 진단ADDM제한적, 외부 도구 연계제한적, 확장/외부 도구 연계
Slow QueryAWR SQL 섹션Slow Query Loglog_min_duration_statement
락 모니터링v$lock, v$sessionperformance_schema.data_lockspg_locks
실행 계획 확인EXPLAIN PLAN, DBMS_XPLANEXPLAIN, EXPLAIN ANALYZEEXPLAIN (ANALYZE, BUFFERS)
통계 수집DBMS_STATSANALYZE TABLEANALYZE, VACUUM ANALYZE
공간 관리DBA_TABLESPACEinformation_schema, sys schemapg_stat_user_tables

시험에서는 Oracle AWR 리포트의 핵심 확인 항목(Top Timed Events, SQL Elapsed Time), ASH가 활성 세션을 1초 단위로 샘플링한다는 점, Buffer Cache Hit Ratio 계산식이 자주 출제됩니다. 다만 실무에서는 캐시 적중률 하나로 결론을 내리지 않고, DB Time, wait event, 문제 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

정리

데이터베이스 모니터링의 핵심은 기준선 확보변화의 원인 분해입니다. 평소의 TPS, latency, active sessions, lock wait, pool pending, replication lag를 알고 있어야 장애 시 현재 수치가 정상인지 비정상인지 판단할 수 있습니다.

각 DBMS는 고유한 진단 도구를 제공합니다. Oracle의 AWR/ASH/ADDM, MySQL의 Performance Schema와 Slow Query Log, PostgreSQL의 pg_stat_statementspg_stat_activity가 대표적입니다. 도구 이름보다 중요한 것은 “어떤 사용자가 느린가 → DB 안에서 무엇을 기다리는가 → 어떤 SQL과 실행 계획이 원인인가 → 조치 후 나아졌는가”의 흐름입니다.

다음 절에서는 애플리케이션과 DB 사이의 ORM과 SQL의 균형을 다루겠습니다.