Connection storm과 pool 운영: max_connections, thread, timeout
Connection storm이란 무엇인가
MySQL에서 connection storm은 짧은 시간에 애플리케이션이 동시 연결을 폭발적으로 늘려 데이터베이스가 감당할 수 없는 수준의 연결 요청을 받는 현상이다. 재시작 직후 다수의 앱 서버가 동시에 연결을 맺으려 할 때, 트래픽 스파이크로 스케일아웃된 Lambda나 컨테이너가 각각 독립 연결을 시도할 때, 연결 풀이 없는 코드에서 요청마다 새 connection을 열 때 발생한다.
max_connections에 도달하면 MySQL은 새 연결 요청에 ERROR 1040: Too Many Connections를 반환한다. 하지만 단순히 max_connections를 높이는 것은 해결책이 아니다. 한계를 높이면 더 많은 연결이 실제로 맺혀 스레드 컨텍스트 스위칭, 메모리 소비, InnoDB 잠금 경합이 오히려 악화될 수 있다.
MySQL 연결 모델: thread-per-connection
MySQL의 기본 모델은 thread-per-connection이다. 클라이언트가 연결을 맺으면 mysqld 내부에 해당 클라이언트 전용 OS 스레드가 하나 생성된다. 연결이 닫혀야 스레드가 소멸한다.
pool: 50 conn
pool: 50 conn
pool: 50 conn
conn/req
thread × connection
각 스레드: ~1MB 메모리
각 연결은 TCP 핸드셰이크, 인증 교환, TLS 협상(설정된 경우), 스레드별 메모리(sort_buffer, join_buffer, read_buffer 등) 할당 비용을 포함한다. 연결이 많을수록 컨텍스트 스위칭 오버헤드가 증가하고, InnoDB의 innodb_thread_concurrency 제한에 의해 실제 동시 실행이 throttle된다.
핵심 파라미터
max_connections
-- 현재 설정 확인
SHOW VARIABLES LIKE 'max_connections';
-- 실제 최대 동시 접속 기록
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
-- 연결 오류 발생 횟수 (max_connections 초과)
SHOW GLOBAL STATUS LIKE 'Connection_errors_max_connections';적절한 max_connections 계산 공식은 정해진 답이 없지만, 실무에서 자주 쓰이는 출발점은 다음과 같다.
max_connections ≈ (가용 메모리 - innodb_buffer_pool_size - OS 오버헤드) / 연결당 메모리연결당 메모리는 sort_buffer_size + join_buffer_size + read_buffer_size + tmp_table_size 등의 합산이며, 기본 설정 기준으로 대략 4~8MB 범위다. 16GB RAM, 8GB buffer pool 환경이라면 실제 연결에 쓸 수 있는 메모리는 약 6~7GB이고, 여기서 연결당 6MB로 나누면 약 1,000~1,100이 나온다. 하지만 실제로 1,000개 스레드를 동시 실행하면 CPU 경합이 심해진다. Threads_running이 CPU 코어 수의 2~4배를 넘으면 성능이 떨어지기 시작한다.
Threads_connected vs Threads_running
SHOW GLOBAL STATUS LIKE 'Threads_connected'; -- 현재 연결된 총 세션 수
SHOW GLOBAL STATUS LIKE 'Threads_running'; -- 현재 쿼리를 실행 중인 스레드 수Threads_connected는 높지만 Threads_running이 낮으면 대부분 세션이 sleep 상태다. Threads_running이 급격히 오르면 실제 부하 증가 또는 쿼리 지연 신호다. Connection storm 탐지에서 Threads_running이 max_connections보다 더 중요한 지표다.
wait_timeout / interactive_timeout
sleep 상태 연결이 자동으로 닫히지 않으면 연결 수가 줄지 않는다.
SHOW VARIABLES LIKE 'wait_timeout'; -- 비대화형 세션 유휴 제한 (초)
SHOW VARIABLES LIKE 'interactive_timeout'; -- 대화형 세션 유휴 제한 (초)기본값은 28800초(8시간)다. 연결 풀이 없는 환경에서 이 값이 크면 zombie connection이 쌓인다. 운영 환경에서는 300~600초(5~10분)로 줄이는 것이 일반적이다.
-- 현재 sleep 중인 연결 수와 최대 sleep 시간 확인
SELECT
COUNT(*) AS sleep_count,
MAX(TIME) AS max_sleep_sec
FROM information_schema.PROCESSLIST
WHERE COMMAND = 'Sleep';연결 풀 계층 전략
Connection pool은 애플리케이션 연결 수와 MySQL 연결 수 사이에 완충 계층을 둬 MySQL이 실제로 받는 연결 수를 제한한다.
애플리케이션 내장 풀
JDBC HikariCP, GORM, SQLAlchemy, go-sql-driver 등 대부분의 ORM/드라이버에는 내장 연결 풀이 있다. 주요 설정 항목은 다음과 같다.
pool_size (minimum idle) — MySQL이 유지해야 할 상시 연결 수
max_pool_size — 최대 연결 수 (이 값 × 앱 서버 수가 MySQL max_connections 이하여야)
connection_timeout — 풀에서 연결을 얻지 못했을 때 대기 시간
max_lifetime — 연결을 재생성하는 주기 (wait_timeout보다 짧게)HikariCP 예시:
maximumPoolSize = 20
minimumIdle = 5
connectionTimeout = 30000 # 30초
maxLifetime = 1800000 # 30분 (wait_timeout 600초보다 길면 안됨)max_lifetime이 MySQL의 wait_timeout보다 길면, MySQL이 먼저 연결을 끊었는데 풀이 그 연결을 살아있다고 착각해 Communications link failure가 발생한다.
프록시 계층: ProxySQL
ProxySQL은 MySQL과 애플리케이션 사이에서 connection multiplexing을 제공한다. 수천 개의 앱 연결을 받아 MySQL 쪽에는 수십~수백 개의 실제 연결만 유지한다.
각 스레드 연결
수천 연결
multiplexing
read/write 분리
50~200 연결
ProxySQL의 주요 연결 관련 설정:
-- ProxySQL 관리 콘솔 접속 후
SELECT * FROM global_variables WHERE variable_name LIKE 'mysql-max%';
-- 핵심 파라미터
-- mysql-max_connections: ProxySQL이 허용할 최대 클라이언트 연결
-- mysql-connection_max_age_ms: 백엔드 연결 최대 유지 시간
-- mysql-connect_timeout_server: MySQL에 연결 시 타임아웃
-- mysql-wait_timeout: 유휴 클라이언트 연결 종료 시간ProxySQL은 LOAD MYSQL VARIABLES TO RUNTIME과 SAVE MYSQL VARIABLES TO DISK로 설정을 반영한다. 재시작 없이 동적으로 적용 가능하다.
Thread Pool 플러그인 (Enterprise / Percona)
MySQL Enterprise Edition 및 Percona Server의 Thread Pool 플러그인은 thread-per-connection 대신 스레드 그룹당 연결을 할당해 OS 스레드 수를 CPU 코어 수에 비례하게 제한한다.
-- Thread Pool 활성 여부 확인 (Enterprise / Percona Server)
SHOW VARIABLES LIKE 'thread_handling';
-- thread_pool = thread pool 사용 중
-- one-thread-per-connection = 기본 모델
SHOW VARIABLES LIKE 'thread_pool_size'; -- 스레드 그룹 수 (권장: CPU 코어 수)
SHOW VARIABLES LIKE 'thread_pool_max_threads'; -- 최대 허용 스레드Thread Pool은 max_connections를 높여도 실제 실행 스레드를 제한해 컨텍스트 스위칭 폭발을 억제한다. 단, Community Edition에서는 기본 제공되지 않는다.
Connection storm 발생 시 즉시 대응
-- 1. 현재 연결 상태 빠른 확인
SHOW GLOBAL STATUS LIKE 'Threads_%';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
SHOW GLOBAL STATUS LIKE 'Connection_errors%';
-- 2. 가장 많은 연결을 보낸 호스트 확인
SELECT HOST, COUNT(*) AS cnt
FROM information_schema.PROCESSLIST
GROUP BY HOST
ORDER BY cnt DESC
LIMIT 20;
-- 3. 오래된 sleep 연결 일괄 확인
SELECT ID, USER, HOST, DB, TIME, STATE, INFO
FROM information_schema.PROCESSLIST
WHERE COMMAND = 'Sleep' AND TIME > 300
ORDER BY TIME DESC;
-- 4. 특정 세션 종료
KILL 12345;프로세스 수준에서 해결하지 않으면 단기 임시방편으로 max_connections를 동적으로 높일 수 있다.
-- 동적 변경 (재시작 없이 즉시 적용)
SET GLOBAL max_connections = 500;
-- 영구 적용은 my.cnf에 기록운영 지표와 알림 임계값
| 지표 | 설명 | 알림 기준 |
|---|---|---|
Threads_running | 실제 실행 중인 스레드 | CPU 코어 × 4 초과 시 경고 |
Threads_connected | 현재 연결 수 | max_connections × 80% 초과 시 경고 |
Connection_errors_max_connections | 연결 거부 횟수 | 0 초과 즉시 알림 |
Max_used_connections | 최대 동시 연결 기록 | max_connections × 90% 초과 검토 |
| sleep 연결 수 | PROCESSLIST에서 직접 계산 | 전체의 70% 초과 시 wait_timeout 점검 |
-- 실시간 연결 상태 한눈에 보기
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',
'Aborted_connects'
);실전 체크리스트
wait_timeout을 300~600초로 낮춰 zombie 연결 제거- 앱 서버 수 × 풀 크기가
max_connections를 초과하지 않도록 설계 max_lifetime(HikariCP 등)을wait_timeout보다 짧게 설정- Lambda/컨테이너 환경이라면 ProxySQL/RDS Proxy 같은 외부 풀 계층 추가
Connection_errors_max_connections알림을 0 초과 즉시 발동으로 설정Threads_running> CPU 코어 × 4이면 쿼리 느려지기 시작했다고 판단
References
- MySQL 8.4 Reference Manual, "Connection Handling and Scaling": https://dev.mysql.com/blog-archive/mysql-connection-handling-and-scaling/
- MySQL 8.4 Reference Manual, "Thread Pool Tuning": https://dev.mysql.com/doc/refman/8.4/en/thread-pool-tuning.html
- Percona Blog, "Fixing MySQL Scalability Problems with ProxySQL or Thread Pool": https://www.percona.com/blog/fixing-mysql-scalability-problems-proxysql-thread-pool/
- Percona Blog, "Preventing MySQL Error 1040: Too Many Connections": https://www.percona.com/blog/mysql-error-too-many-connections/
- Releem, "max_connections — MySQL Max Connections Limit": https://releem.com/docs/mysql-performance-tuning/max_connections
- AWS Prescriptive Guidance, "Managing Connections — Aurora MySQL": https://docs.aws.amazon.com/prescriptive-guidance/latest/hyperscale-aurora-mysql/manage-connections.html
- MinervaDB, "MySQL Connection Pooling Best Practices at Scale": https://minervadb.com/mysql-connection-pooling-best-practices-at-scale/