Java Too many connections 오류는 어떤 상황에서 발생할까?
Java 서비스에서 Too many connections 오류가 발생했다는 것은 MySQL이 허용하는 최대 커넥션 수를 이미 모두 사용했다는 의미입니다. 애플리케이션 입장에서는 DB에 새 연결을 만들려고 했지만, MySQL 서버가 더 이상 연결을 받아줄 수 없는 상태라고 보면 됩니다.
대표적인 에러 메시지는 다음과 비슷합니다.
java.sql.SQLNonTransientConnectionException: Too many connections
com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException
Spring Boot와 HikariCP를 사용한다면 애플리케이션 로그에는 커넥션 풀에서 커넥션을 가져오지 못했다는 메시지가 함께 보일 수 있습니다.
HikariPool-1 - Connection is not available, request timed out after 30000ms
이 두 메시지는 표현은 다르지만 같은 흐름에서 발생하는 경우가 많습니다. MySQL은 더 이상 커넥션을 받을 수 없고, Java 애플리케이션은 커넥션 풀에서 사용 가능한 연결을 기다리다가 타임아웃이 발생하는 구조입니다.
Java Too many connections를 단순 DB 설정 문제로만 보면 안 되는 이유
Java에서 Too many connections가 발생하면 가장 먼저 떠오르는 해결책은 MySQL의 max_connections 값을 늘리는 것입니다. 물론 필요한 경우에는 이 설정을 조정해야 합니다. 하지만 원인을 확인하지 않고 숫자만 늘리면 같은 문제가 다시 발생할 가능성이 높습니다.
커넥션 수가 부족해지는 이유는 크게 세 가지로 나눠볼 수 있습니다.
- 애플리케이션 인스턴스 수에 비해 DB 최대 커넥션 수가 작음
- HikariCP 같은 커넥션 풀 설정이 과하게 큼
- 커넥션을 반납하지 못하는 코드나 오래 점유하는 쿼리가 있음
실무에서는 이 세 가지가 섞여 있는 경우도 많습니다. 예를 들어 서버 인스턴스를 늘렸는데 각 인스턴스의 커넥션 풀 크기는 그대로라면, 전체 DB 커넥션 사용량은 예상보다 빠르게 증가합니다.
Too many connections 발생 시 먼저 확인할 MySQL 상태
Too many connections가 발생했을 때는 먼저 MySQL 서버에서 현재 커넥션 상태를 확인해야 합니다. 감으로 설정을 바꾸기보다, 지금 DB가 어떤 상태인지 숫자로 확인하는 것이 우선입니다.
현재 최대 커넥션 수 확인
MySQL의 최대 커넥션 수는 다음 명령어로 확인할 수 있습니다.
SHOW VARIABLES LIKE 'max_connections';
현재 열린 커넥션 수는 다음과 같이 확인합니다.
SHOW STATUS LIKE 'Threads_connected';
max_connections가 200인데 Threads_connected가 계속 190 이상으로 유지된다면, DB 입장에서는 거의 한계에 가까운 상태입니다. 일시적으로 튄 것인지, 평소에도 높은 상태인지 함께 봐야 합니다.
실행 중인 커넥션 확인
현재 어떤 커넥션이 열려 있는지 확인하려면 PROCESSLIST를 조회합니다.
SHOW FULL PROCESSLIST;
또는 information_schema를 이용해 조금 더 정리된 형태로 볼 수 있습니다.
SELECT
ID,
USER,
HOST,
DB,
COMMAND,
TIME,
STATE,
INFO
FROM information_schema.PROCESSLIST
ORDER BY TIME DESC;
여기서 Sleep 상태의 커넥션이 지나치게 많다면 커넥션 풀 설정이나 반납 흐름을 의심해볼 수 있습니다. 반대로 특정 쿼리가 오래 실행 중이라면 커넥션 부족의 원인이 느린 쿼리일 가능성도 있습니다.
Java HikariCP 설정에서 확인해야 할 부분
Java Spring Boot 환경에서는 대부분 HikariCP를 기본 커넥션 풀로 사용합니다. Too many connections가 발생했을 때는 MySQL 설정뿐 아니라 HikariCP의 maximum-pool-size도 반드시 함께 봐야 합니다.
예를 들어 다음과 같은 설정이 있다고 가정해보겠습니다.
spring:
datasource:
hikari:
maximum-pool-size: 50
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
이 설정만 보면 한 서버에서 최대 50개의 DB 커넥션을 사용할 수 있다는 의미입니다. 문제는 운영 환경에서 애플리케이션 인스턴스가 여러 대일 때입니다.
애플리케이션 인스턴스 수: 6대
인스턴스당 maximum-pool-size: 50
최대 사용 가능 커넥션 수:
6 * 50 = 300
MySQL의 max_connections가 250인데 애플리케이션이 이론상 300개까지 커넥션을 만들 수 있다면, 트래픽이 조금만 몰려도 Too many connections가 발생할 수 있습니다. 그래서 커넥션 풀 크기는 서버 한 대 기준이 아니라 전체 인스턴스 수 기준으로 계산해야 합니다.
maximum-pool-size를 무조건 크게 잡으면 안 됩니다
커넥션 풀 크기를 크게 잡으면 요청을 더 많이 처리할 수 있을 것처럼 보이지만, DB가 동시에 처리할 수 있는 작업량에는 한계가 있습니다. 커넥션 수가 많아진다고 쿼리 처리 능력이 같은 비율로 좋아지는 것은 아닙니다.
오히려 과도한 커넥션은 DB 내부에서 더 많은 동시 작업을 만들고, 쿼리 대기와 락 경합을 늘릴 수 있습니다. 그래서 maximum-pool-size는 넉넉하게 크게 잡는 값이 아니라, 애플리케이션과 DB가 안정적으로 감당할 수 있는 범위 안에서 정해야 합니다.
Too many connections 원인 추적 순서
Java Too many connections 문제를 볼 때는 한 번에 모든 설정을 바꾸기보다 순서대로 확인하는 편이 좋습니다. 원인을 나누어 보면 조치 방향도 훨씬 명확해집니다.
1단계: DB 전체 커넥션 한계 확인
가장 먼저 MySQL의 max_connections와 현재 커넥션 수를 확인합니다.
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Max_used_connections';
Max_used_connections는 MySQL 서버가 기동된 이후 사용된 최대 커넥션 수를 보여줍니다. 이 값이 max_connections에 거의 붙어 있다면, 이미 어느 시점에 커넥션 한계에 도달했다는 뜻입니다.
2단계: 애플리케이션 전체 커넥션 가능 수 계산
다음으로 Java 애플리케이션이 만들 수 있는 최대 커넥션 수를 계산합니다.
전체 최대 커넥션 수 =
애플리케이션 인스턴스 수 * 인스턴스당 maximum-pool-size
여기에 배치 서버, 관리자 서버, 모니터링 도구, 마이그레이션 작업 등 다른 DB 접속 주체도 포함해야 합니다. 웹 애플리케이션만 계산하면 실제 커넥션 사용량을 낮게 보는 실수를 하기 쉽습니다.
3단계: Sleep 커넥션이 많은지 확인
Sleep 상태가 많다고 해서 무조건 문제는 아닙니다. 커넥션 풀은 재사용을 위해 일정 수의 커넥션을 유지하기 때문입니다. 다만 Sleep 상태가 과도하게 많고 줄어들지 않는다면 커넥션 풀 크기가 과하게 잡혀 있을 수 있습니다.
SELECT
COMMAND,
COUNT(*) AS count
FROM information_schema.PROCESSLIST
GROUP BY COMMAND;
이 쿼리를 통해 현재 커넥션이 Sleep, Query, Execute 등 어떤 상태로 분포되어 있는지 볼 수 있습니다.
4단계: 오래 실행되는 쿼리 확인
커넥션은 쿼리가 실행되는 동안 점유됩니다. 특정 쿼리가 오래 걸리면 해당 커넥션은 그 시간만큼 반환되지 않습니다. 이런 쿼리가 동시에 많이 발생하면 커넥션 풀이 빠르게 고갈됩니다.
SELECT
ID,
USER,
HOST,
DB,
TIME,
STATE,
INFO
FROM information_schema.PROCESSLIST
WHERE COMMAND != 'Sleep'
ORDER BY TIME DESC;
여기서 오래 실행 중인 쿼리가 보인다면 단순히 커넥션 수를 늘리는 것보다 해당 쿼리의 실행 계획, 인덱스, 트랜잭션 범위를 먼저 확인하는 것이 맞습니다.
Java 코드에서 커넥션 누수를 의심해야 하는 경우
Java Too many connections 문제에서 놓치기 쉬운 부분이 커넥션 누수입니다. Spring의 JdbcTemplate, JPA, MyBatis를 일반적인 방식으로 사용한다면 커넥션 반납은 프레임워크가 처리합니다. 하지만 직접 커넥션을 열거나 트랜잭션 경계를 잘못 다루면 문제가 생길 수 있습니다.
직접 Connection을 열고 닫지 않는 코드
다음과 같은 코드는 문제가 될 수 있습니다.
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM orders WHERE user_id = ?"
);
statement.setLong(1, userId);
ResultSet resultSet = statement.executeQuery();
이 코드에는 Connection, PreparedStatement, ResultSet을 닫는 처리가 없습니다. 예외가 발생하면 커넥션이 반환되지 않을 수 있고, 이 흐름이 반복되면 커넥션 풀이 고갈됩니다.
직접 JDBC를 사용해야 한다면 try-with-resources를 사용하는 편이 안전합니다.
try (
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM orders WHERE user_id = ?"
)
) {
statement.setLong(1, userId);
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
// 결과 처리
}
}
}
이 방식은 블록을 벗어날 때 리소스를 자동으로 닫습니다. 코드가 조금 길어 보이더라도 커넥션 반납 의도가 명확하다는 장점이 있습니다.
트랜잭션 안에서 외부 API를 호출하는 코드
커넥션 누수는 아니지만 커넥션을 오래 붙잡는 코드도 문제가 됩니다. 대표적인 예가 트랜잭션 안에서 외부 API를 호출하는 경우입니다.
@Transactional
public void pay(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow();
paymentClient.approve(order); // 외부 API 호출
order.complete();
}
이 코드는 트랜잭션이 시작된 상태에서 외부 API 응답을 기다립니다. 이 동안 DB 커넥션이 함께 점유될 수 있습니다. 외부 API가 느려지면 DB 커넥션까지 같이 묶이는 구조가 됩니다.
가능하면 외부 호출과 DB 트랜잭션 범위를 분리하는 편이 좋습니다. 모든 경우에 분리할 수 있는 것은 아니지만, 트랜잭션 안에 꼭 들어가야 하는 작업인지 확인해야 합니다.
Spring Boot에서 HikariCP 설정 예시
Too many connections 대응을 위해 Spring Boot에서 HikariCP 설정을 조정할 때는 값을 크게 만드는 것보다 전체 구조에 맞게 제한하는 것이 중요합니다.
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 20000
각 설정의 의미는 다음과 같습니다.
maximum-pool-size: 한 애플리케이션 인스턴스가 가질 수 있는 최대 커넥션 수minimum-idle: 유휴 상태로 유지할 최소 커넥션 수connection-timeout: 커넥션을 얻기 위해 기다리는 최대 시간idle-timeout: 사용하지 않는 커넥션을 정리하기까지의 시간max-lifetime: 커넥션을 재사용할 수 있는 최대 수명leak-detection-threshold: 커넥션 누수 의심 로그를 남기는 기준 시간
leak-detection-threshold는 운영에서 항상 켜두기보다 문제를 추적할 때 유용하게 사용할 수 있습니다. 설정값보다 오래 커넥션이 반환되지 않으면 HikariCP가 관련 로그를 남겨주기 때문에, 어느 코드 경로에서 커넥션을 오래 잡고 있는지 추적하는 데 도움이 됩니다.
MySQL max_connections 조정 시 주의할 점
Java 애플리케이션 설정을 조정해도 DB의 최대 커넥션 수가 너무 작다면 MySQL의 max_connections 조정도 필요합니다. 다만 이 값은 무작정 크게 올리기보다 DB 서버의 메모리와 워크로드를 고려해서 정해야 합니다.
현재 값을 임시로 바꾸려면 다음처럼 실행할 수 있습니다.
SET GLOBAL max_connections = 300;
이 설정은 MySQL 재시작 후 사라질 수 있습니다. 영구 적용이 필요하다면 MySQL 설정 파일에 반영해야 합니다.
[mysqld]
max_connections=300
AWS RDS를 사용한다면 직접 설정 파일을 수정하는 대신 Parameter Group에서 값을 조정합니다. 적용 방식이 즉시 반영인지, 재부팅이 필요한 값인지도 함께 확인해야 합니다.
Too many connections 긴급 대응 방법
Too many connections가 이미 발생한 상황이라면 우선 서비스 영향도를 줄이는 조치가 필요합니다. 다만 긴급 조치와 근본 조치는 구분해야 합니다.
긴급 조치 1: 불필요한 세션 정리
특정 세션이 오래 점유되고 있고 명확히 불필요하다고 판단되면 KILL 명령으로 종료할 수 있습니다.
KILL 12345;
다만 실행 중인 쿼리를 종료하면 애플리케이션 오류나 롤백이 발생할 수 있습니다. 운영 DB에서는 어떤 세션인지 확인하지 않고 일괄 종료하는 방식은 피하는 편이 좋습니다.
긴급 조치 2: 애플리케이션 인스턴스 재시작
특정 애플리케이션 인스턴스에서 커넥션을 비정상적으로 많이 점유하고 있다면 해당 인스턴스를 재시작해 커넥션을 반환시키는 방법도 있습니다. 다만 이 방법은 증상을 완화하는 조치일 뿐입니다.
재시작 후에도 같은 패턴으로 커넥션이 증가한다면 코드, 트랜잭션, 커넥션 풀 설정을 다시 봐야 합니다.
긴급 조치 3: max_connections 임시 상향
DB 서버가 감당 가능한 범위라면 max_connections를 임시로 올려 장애 범위를 줄일 수 있습니다. 하지만 이 방법은 원인 분석과 함께 진행되어야 합니다.
단순히 커넥션 수만 늘리면 문제를 뒤로 미루는 결과가 될 수 있습니다. 특히 오래 걸리는 쿼리나 커넥션 누수가 원인이라면, 커넥션 한계에 도달하는 시간만 늦춰질 뿐입니다.
Too many connections 재발을 막기 위한 운영 기준
Java Too many connections 문제는 한 번 해결했다고 끝내기보다 재발하지 않도록 기준을 만들어두는 것이 좋습니다. 운영 환경에서는 서버 수, 배치 작업, 관리자 도구, 모니터링 연결까지 모두 커넥션을 사용하기 때문입니다.
전체 커넥션 예산을 정합니다
DB의 max_connections를 기준으로 애플리케이션별 커넥션 예산을 나누는 방식이 좋습니다.
MySQL max_connections: 300
웹 애플리케이션: 180
배치 애플리케이션: 40
관리자 서비스: 30
운영 및 모니터링 여유분: 50
이렇게 나누어두면 신규 서버를 늘리거나 배치 애플리케이션을 추가할 때 커넥션 사용량을 미리 계산할 수 있습니다. 협업 관점에서도 “서버 한 대당 몇 개까지 허용할 것인가”가 명확해집니다.
커넥션 풀 설정을 배포 단위와 함께 관리합니다
커넥션 풀 설정은 애플리케이션 코드만큼 중요합니다. 특히 Kubernetes, ECS, Auto Scaling 환경에서는 인스턴스 수가 변할 수 있기 때문에 maximum-pool-size를 고정값으로만 바라보면 위험합니다.
예를 들어 인스턴스가 4대일 때는 문제가 없던 설정이 10대로 늘어나는 순간 DB 커넥션 한계를 넘을 수 있습니다. 그래서 배포 설정과 DB 커넥션 설정은 함께 검토해야 합니다.
모니터링 지표를 정해둡니다
Too many connections는 로그에 에러가 찍힌 뒤에야 알면 대응이 늦습니다. 최소한 다음 지표는 모니터링하는 편이 좋습니다.
- MySQL
Threads_connected - MySQL
Max_used_connections - HikariCP active connections
- HikariCP idle connections
- HikariCP pending threads
- DB connection timeout 로그
특히 HikariCP의 pending threads가 증가한다면 애플리케이션 요청이 커넥션을 기다리고 있다는 의미입니다. 이 값이 자주 튄다면 DB 쿼리 시간, 트랜잭션 범위, 커넥션 풀 크기를 같이 봐야 합니다.
Java Too many connections 대응 체크리스트
Java Too many connections 문제를 정리하면 다음 순서로 확인하는 것이 좋습니다.
- MySQL
max_connections값을 확인합니다. - 현재
Threads_connected와Max_used_connections를 확인합니다. - 애플리케이션 인스턴스 수와 HikariCP
maximum-pool-size를 곱해 전체 커넥션 가능 수를 계산합니다. SHOW FULL PROCESSLIST로 Sleep 커넥션과 오래 실행 중인 쿼리를 확인합니다.- 직접 JDBC를 사용하는 코드에서 커넥션 반납 누락이 없는지 확인합니다.
@Transactional범위 안에서 외부 API 호출이나 오래 걸리는 작업을 하고 있지 않은지 봅니다.- 필요하다면 HikariCP 설정과 MySQL
max_connections를 함께 조정합니다. - 재발 방지를 위해 DB 커넥션 지표를 모니터링합니다.
마무리: Too many connections는 숫자보다 구조를 봐야 합니다
Java에서 Too many connections가 발생하면 당장 보이는 해결책은 MySQL의 최대 커넥션 수를 늘리는 것입니다. 하지만 실제로는 애플리케이션 인스턴스 수, HikariCP 설정, 쿼리 실행 시간, 트랜잭션 범위, 커넥션 반납 흐름을 함께 확인해야 합니다.
커넥션은 무한히 늘릴 수 있는 자원이 아닙니다. DB와 애플리케이션 사이에서 제한된 연결을 어떻게 나누고, 얼마나 빨리 반환하고, 어떤 상황에서 오래 점유되는지를 보는 것이 중요합니다.
운영 환경에서는 “DB 커넥션이 부족했다”에서 끝내지 말고 “어떤 애플리케이션이, 어떤 설정으로, 어떤 코드 경로에서 커넥션을 사용했는지”까지 확인해야 합니다. 그래야 같은 Too many connections 오류가 반복되는 것을 막을 수 있습니다.
정리하면, Too many connections는 DB 설정 하나만의 문제가 아니라 Java 애플리케이션의 커넥션 사용 방식과 운영 설정이 함께 드러나는 신호입니다. 원인을 나누어 확인하고, 전체 커넥션 예산을 기준으로 관리하는 것이 가장 안정적인 대응 방법입니다.
'개발 > JAVA' 카테고리의 다른 글
| [JAVA] JPA N+1 문제 발견하고 해결한 과정: 원인 분석부터 Fetch Join 적용까지 (0) | 2026.05.20 |
|---|---|
| [JAVA] Java Deadlock 발생했을 때 처리 전략: 원인 분석부터 재발 방지까지 (0) | 2026.05.19 |
| [JAVA] Java Could not open JPA EntityManager 에러 해결 방법 (0) | 2026.05.17 |
| [JAVA] Java Spring Transaction silently rolled back 문제 원인 분석 (1) | 2026.05.16 |
| [JAVA] LazyInitializationException 실무에서 해결한 방법 (0) | 2026.05.15 |
