지금 몇 개가 열려 있는지, 기동 후 최대 몇 개까지 갔는지, 연결이 거절된 적이 있는지를 함께 본다. 현재 값만 보고 늘리면 근거가 없다.
SHOW GLOBAL STATUS LIKE 'Threads_connected'; -- 지금 열린 연결
SHOW GLOBAL STATUS LIKE 'Threads_running'; -- 실제로 일하는 스레드
SHOW GLOBAL STATUS LIKE 'Max_used_connections';-- 기동 후 최고치
SHOW GLOBAL STATUS LIKE 'Connections'; -- 기동 후 누적 접속 시도
SHOW GLOBAL STATUS LIKE 'Aborted_connects'; -- 인증 단계에서 실패한 수
SHOW GLOBAL STATUS LIKE 'Aborted_clients'; -- 접속 후 비정상 종료된 수
SHOW GLOBAL STATUS LIKE 'Uptime';
SHOW GLOBAL VARIABLES LIKE 'max_connections';
Max_used_connections 가 max_connections 에 닿았다면 이미 거절이 있었을 가능성이 크다. 버전에 따라 그 시각을 알려 주는 Max_used_connections_time 이 있으므로 SHOW GLOBAL STATUS LIKE 'Max_used_connections%' 로 함께 확인한다.
Threads_connected 는 높은데 Threads_running 이 낮으면 연결만 붙들고 있는 것이다. 이 경우 max_connections 를 올리는 대신 커넥션 풀 설정과 유휴 연결 정리를 먼저 본다.
ERROR 1040 (HY000): Too many connections
SUPER 권한을 가진 계정용으로 한 자리를 남겨 두므로 관리자는 그 상황에서도 붙을 수 있다. MySQL 8.0 은 CONNECTION_ADMIN 권한과 별도 관리 포트(admin_port)도 제공한다.
임시로 늘리는 것은 재기동 없이 즉시 적용된다.
SET GLOBAL max_connections = 500;
영구 적용은 설정 파일에 넣는다. 값을 크게 잡을 때는 open_files_limit 과 systemd 의 LimitNOFILE 도 함께 올려야 한다. 연결 하나당 스레드와 버퍼가 붙으므로 메모리 여유도 확인한다.
[mysqld]
max_connections = 500
SHOW VARIABLES LIKE 'wait_timeout'; -- 비대화형 연결의 유휴 허용 시간(초)
SHOW VARIABLES LIKE 'interactive_timeout'; -- 대화형 클라이언트용
기본값은 보통 28800초(8시간)다. 애플리케이션이 커넥션 풀을 쓰면 서버가 먼저 끊은 연결을 풀이 그대로 꺼내 쓰다가 Communications link failure 를 만난다. 서버 쪽 wait_timeout 을 줄이려면 풀의 유휴 검증(testWhileIdle, maxLifetime)을 함께 짧게 잡아야 한다. 풀의 maxLifetime 은 서버 wait_timeout 보다 짧아야 한다.
세션이 접속할 때 interactive_timeout 값이 그 세션의 wait_timeout 으로 복사되므로, 대화형 클라이언트만 길게 두고 싶으면 두 값을 다르게 잡는다.
SHOW FULL PROCESSLIST;
SELECT id, user, host, db, command, time, state, LEFT(info, 100) AS query
FROM information_schema.processlist
WHERE command <> 'Sleep'
ORDER BY time DESC;
MySQL 8.0 은 performance_schema.processlist 를 권장한다. 문제 세션은 KILL <id> 로 끊는다. KILL QUERY <id> 는 실행 중인 쿼리만 중단하고 연결은 유지한다.
SELECT table_schema,
table_name,
ROUND((data_length + index_length) / 1024 / 1024, 2) AS total_mb,
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
ROUND(index_length / 1024 / 1024, 2) AS index_mb,
table_rows
FROM information_schema.tables
WHERE table_schema = 'appdb'
ORDER BY (data_length + index_length) DESC;
데이터베이스별 합계는 table_schema 로 묶는다.
SELECT table_schema,
ROUND(SUM(data_length + index_length) / 1024 / 1024 / 1024, 2) AS total_gb
FROM information_schema.tables
GROUP BY table_schema
ORDER BY total_gb DESC;
주의할 점이 둘 있다. table_rows 는 InnoDB 에서 통계에 기반한 추정치라 정확하지 않다. 정확한 건수는 SELECT COUNT(*) 로 세야 한다. 그리고 data_length 는 파일 크기이므로 DELETE 로 지운 공간은 줄어들지 않는다. 실제로 줄이려면 OPTIMIZE TABLE 또는 ALTER TABLE ... ENGINE=InnoDB 로 다시 쓴다.
log_error 를 지정하지 않으면 systemd 로 기동한 인스턴스의 표준 오류가 journald 로 흘러 시스템 로그에 섞인다. 분리하려면 경로를 명시한다.
[mysqld]
log_error = /var/log/mariadb/mariadb.log
mkdir -p /var/log/mariadb
touch /var/log/mariadb/mariadb.log
chown mysql:mysql /var/log/mariadb/mariadb.log
systemctl restart mariadb
어떤 설정 파일이 실제로 읽히는지는 짐작하지 말고 확인한다.
mysqld --verbose --help 2>/dev/null | grep -A1 'Default options'
systemctl cat mariadb | grep -i exec