/인프라/ERROR 1040 Too many connections 해결: MySQL 커넥션 고갈 진단·복구 런북
InfrastructureMySQLDB 운영

ERROR 1040 Too many connections 해결: MySQL 커넥션 고갈 진단·복구 런북

This article is not yet available in English. Showing the original Korean version.

MySQL ERROR 1040 Too many connections를 30초 만에 진단하고 즉시 복구하는 런북. SHOW PROCESSLIST·kill부터 max_connections·wait_timeout, HikariCP·Django·Laravel 풀 설정까지 복붙 가이드.

ERROR 1040 Too many connections 해결: MySQL 커넥션 고갈 진단·복구 런북

지금 이 에러를 보고 들어왔다면 — 3분 안에 살립니다

애플리케이션 로그나 콘솔에 다음 원문이 찍혀 있을 겁니다.

TEXT
ERROR 1040 (HY000): Too many connections

이 에러는 문법 오류도, 쿼리 오류도 아닙니다. MySQL/MariaDB 서버가 열어둔 커넥션 슬롯(max_connections)을 전부 소진해서 새 접속을 거부하는 상태입니다. PostgreSQL의 FATAL: sorry, too many clients already가 백엔드 프로세스 상한이라면, MySQL은 하나의 서버 프로세스 안에서 스레드 단위로 커넥션을 관리한다는 점이 다릅니다. 즉 슬롯이 꽉 찼을 뿐 서버 자체는 살아 있고, Sleep 상태로 놀고 있는 커넥션만 정리하면 즉시 복구됩니다.

적용 범위: MySQL 5.7 / 8.0, MariaDB 10.x, AWS RDS/Aurora MySQL 호환. 이 글은 "명령 먼저, 설명은 뒤"로 구성했습니다. 지금 서비스가 죽어 있다면 아래 순서대로 그대로 복붙하세요.

  • 1단계 진단(30초) → 슬롯 고갈 확진
  • 2단계 복구 → root 예약 슬롯 접속 + kill + 임시 상향
  • 3단계 차단 → my.cnf 튜닝 + 커넥션 풀 설정

30초 확진: 슬롯이 꽉 찼는지 3개 명령으로 판정

먼저 접속만 된다면(모니터링용 커넥션, 관리자 세션 등) 아래를 실행합니다. 앱 계정으로 접속이 거부돼도, root는 예약 슬롯이 있어 대부분 들어갑니다(다음 섹션 참고).

SQL
-- 현재 열린 커넥션 수
SHOW STATUS LIKE 'Threads_connected';
-- 서버 기동 후 최대로 도달했던 커넥션 수
SHOW STATUS LIKE 'Max_used_connections';
-- 허용 상한
SHOW VARIABLES LIKE 'max_connections';
-- 슬롯을 점유 중인 세션 전체 (누가 범인인지)
SHOW FULL PROCESSLIST;
-- 인증/네트워크 실패로 끊긴 접속 누적치
SHOW STATUS LIKE 'Aborted_connects';

출력값을 이렇게 읽습니다.

항목정상 판단고갈 판단의미
Threads_connectedmax_connections보다 여유 있음max_connections에 근접/동일지금 열려 있는 커넥션 수
Max_used_connectionsmax_connections보다 작음max_connections와 같음한 번이라도 상한에 닿았다는 증거
max_connections워크로드 대비 충분기본값 151에 묶여 있음허용 슬롯 상한
Aborted_connects낮게 유지빠르게 증가인증 실패·타임아웃 (슬롯 고갈과 별개 원인)

Max_used_connectionsmax_connections와 같으면 슬롯 고갈 확진입니다.

SHOW FULL PROCESSLIST 결과에서는 Command 컬럼과 Time 컬럼을 봅니다.

TEXT
+-----+--------+-----------------+------+---------+------+-------+------------------+
| Id  | User   | Host            | db   | Command | Time | State | Info             |
+-----+--------+-----------------+------+---------+------+-------+------------------+
| 812 | appuser| 10.0.1.20:51022 | shop | Sleep   | 240  |       | NULL             |
| 813 | appuser| 10.0.1.20:51044 | shop | Sleep   | 238  |       | NULL             |
| 820 | appuser| 10.0.1.21:33002 | shop | Query   | 0    | ...   | SELECT ...       |
+-----+--------+-----------------+------+---------+------+-------+------------------+

CommandSleep이고 Time이 큰 커넥션이 잔뜩 쌓여 있다면, 애플리케이션 커넥션 풀이 커넥션을 반납하지 않고 붙잡고 있는 누수 신호입니다. 특정 Host(파드 IP나 서버 IP)에 커넥션이 몰려 있으면 그 앱이 범인입니다.

원인 분기표: 진짜 슬롯 고갈인가, 다른 문제인가

관측 지표실제 원인대응 방향
Threads_connectedmax_connections, Sleep 다수커넥션 풀 누수/과다kill + 풀 사이즈·idle timeout 조정
Aborted_connects 급증, Threads_connected는 여유비밀번호 오류·방화벽·네트워크 끊김슬롯 문제 아님. 인증/네트워크 점검
파드 스케일아웃 직후 급증파드 수 × 풀 사이즈 > 상한풀 사이즈 축소 또는 RDS Proxy/ProxySQL 도입
RDS/Aurora에서 낮은 상한인스턴스 클래스 연동 기본값파라미터 그룹에서 max_connections 조정

Aborted_connects만 오르는데 Threads_connected는 여유롭다면 1040은 부차적 증상이고 실제로는 인증 폭주나 네트워크 문제일 수 있으니 방향을 틀어야 합니다.

즉시 복구: root 예약 슬롯으로 들어가 kill 하기

MySQL은 CONNECTION_ADMIN(8.0) 또는 SUPER(5.7) 권한 계정에 슬롯 1개를 예약해 둡니다. 그래서 앱 계정이 1040으로 거부돼도 root는 접속되는 경우가 많습니다. 먼저 들어갑니다.

Bash
mysql -u root -p

접속되면 Sleep 커넥션부터 정리합니다. 개별 kill은 이렇게 합니다.

SQL
KILL 812;
KILL 813;

수십 개를 일일이 치기 어렵다면, kill 문을 자동 생성해서 한 번에 실행합니다.

SQL
-- 10분(600초) 이상 Sleep인 커넥션에 대한 KILL 문 생성
SELECT CONCAT('KILL ', id, ';') AS kill_stmt
FROM information_schema.processlist
WHERE command = 'Sleep' AND time > 600;

출력된 KILL ...; 문들을 복사해 붙여 실행하면 됩니다. 셸에서 원라이너로 처리하려면 mysqladmin이 편합니다.

Bash
# 현재 프로세스 목록 확인
mysqladmin -u root -p processlist

# 특정 id 강제 종료
mysqladmin -u root -p kill 812

# Sleep 커넥션 id를 뽑아 한 번에 kill (셸 원라이너)
mysql -u root -p -N -e \
"SELECT id FROM information_schema.processlist WHERE command='Sleep' AND time>600" \
| while read id; do mysqladmin -u root -p'YOUR_PW' kill "$id"; done

⚠️ 비밀번호를 -p'PW'로 붙이면 셸 히스토리에 남습니다. 급한 복구 후에는 history -c로 지우거나 .my.cnf를 사용하세요.

슬롯을 비운 뒤에도 트래픽이 몰려 금방 다시 찬다면, 재기동 없이 상한을 임시로 올립니다.

SQL
SET GLOBAL max_connections = 500;
-- 반영 확인
SHOW VARIABLES LIKE 'max_connections';

이 명령은 런타임 임시 조치입니다. MySQL을 재시작하면 my.cnf 값으로 되돌아갑니다. 즉 급한 불을 끄는 용도이며, 반드시 아래 3단계로 영구 설정과 근본 원인(풀 누수)을 잡아야 합니다. 상한을 무작정 올리면 커넥션당 메모리(정렬/조인 버퍼 등)로 인해 OOM 위험이 커진다는 점도 기억하세요.

재발 차단 1: my.cnf 서버 튜닝

/etc/my.cnf 또는 /etc/mysql/my.cnf[mysqld] 섹션에 영구 값을 지정합니다.

INI
[mysqld]
# 동시 접속 상한 (워크로드·메모리 고려해 산정)
max_connections = 500
# 단일 계정이 독점하지 못하도록 계정별 상한
max_user_connections = 200
# 비대화형(앱) 커넥션이 유휴 상태로 유지되는 최대 초 (기본 28800초=8시간이 문제)
wait_timeout = 600
# mysql 클라이언트 등 대화형 세션 유휴 상한
interactive_timeout = 600

wait_timeout을 8시간에서 10분으로 줄이면, 풀이 반납을 놓친 유휴 커넥션을 서버가 알아서 회수합니다. 적용 후 재시작하고 검증합니다.

Bash
sudo systemctl restart mysqld
SQL
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'max_user_connections';
SHOW VARIABLES LIKE 'wait_timeout';

각 값이 설정대로 나오면 성공입니다. 값이 반영되지 않았다면 편집한 파일이 실제 로드되는 설정 파일이 아닐 수 있으니 mysqld --help --verbose | grep -A1 "Default options"로 로드 경로를 확인하세요. RDS/Aurora는 my.cnf 대신 파라미터 그룹에서 조정하며, Aurora 기본 max_connections는 인스턴스 클래스 메모리에 연동된 공식으로 계산되므로 인스턴스를 키우거나 파라미터를 명시적으로 덮어써야 합니다(공식 파라미터 문서 확인 필요).

재발 차단 2: 프레임워크별 커넥션 풀 설정

근본 원인은 대부분 애플리케이션입니다. 핵심 원칙: 커넥션의 최대 수명(maxLifetime)을 서버 wait_timeout보다 짧게 잡아, 서버가 끊기 전에 앱이 먼저 정리하도록 합니다.

HikariCP (Spring Boot)

PROPERTIES
# application.properties
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=300000      # 5분
spring.datasource.hikari.max-lifetime=570000      # 9.5분 < wait_timeout(600s)
spring.datasource.hikari.connection-timeout=3000

max-lifetimewait_timeout보다 반드시 짧게 두는 것이 핵심입니다. 그렇지 않으면 서버가 끊은 죽은 커넥션을 풀이 재사용하려다 오류가 납니다.

Django

Python
# settings.py
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "CONN_MAX_AGE": 60,   # 커넥션 재사용 최대 초. wait_timeout보다 짧게
        "OPTIONS": {"connect_timeout": 5},
    }
}

Django는 요청 종료 시 커넥션을 정리하지만, CONN_MAX_AGE를 무한대(None)나 너무 크게 두면 워커 수 × 커넥션이 누적됩니다. wait_timeout보다 작게 잡으세요.

Laravel / PDO

PHP
// config/database.php
'mysql' => [
    // ...
    'options' => [
        PDO::ATTR_PERSISTENT => false, // ★ 영속 커넥션은 누수·잔존 위험, 기본 false 유지
        PDO::ATTR_TIMEOUT => 5,
    ],
],

PDO::ATTR_PERSISTENT => true는 커넥션을 프로세스에 붙여둬 슬롯 고갈을 유발하기 쉽습니다. 특별한 이유가 없으면 끄는 것이 안전합니다.

Node.js (mysql2)

JavaScript
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
  host: 'db.internal',
  user: 'appuser',
  database: 'shop',
  connectionLimit: 15,     // 인스턴스당 최대 커넥션
  waitForConnections: true,
  queueLimit: 0,
  idleTimeout: 60000,      // 유휴 커넥션 회수(60초)
  enableKeepAlive: true,
});

컨테이너·서버리스 환경의 함정

쿠버네티스에서 파드가 10개로 스케일아웃되고 각 파드가 connectionLimit=15라면 순간 150 커넥션을 요구합니다. 상한 산정은 반드시 (파드/워커 수) × (풀 사이즈)로 계산하세요. Lambda 같은 서버리스는 동시성만큼 커넥션이 폭증하므로, 애플리케이션 풀 대신 RDS Proxy·ProxySQL 같은 커넥션 풀링 미들웨어를 앞단에 두어 실제 DB 커넥션 수를 통제하는 방식이 실무에서 점점 표준이 되고 있습니다.

마무리: 진단 → 복구 → 차단 체크리스트

한 화면에 요약합니다.

SQL
-- ① 진단
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
SHOW VARIABLES LIKE 'max_connections';
SHOW FULL PROCESSLIST;

-- ② 즉시 복구 (root 접속 후)
SELECT CONCAT('KILL ', id, ';') FROM information_schema.processlist
  WHERE command='Sleep' AND time>600;   -- 출력 복사 실행
SET GLOBAL max_connections = 500;        -- 임시(재기동 시 초기화)

-- ③ 영구 차단: my.cnf에 max_connections/wait_timeout,
--    앱 풀에 maxLifetime < wait_timeout
  • Max_used_connections가 상한에 닿았는지로 고갈 확진
  • Sleep 커넥션 kill로 즉시 슬롯 확보
  • SET GLOBAL은 임시 조치임을 인지하고 my.cnf 영구화
  • 각 앱 풀 사이즈 합계가 상한을 넘지 않는지 재계산
  • Threads_connected, Aborted_connects 모니터링 알람 설정 (상한의 80% 도달 시 경보 권장)

마지막으로 재발 방지의 핵심은 모니터링입니다. Threads_connectedmax_connections의 80%를 넘으면 알람이 울리도록 설정해 두면, 다음 번에는 1040을 만나기 전에 손을 쓸 수 있습니다.

자주 묻는 질문 (FAQ)

Q. SET GLOBAL max_connections로 올렸는데 재시작하니 원래대로 돌아왔어요. A. 정상입니다. SET GLOBAL은 런타임 임시값이라 재기동하면 my.cnf 설정으로 복귀합니다. 영구 반영하려면 [mysqld]max_connections 값을 적고 재시작한 뒤 SHOW VARIABLES LIKE 'max_connections';로 확인하세요.

Q. root로도 접속이 안 돼 ERROR 1040이 떠요. A. 예약 슬롯까지 소진되었거나(다른 관리 세션 점유) CONNECTION_ADMIN/SUPER 권한이 없는 계정입니다. 로컬 소켓으로 다시 시도(mysql -u root -p --socket=/var/run/mysqld/mysqld.sock)하거나, 그래도 안 되면 mysqladmin으로 종료를 시도하고 최후에는 서비스 재시작으로 슬롯을 초기화하세요.

Q. Sleep 커넥션을 kill 했는데 금방 다시 꽉 차요. A. 애플리케이션 커넥션 풀이 계속 새 커넥션을 만들고 반납하지 않는 누수입니다. 파드/워커 수 × 풀 사이즈가 상한을 넘는지 재계산하고, HikariCP max-lifetimewait_timeout보다 짧게 설정한 뒤, 필요하면 RDS Proxy·ProxySQL로 커넥션을 집약하세요.

✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 · ·업데이트 ·
관련 공식 문서MySQL 공식 매뉴얼

Comments

Be the first to comment.