본문 바로가기
IT/java by 하루 또다시 하루 2026. 6. 5.
반응형

Spring Boot 애플리케이션을 운영하다 보면 처음에는 멀쩡하다가 시간이 지날수록 DB 연결이 느려지거나, 특정 스케줄러 실행 후 커넥션 풀이 바닥나는 상황을 겪게 됩니다. 커넥션 누수는 그 특성상 즉각적으로 에러를 뿜지 않고 서서히 시스템을 마비시키기 때문에 원인을 찾기가 까다롭습니다.

결론부터 말씀드리면, "Connection is not available" 오류가 났을 때 무작정 풀 사이즈부터 늘려서는 안 됩니다. HikariCP 상태 로그와 leakDetectionThreshold 설정을 이용해 실제로 커넥션이 반환되지 않고 누수되고 있는지, 아니면 단순히 쿼리가 오래 걸리는 것인지부터 확인해야 합니다.

커넥션 누수를 의심해야 하는 대표적 증상

커넥션 누수가 발생하면 보통 다음과 같은 연쇄적인 증상이 나타납니다.

  • 트래픽이 많지 않은데도 DB 요청이 점점 느려짐
  • 특정 스케줄러나 배치 작업 실행 직후, 다른 일반적인 DB 작업들이 밀리기 시작함
  • 로그를 확인했을 때 HikariCP의 active 커넥션 수가 줄어들지 않고 계속 높게 유지됨
  • Connection is not available 오류가 반복적으로 발생함
  • 특정 API를 호출한 뒤에만 유독 위와 같은 문제들이 반복됨

여기서 가장 중요한 핵심은 커넥션 풀이 부족하다는 현상 자체가 무조건 누수를 의미하지는 않는다는 것입니다. 쿼리 성능이 떨어져서 커넥션을 오래 쥐고 있는 상황과 명확히 구분해야 합니다.

왜 이런 문제가 발생할까?

1) 커넥션을 반환하지 않는 코드가 있는 경우

직접 JDBC API(Connection, PreparedStatement, ResultSet)를 사용하는 레거시 코드나 특정 유틸리티에서 자원을 명시적으로 닫지 않으면 커넥션이 풀로 돌아가지 못합니다. Spring의 JdbcTemplate이나 MyBatis를 사용하면 프레임워크가 알아서 반환해 주지만, 수동으로 제어하는 구간이 단 한 곳이라도 있다면 누수 지점이 될 수 있습니다.

2) 트랜잭션 범위가 너무 긴 경우

커넥션 누수처럼 보이지만, 실제로는 트랜잭션이 불필요하게 길게 유지되는 경우가 실무에서 훨씬 더 많습니다. 비즈니스 로직 내부에 외부 API 호출이나 무거운 파일 처리가 포함되어 있다면, 그 작업이 끝날 때까지 DB 커넥션은 반환되지 않고 묶여 있게 됩니다.

커넥션 누수 확인 및 해결을 위한 4단계 가이드

1단계. HikariCP Pool 상태 로그 활성화 및 확인

가장 먼저 현재 커넥션 풀의 상태를 파악해야 합니다. 설정 파일에 아래 값을 추가하여 로그를 활성화합니다.

Properties
 
logging.level.com.zaxxer.hikari=DEBUG

로그를 통해 active, idle, total, waiting 값을 확인합니다. 만약 active 수치가 계속 높게 유지되고 idle 상태의 커넥션이 거의 없다면, 커넥션이 어딘가에서 오래 사용 중이거나 반환되지 않고 있을 가능성이 큽니다.

2단계. leakDetectionThreshold 설정으로 범인 찾기

HikariCP는 특정 시간 이상 커넥션이 반환되지 않으면 누수를 의심하고 해당 커넥션을 점유한 코드의 스택 트레이스(Stack Trace)를 로그로 남겨줍니다.

Properties
 
spring.datasource.hikari.leak-detection-threshold=5000

위 설정은 커넥션을 5초(5000ms) 이상 쥐고 있을 때 경고 로그를 남깁니다. 이 로그에 찍힌 코드를 확인하면 어느 지점에서 커넥션을 오래 잡고 있는지 정확히 추적할 수 있습니다.

3단계. 트랜잭션 범위 분리하기

누수 탐지 로그에 찍힌 코드가 @Transactional 어노테이션을 사용하고 있다면 내부 로직을 점검해야 합니다.

Java
 
@Transactional
public void process() {
    // DB 조회
    // 외부 API 호출 (시간 지연 유발)
    // DB 저장
}

이 구조에서 외부 API 호출 시간이 길어지면 트랜잭션도 길어지며 커넥션 반환이 지연됩니다. 외부 API 호출, SSH 접근, 무거운 파일 I/O 작업 등은 트랜잭션 범위 밖으로 분리하는 것이 좋습니다.

4단계. 직접 JDBC 사용 코드 점검하기

JPA나 MyBatis를 거치지 않고 직접 커넥션을 열어 쓰는 코드가 있다면, 반드시 Java 7부터 지원하는 try-with-resources 문법을 사용해야 합니다.

Java
 
try (
    Connection conn = dataSource.getConnection();
    PreparedStatement ps = conn.prepareStatement(sql);
    ResultSet rs = ps.executeQuery()
) {
    // 데이터 처리 로직
}

직접 연 자원은 반드시 직접 닫아야 합니다. close() 호출이 누락되어 있거나 예외 발생 시 제대로 닫히지 않는 구조라면 치명적인 누수 원인이 됩니다.

실전에서 자주 놓치는 부분

  • 무작정 Pool Size 늘리기: 오류가 난다고 풀 사이즈를 바로 늘리는 것은 임시방편일 뿐입니다. 커넥션을 오래 잡고 있는 코드를 수정하지 않으면 시간이 지나 결국 같은 문제가 반복됩니다.
  • leakDetectionThreshold 값을 운영 환경에서 너무 짧게 유지하기: 개발이나 원인 분석 중에는 유용하지만, 평소 쿼리가 무거워 5초 이상 걸리는 시스템에 이 값을 너무 짧게 두면 로그가 과도하게 쌓이고 오버헤드가 발생할 수 있습니다. 운영 환경에서는 상황에 맞게 적절히 조정해야 합니다.

마무리

Spring Boot 커넥션 누수는 실제 누수인지, 아니면 단순히 오래 걸리는 쿼리인지 구분하는 것이 문제 해결의 시작입니다.

HikariCP 로그로 풀 상태를 먼저 확인하고, leakDetectionThreshold를 활용해 커넥션을 오래 잡고 있는 코드를 찾아내십시오. 그 후 트랜잭션 범위와 직접 JDBC를 사용하는 코드를 점검하면 됩니다. 급한 마음에 풀 사이즈를 늘리기 전에 반드시 active, idle, waiting 로그부터 확인하시기 바랍니다.

반응형