LLM WikiAccess-protected knowledge portal
← 스터디 홈
6편 · 약 18분

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 스레드가 하나 생성된다. 연결이 닫혀야 스레드가 소멸한다.

App Server 1
pool: 50 conn
App Server 2
pool: 50 conn
App Server 3
pool: 50 conn
Lambda ×N
conn/req
mysqld
thread × connection
max_connections = 151
각 스레드: ~1MB 메모리
MySQL thread-per-connection 모델

각 연결은 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_runningmax_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 쪽에는 수십~수백 개의 실제 연결만 유지한다.

App ×100
각 스레드 연결

수천 연결
ProxySQL
Connection Pool
multiplexing
쿼리 라우팅
read/write 분리

50~200 연결
MySQL Primary
Replica ×N
ProxySQL 연결 구조

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 RUNTIMESAVE 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/