MySQL sys 스키마: 운영자가 바로 쓰는 진단 뷰
MySQL sys 스키마의 구성 원리와 핵심 진단 뷰를 성능, 인덱스, 잠금, 메모리 관점에서 운영 절차와 함께 정리한다.
MySQL 장애 대응에서 어려운 점은 상태 정보를 얻을 수 없다는 데 있지 않다. 오히려 performance_schema와 information_schema에 있는 수많은 테이블을 어떤 순서로 조합하고 해석해야 하는지가 문제다. sys 스키마는 이 간극을 줄이기 위해 만들어진 진단 계층이다. 자주 필요한 조인과 단위 변환을 뷰, 함수, 프로시저로 제공하여 운영자가 원시 계측 데이터를 빠르게 읽을 수 있게 한다.
그러나 sys 스키마를 독립된 모니터링 저장소로 이해하면 안 된다. 대부분의 값은 performance_schema 또는 information_schema에서 가져온 현재 상태와 누적 통계를 보기 좋게 표현한 것이다. 원본 계측이 비활성화되어 있거나 서버가 재시작되어 누적값이 초기화되면 sys의 결과도 비거나 달라진다. 이 글에서는 이러한 내부 구조를 먼저 설명한 뒤, 운영 현장에서 유용한 뷰를 성능·인덱스·잠금·메모리 영역으로 나누어 살펴본다.
1. sys 스키마의 위치와 역할
sys는 MySQL 5.7부터 기본 배포에 포함되었고 MySQL 8.0에서도 서버 설치 시 함께 제공된다. 핵심 목적은 다음 세 가지다.
- 복잡한
performance_schema조인을 재사용 가능한 뷰로 제공한다. - 피코초 단위 지연 시간과 바이트 단위 용량을 사람이 읽기 쉬운 문자열로 변환한다.
- 진단 설정, 세션 추적, 계측 제어에 필요한 함수와 프로시저를 제공한다.
flowchart LR
A[MySQL 실행 경로] --> B[Performance Schema 계측]
A --> C[Information Schema 메타데이터]
B --> D[sys 원시형 x$ 뷰]
C --> D
D --> E[sys 사람이 읽는 뷰]
E --> F[DBA 진단 SQL]
E --> G[점검 스크립트와 대시보드]
sys의 일반 뷰는 format_time(), format_bytes() 같은 함수를 사용해 2.34 s, 128.00 MiB처럼 읽기 쉬운 값을 보여준다. 같은 이름 앞에 x$가 붙은 뷰는 대체로 원시 숫자를 유지한다. 예를 들어 statement_analysis는 사람이 읽는 보고서에 적합하고, x$statement_analysis는 숫자 정렬·임계값 비교·시계열 수집에 더 적합하다.
따라서 다음 원칙이 유용하다.
- 터미널에서 즉시 확인할 때는 일반 뷰를 우선한다.
- 자동화된 수집과 계산에는
x$뷰 또는 원본performance_schema를 사용한다. - 결과가 예상과 다르면 원본 계측 테이블과 consumer 설정까지 내려가 확인한다.
sys결과는 원인 확정이 아니라 조사 범위를 줄이는 출발점으로 사용한다.
2. 설치 상태와 관측 기반 확인
먼저 서버 버전, sys 스키마 존재 여부, 이 글에서 사용할 핵심 뷰의 존재 여부를 확인한다. 관리형 서비스나 오래된 업그레이드 이력이 있는 서버에서는 구성 요소가 표준 설치와 다를 수 있으므로, 이름을 가정하기보다 실제 메타데이터를 확인하는 편이 안전하다.
SELECT VERSION() AS mysql_version;
SELECT SCHEMA_NAME
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME = 'sys';
SELECT TABLE_NAME
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = 'sys'
AND TABLE_NAME IN (
'statement_analysis',
'schema_table_statistics',
'schema_redundant_indexes',
'innodb_lock_waits',
'memory_global_total',
'metrics'
)
ORDER BY TABLE_NAME;
실행 결과(MySQL 8.0.x):
mysql> SELECT VERSION() AS mysql_version;
+---------------+
| mysql_version |
+---------------+
| 8.0.46 |
+---------------+
1 row in set (0.00 sec)
mysql> SELECT SCHEMA_NAME
-> FROM information_schema.SCHEMATA
-> WHERE SCHEMA_NAME = 'sys';
+-------------+
| SCHEMA_NAME |
+-------------+
| sys |
+-------------+
1 row in set (0.00 sec)
mysql> SELECT TABLE_NAME
-> FROM information_schema.VIEWS
-> WHERE TABLE_SCHEMA = 'sys'
-> AND TABLE_NAME IN (
-> 'statement_analysis',
-> 'schema_table_statistics',
-> 'schema_redundant_indexes',
-> 'innodb_lock_waits',
-> 'memory_global_total',
-> 'metrics'
-> )
-> ORDER BY TABLE_NAME;
+--------------------------+
| TABLE_NAME |
+--------------------------+
| innodb_lock_waits |
| memory_global_total |
| metrics |
| schema_redundant_indexes |
| schema_table_statistics |
| statement_analysis |
+--------------------------+
6 rows in set (0.00 sec)
sys가 존재하지만 특정 뷰가 보이지 않는다면 서버 버전과 배포판을 먼저 확인한다. 뷰 정의만 임의로 복사해 복구하는 작업은 원본 performance_schema 테이블과 함수의 버전 불일치를 만들 수 있다. 패키지 또는 관리형 서비스가 제공하는 공식 업그레이드 절차를 우선해야 한다.
Performance Schema가 비어 있으면 sys도 비어 있다
sys는 계측기를 대신 켜 주는 저장소가 아니다. 다음 쿼리는 핵심 consumer의 활성 상태를 확인한다.
SELECT NAME, ENABLED
FROM performance_schema.setup_consumers
WHERE NAME IN (
'events_statements_current',
'events_statements_history',
'events_statements_history_long',
'global_instrumentation',
'thread_instrumentation'
)
ORDER BY NAME;
실행 결과(MySQL 8.0.x):
mysql> SELECT NAME, ENABLED
-> FROM performance_schema.setup_consumers
-> WHERE NAME IN (
-> 'events_statements_current',
-> 'events_statements_history',
-> 'events_statements_history_long',
-> 'global_instrumentation',
-> 'thread_instrumentation'
-> )
-> ORDER BY NAME;
+--------------------------------+---------+
| NAME | ENABLED |
+--------------------------------+---------+
| events_statements_current | YES |
| events_statements_history | YES |
| events_statements_history_long | NO |
| global_instrumentation | YES |
| thread_instrumentation | YES |
+--------------------------------+---------+
5 rows in set (0.00 sec)
statement_analysis 같은 요약 뷰는 statement digest 계측에 의존한다. 세부 이력 consumer가 꺼져 있어도 digest 요약은 수집될 수 있지만, 현재 실행문이나 개별 세션의 과거 문장을 추적하려면 관련 consumer가 필요하다. 계측을 켜는 작업은 메모리와 CPU 비용을 추가할 수 있으므로, 운영 서버에서는 “보이지 않으니 모두 활성화”하는 방식보다 조사 목적과 보존 범위를 정한 뒤 변경해야 한다.
3. 문장 성능: statement_analysis
sys.statement_analysis는 performance_schema.events_statements_summary_by_digest를 기반으로 정규화된 SQL digest별 실행 횟수, 총 지연 시간, 평균 처리 행 수, full scan 여부를 보여준다. 애플리케이션 SQL의 우선순위를 정할 때 가장 먼저 볼 수 있는 뷰다.
다음 쿼리는 총 지연 시간이 큰 문장 상위 10개를 조회한다.
SELECT db,
exec_count,
total_latency,
avg_latency,
rows_sent_avg,
rows_examined_avg,
full_scan,
LEFT(query, 120) AS query_sample
FROM sys.statement_analysis
ORDER BY total_latency DESC
LIMIT 10;
실행 결과(MySQL 8.0.x):
다음은 검증 시점의 출력에서 핵심 열과 일부 행만 발췌한 결과다. 누적 시간과 실행 횟수는 서버의 관측 구간에 따라 달라진다.
mysql> SELECT db, exec_count, total_latency, avg_latency,
-> rows_sent_avg, rows_examined_avg, full_scan,
-> LEFT(query, 120) AS query_sample
-> FROM sys.statement_analysis
-> ORDER BY total_latency DESC
-> LIMIT 10;
+-----------------+------------+---------------+-------------+---------------+-------------------+------------------------------+
| db | exec_count | total_latency | avg_latency | rows_sent_avg | rows_examined_avg | query_sample |
+-----------------+------------+---------------+-------------+---------------+-------------------+------------------------------+
| NULL | 3 | 692.88 us | 230.96 us | 1 | 1 | SELECT `VERSION` ( ) |
| mysql_tech_note | 1 | 2.76 ms | 2.76 ms | 1 | 4 | SELECT SCHEMA_NAME FROM ... |
| mysql_tech_note | 1 | 1.55 ms | 1.55 ms | 5 | 10 | SELECT NAME, `ENABLED` ... |
+-----------------+------------+---------------+-------------+---------------+-------------------+------------------------------+
결과를 해석할 때는 한 열만 보지 않는다.
total_latency가 크고exec_count도 크면 호출 빈도가 높은 누적 비용 문제일 수 있다.avg_latency가 큰데 실행 횟수가 적으면 배치, 리포트, 잠금 대기 또는 간헐적 비효율을 의심한다.rows_examined_avg가rows_sent_avg보다 훨씬 크면 선택도, 인덱스, 불필요한 조인, 필터 위치를 점검한다.full_scan은 경고 신호이지 즉시 장애 판정은 아니다. 작은 기준 테이블의 full scan은 인덱스 접근보다 저렴할 수 있다.- digest는 리터럴을 정규화하므로 서로 다른 사용자 입력이 같은 행으로 합쳐질 수 있다.
누적값은 서버 시작 이후 또는 요약 테이블 초기화 이후의 관측 구간을 반영한다. 배포 전후를 비교하려면 조회 시각, 서버 uptime, 통계 초기화 시점을 함께 기록해야 한다. 단순히 오늘의 상위 10개와 어제의 상위 10개를 비교하면 관측 구간이 달라 잘못된 결론을 내릴 수 있다.
4. 테이블 I/O: schema_table_statistics
쿼리 digest가 “어떤 SQL이 비싼가”에 답한다면 schema_table_statistics는 “어떤 테이블에 I/O와 변경이 집중되는가”를 보여준다. 읽기·쓰기 지연 시간과 행 처리량을 테이블 단위로 집계하므로, hot table 탐색과 용량·샤딩 검토의 출발점이 된다.
SELECT table_schema,
table_name,
total_latency,
rows_fetched,
fetch_latency,
rows_inserted,
rows_updated,
rows_deleted,
io_read,
io_write
FROM sys.schema_table_statistics
WHERE table_schema NOT IN ('mysql', 'sys', 'performance_schema', 'information_schema')
ORDER BY total_latency DESC
LIMIT 10;
실행 결과(MySQL 8.0.x):
mysql> SELECT table_schema,
-> table_name,
-> total_latency,
-> rows_fetched,
-> fetch_latency,
-> rows_inserted,
-> rows_updated,
-> rows_deleted,
-> io_read,
-> io_write
-> FROM sys.schema_table_statistics
-> WHERE table_schema NOT IN ('mysql', 'sys', 'performance_schema', 'information_schema')
-> ORDER BY total_latency DESC
-> LIMIT 10;
Empty set (0.01 sec)
rows_fetched는 애플리케이션이 최종 반환받은 행 수와 같지 않다. 스토리지 엔진과 서버 계층 사이에서 계측된 테이블 접근량으로 이해해야 한다. 또한 높은 io_read가 곧 물리 디스크 병목을 뜻하지는 않는다. Performance Schema의 file I/O, 운영체제 지표, InnoDB Buffer Pool hit ratio, 스토리지 지연 시간을 함께 확인해야 한다.
다음과 같은 상관관계를 찾는 데 유용하다.
- 특정 테이블의
rows_updated와io_write가 함께 증가하는가? - 읽기 지연이 큰 테이블이 statement digest의 상위 SQL과 연결되는가?
- 예상보다
rows_fetched가 큰 테이블에 비효율적인 range scan 또는 full scan이 있는가? - 테이블 자체보다 인덱스별 통계가 필요한가? 이때는
schema_index_statistics로 내려간다.
5. 중복 인덱스: schema_redundant_indexes
sys.schema_redundant_indexes는 한 인덱스의 선두 열 구성이 다른 인덱스에 포함되는 경우를 찾아낸다. 중복 인덱스는 읽기 경로를 거의 늘리지 않으면서 INSERT, UPDATE, DELETE, purge, redo/undo, Buffer Pool 사용량을 증가시킬 수 있다.
다음 예제는 (customer_id) 인덱스가 (customer_id, created_at)의 왼쪽 접두부와 겹치는 상황을 재현한다.
DROP TABLE IF EXISTS sys_note_orders;
CREATE TABLE sys_note_orders (
order_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
customer_id BIGINT UNSIGNED NOT NULL,
created_at DATETIME NOT NULL,
amount DECIMAL(12,2) NOT NULL,
PRIMARY KEY (order_id),
KEY idx_customer (customer_id),
KEY idx_customer_created (customer_id, created_at)
) ENGINE = InnoDB;
INSERT INTO sys_note_orders (customer_id, created_at, amount) VALUES
(101, '2026-08-16 08:00:00', 12000.00),
(101, '2026-08-16 08:05:00', 18000.00),
(202, '2026-08-16 08:10:00', 7500.00);
SELECT table_schema,
table_name,
redundant_index_name,
dominant_index_name
FROM sys.schema_redundant_indexes
WHERE table_schema = DATABASE()
AND table_name = 'sys_note_orders';
ALTER TABLE sys_note_orders
ALTER INDEX idx_customer INVISIBLE;
SELECT INDEX_NAME, IS_VISIBLE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'sys_note_orders'
GROUP BY INDEX_NAME, IS_VISIBLE
ORDER BY INDEX_NAME;
DROP TABLE sys_note_orders;
실행 결과(MySQL 8.0.x):
다음은 검증된 전체 실행에서 긴 DDL 정의와 입력값을 줄이고, DDL/DML 성공 여부와 진단 결과를 보존한 발췌다.
mysql> CREATE TABLE sys_note_orders (...);
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO sys_note_orders (...) VALUES (...), (...), (...);
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> SELECT table_schema, table_name,
-> redundant_index_name, dominant_index_name
-> FROM sys.schema_redundant_indexes
-> WHERE table_schema = DATABASE()
-> AND table_name = 'sys_note_orders';
+-----------------+-----------------+----------------------+----------------------+
| table_schema | table_name | redundant_index_name | dominant_index_name |
+-----------------+-----------------+----------------------+----------------------+
| mysql_tech_note | sys_note_orders | idx_customer | idx_customer_created |
+-----------------+-----------------+----------------------+----------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE sys_note_orders ALTER INDEX idx_customer INVISIBLE;
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> SELECT INDEX_NAME, IS_VISIBLE ...;
+----------------------+------------+
| INDEX_NAME | IS_VISIBLE |
+----------------------+------------+
| idx_customer | NO |
| idx_customer_created | YES |
| PRIMARY | YES |
+----------------------+------------+
3 rows in set (0.00 sec)
mysql> DROP TABLE sys_note_orders;
Query OK, 0 rows affected (0.00 sec)
이 뷰의 결과를 곧바로 DROP INDEX 목록으로 사용해서는 안 된다. 외래 키 지원, unique 제약, 인덱스 prefix 길이, 정렬 방향, covering 효과, 힌트 의존 SQL, 통계 변동을 확인해야 한다. 안전한 절차는 다음과 같다.
- 중복 후보를 찾는다.
information_schema.STATISTICS에서 열 순서와 속성을 확인한다.schema_index_statistics와 실제 쿼리 계획에서 사용 여부를 조사한다.- 후보 인덱스를 Invisible로 전환한다.
- 대표 SQL의
EXPLAIN, 지연 시간, 오류율을 관찰한다. - 충분한 검증 후 삭제하고, 문제가 있으면 즉시 Visible로 복구한다.
Invisible index도 쓰기 시 유지되므로 전환만으로 쓰기 비용이 줄지는 않는다. Invisible 단계의 목적은 optimizer가 해당 인덱스를 사용하지 않을 때의 읽기 영향을 검증하는 것이다.
6. 잠금 대기: innodb_lock_waits
sys.innodb_lock_waits는 performance_schema.data_lock_waits, 잠금 정보, 세션 정보를 결합해 대기 트랜잭션과 차단 트랜잭션을 연결한다. 장애 대응에서는 단순히 “대기가 있다”보다 “누가 누구를 막고 있고, 각각 어떤 SQL과 트랜잭션 상태를 갖는가”가 중요하다.
SELECT wait_started,
wait_age,
locked_table,
locked_index,
waiting_pid,
blocking_pid,
waiting_query,
blocking_query
FROM sys.innodb_lock_waits
ORDER BY wait_age_secs DESC
LIMIT 10;
실행 결과(MySQL 8.0.x):
mysql> SELECT wait_started,
-> wait_age,
-> locked_table,
-> locked_index,
-> waiting_pid,
-> blocking_pid,
-> waiting_query,
-> blocking_query
-> FROM sys.innodb_lock_waits
-> ORDER BY wait_age_secs DESC
-> LIMIT 10;
Empty set (0.00 sec)
단일 검증 인스턴스에 실제 잠금 대기가 없으면 이 쿼리는 Empty set을 반환한다. 이는 진단 실패가 아니라 관측 시점에 wait-for 관계가 없다는 뜻이다. 잠금 문제가 간헐적이라면 주기적인 snapshot 수집, 애플리케이션 timeout 로그, performance_schema.data_lock_waits 관측을 함께 구성해야 한다.
차단 세션을 발견해도 다음 확인 없이 즉시 KILL하지 않는다.
- 차단 세션이 실제로 업무 트랜잭션을 수행 중인지, 유휴 상태로 열린 트랜잭션인지 확인한다.
- 변경 행 수와 트랜잭션 시작 시각을 확인한다.
- rollback 예상 시간과 서비스 영향을 평가한다.
- 대기 SQL이 idempotent하게 재시도될 수 있는지 확인한다.
- DDL, metadata lock, row lock 가운데 어떤 자원 경합인지 구분한다.
sys.innodb_lock_waits는 InnoDB 데이터 잠금 대기를 중심으로 한다. metadata lock은 schema_table_lock_waits 및 performance_schema.metadata_locks를 별도로 확인해야 한다.
7. 메모리와 핵심 지표: memory_global_total, metrics
sys.memory_global_total은 Performance Schema가 계측한 현재 메모리 사용량의 합계를 읽기 쉬운 형태로 보여준다.
SELECT total_allocated
FROM sys.memory_global_total;
SELECT Variable_name,
Variable_value,
Type,
Enabled
FROM sys.metrics
WHERE Variable_name IN (
'Threads_connected',
'Threads_running',
'Innodb_buffer_pool_reads',
'Innodb_buffer_pool_read_requests'
)
ORDER BY Variable_name;
실행 결과(MySQL 8.0.x):
mysql> SELECT total_allocated
-> FROM sys.memory_global_total;
+-----------------+
| total_allocated |
+-----------------+
| 382.53 MiB |
+-----------------+
1 row in set (0.00 sec)
mysql> SELECT Variable_name,
-> Variable_value,
-> Type,
-> Enabled
-> FROM sys.metrics
-> WHERE Variable_name IN (
-> 'Threads_connected',
-> 'Threads_running',
-> 'Innodb_buffer_pool_reads',
-> 'Innodb_buffer_pool_read_requests'
-> )
-> ORDER BY Variable_name;
+----------------------------------+----------------+---------------+---------+
| Variable_name | Variable_value | Type | Enabled |
+----------------------------------+----------------+---------------+---------+
| innodb_buffer_pool_read_requests | 21462 | Global Status | YES |
| innodb_buffer_pool_reads | 1035 | Global Status | YES |
| threads_connected | 1 | Global Status | YES |
| threads_running | 2 | Global Status | YES |
+----------------------------------+----------------+---------------+---------+
4 rows in set (0.01 sec)
sys.metrics는 global status, InnoDB metrics, Performance Schema 메모리 계측 등 서로 다른 출처의 값을 한 인터페이스로 모아 빠른 확인에 유용하다. 하지만 모든 행의 의미와 단위가 동일하지는 않다. Type과 원본 변수의 정의를 확인하고, counter와 gauge를 구분해야 한다.
memory_global_total도 운영체제에서 본 mysqld RSS와 같지 않다. 다음 항목은 서로 다른 관측값이다.
- Performance Schema가 계측하는 MySQL 내부 할당량
- allocator가 프로세스에 보유한 메모리
- 운영체제가 보고하는 RSS와 virtual memory
- Buffer Pool, connection buffer, thread stack, mmap 영역
- 컨테이너 또는 cgroup의 memory current와 limit
따라서 메모리 장애를 진단할 때는 memory_global_by_current_bytes, memory_by_thread_by_current_bytes, 연결 수, Buffer Pool 설정, 운영체제/cgroup 지표를 함께 본다. “sys 합계가 작으므로 OOM 위험이 없다”는 결론은 성립하지 않는다.
8. 사람이 읽는 뷰와 x$ 뷰 선택
일반 뷰와 x$ 뷰는 같은 목적을 다른 표현으로 제공한다. 자동화에서는 숫자 단위를 유지하는 것이 중요하다. 예를 들어 문자열 9.80 ms와 1.20 s를 사전식으로 정렬하면 실제 크기 순서를 보장할 수 없다.
SELECT TABLE_NAME
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = 'sys'
AND TABLE_NAME IN (
'statement_analysis',
'x$statement_analysis',
'schema_table_statistics',
'x$schema_table_statistics'
)
ORDER BY TABLE_NAME;
실행 결과(MySQL 8.0.x):
mysql> SELECT TABLE_NAME
-> FROM information_schema.VIEWS
-> WHERE TABLE_SCHEMA = 'sys'
-> AND TABLE_NAME IN (
-> 'statement_analysis',
-> 'x$statement_analysis',
-> 'schema_table_statistics',
-> 'x$schema_table_statistics'
-> )
-> ORDER BY TABLE_NAME;
+---------------------------+
| TABLE_NAME |
+---------------------------+
| schema_table_statistics |
| statement_analysis |
| x$schema_table_statistics |
| x$statement_analysis |
+---------------------------+
4 rows in set (0.00 sec)
사용 기준은 명확하다.
| 목적 | 권장 인터페이스 | 이유 |
|---|---|---|
| 장애 대응 중 사람이 즉시 읽기 | 일반 sys 뷰 |
시간·용량 단위가 변환되어 있다. |
| 임계값 비교와 정렬 | x$ 뷰 |
원시 숫자를 안전하게 계산할 수 있다. |
| 장기 시계열 수집 | 원본 Performance Schema 또는 x$ |
단위와 counter semantics를 명시적으로 관리할 수 있다. |
| 원인 분석과 계측 검증 | 원본 Performance Schema | instrument, consumer, source table까지 추적할 수 있다. |
sys 일반 뷰를 그대로 모니터링 exporter 입력으로 사용하면 편리하지만, 문자열 파싱과 버전별 형식 차이에 취약해질 수 있다. 수집기에는 원시 숫자와 단위를 저장하고, 표시 계층에서 사람이 읽기 좋은 단위로 바꾸는 것이 안전하다.
9. 운영 조사 순서
sys의 강점은 단일 만능 쿼리가 아니라 조사 경로를 빠르게 연결한다는 데 있다. 성능 저하를 조사할 때 다음 흐름을 사용할 수 있다.
flowchart TD
A[증상 확인: 지연 시간·오류율·처리량] --> B[sys.metrics와 processlist로 서버 상태 확인]
B --> C{잠금 대기가 있는가?}
C -- 예 --> D[innodb_lock_waits와 schema_table_lock_waits]
C -- 아니오 --> E[statement_analysis로 고비용 digest 식별]
E --> F[schema_table_statistics와 schema_index_statistics]
F --> G[EXPLAIN ANALYZE와 원본 Performance Schema 확인]
D --> H[트랜잭션 수명·rollback 영향·재시도성 평가]
G --> I[인덱스·SQL·통계·용량 개선]
H --> I
I --> J[변경 전후 동일 관측 구간 비교]
이 흐름의 핵심은 서버 전체 → SQL digest → 테이블/인덱스 → 실행 계획 순으로 범위를 좁히는 것이다. 처음부터 가장 복잡한 SQL에 매달리거나, CPU가 높다는 이유만으로 인덱스를 추가하면 원인과 무관한 변경을 만들 수 있다.
10. Aurora MySQL에서의 해석
Aurora MySQL도 MySQL 호환 performance_schema와 sys를 제공하지만 운영 경계가 다르다.
첫째, sys와 Performance Schema 통계는 기본적으로 접속한 DB 인스턴스의 실행 상태를 반영한다. writer와 reader는 서로 다른 프로세스이므로 reader의 느린 조회를 writer의 statement_analysis에서 찾을 수 있다고 가정하면 안 된다. cluster endpoint가 아닌 실제 인스턴스별로 관측해야 한다.
둘째, Performance Schema 활성화와 세부 계측 설정은 DB parameter group 및 엔진 버전의 영향을 받는다. 변경에 재부팅이 필요한 파라미터인지, 즉시 적용 가능한 consumer인지 구분해야 한다.
셋째, Aurora의 분산 스토리지 계층과 복제 구조는 Community MySQL의 로컬 InnoDB 파일 I/O와 다르다. sys의 table I/O와 statement latency는 여전히 SQL 병목을 찾는 데 유용하지만, 스토리지 지연·replica lag·failover 원인을 완전히 설명하지는 못한다. CloudWatch, Performance Insights 또는 Database Insights, Enhanced Monitoring, Aurora 전용 wait event를 함께 사용해야 한다.
넷째, 관리형 서비스에서는 KILL, 계측 변경, 진단 프로시저 실행 권한이 제한될 수 있다. 운영 runbook은 “쿼리 복사 후 실행”이 아니라 필요한 권한, 대상 인스턴스, 파라미터 적용 범위까지 포함해야 한다.
11. 흔한 오해와 주의점
11.1 결과가 없으면 문제가 없다는 뜻이다
아니다. 계측이 꺼져 있거나, 요약이 초기화되었거나, 짧은 이벤트가 관측 사이에 끝났을 수 있다. setup_instruments, setup_consumers, 서버 uptime, 통계 초기화 이력을 확인한다.
11.2 누적값 상위 항목이 현재 장애 원인이다
누적 total_latency는 과거의 고비용을 포함한다. 현재 장애와 연결하려면 짧은 구간의 delta, 현재 세션, 애플리케이션 지표를 함께 본다.
11.3 unused 또는 redundant 인덱스는 즉시 삭제해도 된다
관측 구간에 실행되지 않은 월말 배치나 장애 복구 SQL이 있을 수 있다. 중복 후보도 unique, foreign key, covering, 정렬 특성이 다를 수 있다. Invisible index 검증과 충분한 관측 기간이 필요하다.
11.4 sys의 formatted 값은 자동화에도 편리하다
표시는 편하지만 수치 계산에는 부적합하다. 자동화는 x$ 또는 원본 숫자를 사용하고 단위를 명시한다.
11.5 sys만 보면 원인이 확정된다
sys는 집계와 상관관계를 제공한다. 실행 계획, 트랜잭션 경계, 애플리케이션 timeout, 운영체제와 스토리지 지표를 결합해야 원인에 도달할 수 있다.
12. 운영 체크리스트
사전 준비
- 대상 MySQL 버전과
sys
장애 조사
-
metrics,processlist -
statement_analysis -
rows_examined_avg,rows_sent_avg,full_scan - 테이블·인덱스 통계와 대표 SQL의
EXPLAIN ANALYZE
변경과 사후 검증
- 자동 수집에는 일반 뷰의 formatted 문자열보다
x$
맺음말
sys 스키마는 MySQL의 새로운 계측 저장소가 아니라 복잡한 원시 관측 데이터를 운영자가 사용할 수 있는 형태로 연결하는 진단 계층이다. statement_analysis로 고비용 SQL을 찾고, schema_table_statistics와 인덱스 뷰로 접근 대상을 좁히며, innodb_lock_waits와 메모리 뷰로 동시성·자원 문제를 확인할 수 있다. 다만 결과의 신뢰 범위는 Performance Schema 설정, 관측 구간, 서버 재시작, 접속 인스턴스에 의해 결정된다.
다음 단계에서는 각 sys 뷰가 의존하는 Performance Schema 원본 테이블을 직접 추적하고, 누적 counter를 시간 구간별 delta로 변환하여 운영 대시보드와 경보에 연결하는 방법을 다룰 수 있다.