LLM WikiAccess-protected knowledge portal
← 스터디 홈
8편 · 약 20분

MySQL 운영 대시보드: 핵심 지표와 알림 임계값

대시보드가 필요한 이유

MySQL 장애는 대부분 예고 없이 오지 않는다. 버퍼 풀 히트율이 서서히 떨어지고, Threads_running이 점점 쌓이고, 복제 지연이 수십 초로 늘어나다 연결 폭주로 이어지는 패턴이 반복된다. 대시보드는 이 전조 증상을 잡아내는 도구다.

운영 대시보드의 목표는 두 가지다.

  1. 이상 징후를 사람이 인지하기 전에 알림으로 전달한다.
  2. 장애 발생 시 근거 있는 진단을 위한 타임라인을 제공한다.

모니터링 스택 아키텍처

MySQL Primary / Replica SHOW STATUS perf_schema mysqld_exporter :9104/metrics scrape 15s Prometheus TSDB + Rules Grafana 대시보드 Alertmanager Slack / PD / 이메일 * collect.heartbeat 활성화 시 복제 지연도 수집
MySQL 모니터링 스택 아키텍처

Prometheus + mysqld_exporter 조합이 사실상 표준이다. mysqld_exporter는 SHOW GLOBAL STATUS, SHOW GLOBAL VARIABLES, SHOW REPLICA STATUS, performance_schema 등을 스크레이핑해 200개 이상의 메트릭을 Prometheus 텍스트 포맷으로 노출한다.


지표 범주와 핵심 항목

1. 연결 지표 (Connection)

연결 포화가 임박하면 새 연결이 Too many connections 오류로 거부된다. Threads_running은 포화도의 가장 민감한 신호다.

지표의미
Threads_connected현재 열린 연결 수
Threads_running실제로 쿼리 실행 중인 스레드 수
Max_used_connections서버 시작 이래 최대 동시 연결 수
Connection_errors_max_connections최대 연결 수 초과로 거부된 횟수 (0이어야 함)
-- 연결 상태 빠른 조회
SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME IN (
  'Threads_connected', 'Threads_running',
  'Max_used_connections', 'Connection_errors_max_connections'
);

-- 현재 연결 포화도 (%)
SELECT
  ROUND(
    (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Threads_connected')
    / (SELECT VARIABLE_VALUE FROM performance_schema.global_variables WHERE VARIABLE_NAME = 'max_connections')
    * 100, 1
  ) AS connection_saturation_pct;
# PromQL: 연결 포화도
mysql_global_status_threads_connected
  / mysql_global_variables_max_connections

# PromQL: 실행 중 스레드
mysql_global_status_threads_running

2. 쿼리 처리량 지표 (Query Throughput)

-- QPS 추이용 기준값 (snapshot 두 번 찍어서 차분 계산)
SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME IN (
  'Questions', 'Slow_queries',
  'Com_select', 'Com_insert', 'Com_update', 'Com_delete'
);
# PromQL: 초당 쿼리 수
rate(mysql_global_status_questions[5m])

# PromQL: 슬로우 쿼리 발생률
rate(mysql_global_status_slow_queries[5m])

# PromQL: 읽기/쓰기 비율
rate(mysql_global_status_commands_total{command="select"}[5m])
  / rate(mysql_global_status_questions[5m])

슬로우 쿼리의 rate가 평상시 대비 급증하면 인덱스 누락, 잠금 경합, 데이터 증가 등을 의심한다. long_query_time은 기본값 10초가 너무 관대하므로 1~2초로 낮춰 운영하는 것이 일반적이다.


3. InnoDB 버퍼 풀 지표 (Buffer Pool)

버퍼 풀 히트율은 MySQL 성능 지표 중 가장 직접적으로 인스턴스 건강도를 나타낸다.

히트율 공식: 1 − Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests

  • 99% 이상: 정상. 작업 세트가 메모리에 잘 올라와 있다.
  • 95~99%: 주의. 버퍼 풀 크기 증설 검토.
  • 95% 미만: 긴급. 디스크 I/O 급증, 쿼리 지연 심화 구간.
SELECT
  s1.VARIABLE_VALUE AS pool_reads,
  s2.VARIABLE_VALUE AS read_requests,
  ROUND(
    (1 - s1.VARIABLE_VALUE / s2.VARIABLE_VALUE) * 100, 2
  ) AS hit_rate_pct
FROM
  (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') s1,
  (SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests') s2;

-- 더티 페이지 비율 (< 75% 권장)
SELECT
  ROUND(
    pages_dirty / pages_data * 100, 1
  ) AS dirty_pct
FROM information_schema.INNODB_BUFFER_POOL_STATS;
# PromQL: 버퍼 풀 히트율
1 - rate(mysql_global_status_innodb_buffer_pool_reads[5m])
  / rate(mysql_global_status_innodb_buffer_pool_read_requests[5m])

# PromQL: 더티 페이지 비율
mysql_global_status_innodb_buffer_pool_pages_dirty
  / mysql_global_status_innodb_buffer_pool_pages_total

4. InnoDB Redo Log 지표

SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME IN (
  'Innodb_os_log_written',      -- redo log 쓰기 바이트 (rate)
  'Innodb_log_waits',           -- 로그 버퍼 부족 대기 (0이어야 함)
  'Innodb_log_write_requests',  -- 로그 쓰기 요청 수
  'Innodb_log_writes'           -- 실제 디스크 쓰기 수
);

Innodb_log_waits가 0이 아니면 innodb_log_buffer_size가 너무 작은 것이다. 기본값 16MB를 64MB 이상으로 늘리면 개선된다.


5. 잠금 지표 (Row Lock)

SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME IN (
  'Innodb_row_lock_current_waits',  -- 현재 대기 중인 행 잠금 수
  'Innodb_row_lock_time_avg',       -- 평균 대기 시간 (ms)
  'Innodb_row_lock_waits'           -- 잠금 대기 발생 누적 횟수
);

-- 현재 잠금 대기 세부 정보 (MySQL 8.0+)
SELECT
  r.trx_id AS waiting_trx,
  r.trx_mysql_thread_id AS waiting_thread,
  b.trx_id AS blocking_trx,
  b.trx_mysql_thread_id AS blocking_thread,
  b.trx_query AS blocking_query,
  TIMESTAMPDIFF(SECOND, r.trx_wait_started, NOW()) AS wait_seconds
FROM information_schema.INNODB_TRX r
JOIN information_schema.INNODB_TRX b
  ON r.trx_wait_started IS NOT NULL
  AND b.trx_id = (
    SELECT blocking_trx_id
    FROM performance_schema.data_lock_waits
    WHERE requesting_engine_transaction_id = r.trx_id
    LIMIT 1
  )
ORDER BY wait_seconds DESC;
# PromQL: 현재 행 잠금 대기 수
mysql_global_status_innodb_row_lock_current_waits

# PromQL: 평균 행 잠금 대기 시간 (ms)
mysql_global_status_innodb_row_lock_time_avg

6. 복제 지표 (Replication)

-- Replica에서 실행
SHOW REPLICA STATUS\G
-- 주요 항목:
-- Seconds_Behind_Source: 복제 지연 (초)
-- Replica_SQL_Running: Yes여야 함
-- Replica_IO_Running: Yes여야 함
-- Last_SQL_Errno: 0이어야 함
# PromQL: 복제 지연 (collect.heartbeat 또는 SHOW REPLICA STATUS 기반)
mysql_slave_status_seconds_behind_master

# 복제 스레드 실행 여부 (1=정상, 0=중단)
mysql_slave_status_slave_sql_running
mysql_slave_status_slave_io_running

collect.heartbeat를 활성화하면 binlog 이벤트 기반이 아닌 실제 헤드비트 쓰기/읽기 타임스탬프로 지연을 측정해 더 정확한 lag 값을 얻을 수 있다.


7. 임시 테이블 지표

SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM performance_schema.global_status
WHERE VARIABLE_NAME IN (
  'Created_tmp_tables',           -- 총 임시 테이블 생성 수
  'Created_tmp_disk_tables'       -- 디스크 임시 테이블 생성 수
);

-- 디스크 임시 테이블 비율 (< 10% 권장)
-- Created_tmp_disk_tables / Created_tmp_tables

디스크 임시 테이블이 많으면 tmp_table_sizemax_heap_table_size를 높이거나, 해당 쿼리에 인덱스를 추가한다.


알림 임계값 기준표

연결 · 스레드
연결 포화도
경고 > 80% / 긴급 > 90%
Threads_running
경고 > 30 / 긴급 > 50
Connection_errors_max_connections
0 초과 즉시 알림
버퍼 풀
히트율
경고 < 99% / 긴급 < 95%
더티 페이지 비율
경고 > 50% / 긴급 > 75%
Innodb_log_waits
> 0 즉시 알림
복제
복제 지연
경고 > 30초 / 긴급 > 300초
IO/SQL Thread 중단
0 = 즉시 알림
Last_SQL_Errno
0 이외 즉시 알림
잠금 · 느린 쿼리
row_lock_current_waits
경고 > 5 / 긴급 > 20
row_lock_time_avg
경고 > 50ms / 긴급 > 200ms
슬로우 쿼리 rate
평상시 2배 이상 급증
알림 임계값 기준표

대시보드 구성 권고안

운영 대시보드를 처음 만들 때 가장 흔한 실수는 패널을 너무 많이 넣는 것이다. 한 화면에 30개 패널은 아무것도 안 본 것과 같다. 권고 레이아웃은 5개 행으로 구성한다.

행 1: 가용성 요약 (신호등)
  - Primary 복제 스레드 상태 | Replica 지연 | Connection_errors_max_connections | Innodb_log_waits

행 2: 연결 · 처리량
  - Threads_running (time series) | 연결 포화도 (gauge) | QPS (time series)

행 3: InnoDB 버퍼 풀
  - 히트율 (time series + stat) | 더티 페이지 비율 | buffer pool 사용량

행 4: 잠금 · 느린 쿼리
  - row_lock_current_waits | row_lock_time_avg | 슬로우 쿼리 rate

행 5: 복제
  - Seconds_Behind_Source (time series, 복수 replica) | IO/SQL Thread 상태

Grafana ID 7362(MySQL Overview) 또는 ID 11323(MySQL Exporter Quickstart) 대시보드를 기본으로 불러온 후 위 패널 구조로 정리하면 빠르게 시작할 수 있다.


Alertmanager 규칙 예시

groups:
  - name: mysql
    rules:
      - alert: MySQLBufferPoolHitRateLow
        expr: |
          1 - rate(mysql_global_status_innodb_buffer_pool_reads[5m])
            / rate(mysql_global_status_innodb_buffer_pool_read_requests[5m]) < 0.95
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "MySQL 버퍼 풀 히트율 {{ $value | humanizePercentage }} — 디스크 I/O 급증 가능"

      - alert: MySQLConnectionSaturation
        expr: |
          mysql_global_status_threads_connected
            / mysql_global_variables_max_connections > 0.8
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "MySQL 연결 포화도 {{ $value | humanizePercentage }}"

      - alert: MySQLReplicationLagHigh
        expr: mysql_slave_status_seconds_behind_master > 30
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "MySQL 복제 지연 {{ $value }}초"

      - alert: MySQLReplicationLagCritical
        expr: mysql_slave_status_seconds_behind_master > 300
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "MySQL 복제 지연 {{ $value }}초 — 복제 상태 즉시 확인"

      - alert: MySQLRowLockWaitsHigh
        expr: mysql_global_status_innodb_row_lock_current_waits > 5
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "MySQL 행 잠금 대기 {{ $value }}건"

      - alert: MySQLInnoDBLogWaits
        expr: rate(mysql_global_status_innodb_log_waits[5m]) > 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "InnoDB 로그 버퍼 대기 발생 — innodb_log_buffer_size 증설 필요"

for 절을 두어 일시적 스파이크는 알림을 보내지 않도록 한다. 연결 포화도 같은 지표는 2분, 복제 지연은 1분으로 설정해 대응 속도와 알림 피로도의 균형을 잡는다.


References

  • Datadog Blog, "Monitoring MySQL performance metrics": https://www.datadoghq.com/blog/monitoring-mysql-performance-metrics/
  • Prometheus mysqld_exporter GitHub: https://github.com/prometheus/mysqld_exporter
  • Percona, "MySQL Performance Monitoring: Best Practices": https://www.percona.com/blog/mysql-performance-monitoring-best-practices/
  • Sysdig, "Top key metrics for monitoring MySQL": https://www.sysdig.com/blog/mysql-monitoring
  • MySQL 8.4 Reference Manual, "Performance Schema Status Variable Tables": https://dev.mysql.com/doc/refman/8.4/en/performance-schema-status-variable-tables.html
  • MySQL 8.4 Reference Manual, "InnoDB Metrics Table": https://dev.mysql.com/doc/refman/8.4/en/information-schema-innodb-metrics-table.html
  • Grafana Labs, "MySQL Query Performance Troubleshooting Dashboard": https://grafana.com/grafana/dashboards/12630-mysql-query-performance-troubleshooting/
  • oneuptime Blog, "How to Monitor MySQL InnoDB Buffer Pool, Slow Query Rate, and Thread State": https://oneuptime.com/blog/post/2026-02-06-mysql-innodb-buffer-pool-slow-query/view