JEUS DB Connection Leak 완벽 해결 방법
JEUS 서버를 운영하다 보면 Connection Leak(커넥션 누수) 문제로 골치를 앓게 되는 경우가 많습니다. 데이터베이스 연결이 적절히 닫히지 않아 커넥션 풀이 고갈되고, 결국 서버 성능 저하나 다운타임으로 이어지는 심각한 문제입니다.
이 글에서는 Connection Leak의 정확한 개념부터 JEUS 환경에서의 발생 원인, 탐지 방법, 그리고 코드와 설정 양쪽에서의 근본적인 해결 방법까지 모두 다룹니다. 실무에서 바로 적용할 수 있는 구체적인 설정값과 코드 예제를 제공하니 끝까지 읽어보시기 바랍니다.

Connection Leak은 데이터베이스 연결이 적절하게 닫히지 않아 Connection Pool에서 사용 가능한 커넥션이 점차 줄어드는 현상을 말합니다. 시간이 지남에 따라 가용 커넥션이 고갈되어 심각한 문제를 일으킵니다.
일반적인 커넥션 사용 흐름은 다음과 같습니다.
① 획득(Acquire): Connection Pool에서 커넥션 가져오기
② 사용(Use): SQL 쿼리 실행 및 데이터 처리
③ 반환(Return): 사용 완료 후 Connection Pool에 돌려주기
Connection Leak은 3번 단계가 누락되어 커넥션이 Pool로 반환되지 않고 계속 점유되는 상태를 의미합니다.
① 서버 성능 저하: 신규 요청이 커넥션을 획득하지 못해 대기 시간 증가
② 응답 시간 지연: 사용자 요청 처리가 느려져 UX 악화
③ 에러 발생: "Cannot get JDBC Connection" 등의 예외 발생
① 데이터베이스 과부하: DB 서버에 불필요한 세션이 계속 유지됨
② 애플리케이션 다운타임: 커넥션 풀 완전 고갈로 서비스 중단
③ 운영 비용 증가: 빈번한 서버 재시작과 긴급 대응 필요
Connection Leak이 발생하면 다음과 같은 증상이 나타납니다.
| 증상 | 설명 | 확인 방법 |
|---|---|---|
| 느린 응답 속도 | 페이지 로딩 시간 증가 | APM 도구로 응답 시간 측정 |
| 간헐적 타임아웃 | 특정 시간대 집중 발생 | 로그에서 Timeout 에러 검색 |
| DB 세션 증가 | 유휴 세션이 계속 증가 | DB 관리 도구로 세션 모니터링 |
| 메모리 사용량 증가 | JVM 힙 메모리 지속 상승 | JVM 모니터링 도구 사용 |
JEUS 서버 환경에서 Connection Leak이 발생하는 구체적인 원인을 파악하고, 효과적인 탐지 방법을 알아보겠습니다.
가장 흔한 원인은 개발자의 실수로 인한 커넥션 미반환입니다.
① finally 블록 누락: try-catch에서 close() 호출 없이 종료
② 예외 처리 오류: catch 블록에서 return하여 finally 미실행
③ 비동기 처리 문제: 멀티스레드 환경에서 커넥션 관리 미흡
④ ORM 설정 오류: Hibernate, MyBatis 세션 미종료
public List<User> getUsers() {
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users");
ResultSet rs = pstmt.executeQuery();
// 처리 로직...
// ❌ close() 호출 없음 - Connection Leak 발생!
return userList;
}
① Connection Pool 크기 부적절: 최대 커넥션 수가 너무 작음
② 타임아웃 미설정: abandoned-connection-timeout 값 없음
③ 유효성 검사 미흡: 좀비 커넥션 탐지 기능 비활성화
① 네트워크 불안정: DB 서버와 연결 끊김으로 커넥션 상태 불명
② 과도한 트래픽: 순간적인 부하 급증으로 리소스 부족
③ JVM 메모리 부족: GC 지연으로 finalize() 메서드 실행 지연
JEUS 관리 콘솔에서 실시간으로 커넥션 상태를 확인할 수 있습니다.
접속 경로: 모니터링 > 리소스 모니터링 > JDBC > DataSource 상태
확인 항목
- ActiveCount: 현재 사용 중인 커넥션 수
- IdleCount: 유휴 상태 커넥션 수
- MaxActive: 최대 커넥션 수
주의사항: ActiveCount가 지속적으로 증가하고 IdleCount가 0에 가깝다면 Leak 의심
tail -f $JEUS_HOME/logs/jeus_jdbc.log
# Connection Leak 관련 로그 검색
grep -i "Connection Abandoned" $JEUS_HOME/logs/jeus_jdbc.log
grep -i "Pool Exhausted" $JEUS_HOME/logs/jeus_jdbc.log
grep -i "Cannot get JDBC Connection" $JEUS_HOME/logs/jeus_jdbc.log
DB 서버에서 직접 활성 세션을 확인하여 누수를 탐지할 수 있습니다.
SELECT username, machine, program, process, status,
TO_CHAR(logon_time, 'YYYY-MM-DD HH24:MI:SS') AS logon_time
FROM v$session
WHERE username = 'APP_USER'
ORDER BY logon_time;
-- 1시간 이상 유지된 커넥션 확인
SELECT username, machine,
ROUND(last_call_et/3600, 2) AS "경과시간(시간)"
FROM v$session
WHERE username = 'APP_USER'
AND last_call_et > 3600;
SHOW FULL PROCESSLIST;
-- 특정 사용자의 연결 확인
SELECT * FROM information_schema.PROCESSLIST
WHERE USER = 'app_user'
AND TIME > 3600;
JEUS DataSource 설정을 통해 서버 레벨에서 Connection Leak을 자동으로 관리하는 방법을 알아보겠습니다.
가장 효과적인 해결책은 일정 시간 동안 사용되지 않는 커넥션을 자동으로 회수하는 것입니다.
<data-source>
<database>
<data-source-name>sampleDS</data-source-name>
<data-source-class-name>oracle.jdbc.pool.OracleDataSource</data-source-class-name>
<!-- Connection Pool 크기 설정 -->
<max-pool-size>100</max-pool-size>
<min-pool-size>20</min-pool-size>
<initial-pool-size>20</initial-pool-size>
<!-- ✅ Connection Leak 자동 처리 설정 -->
<abandoned-connection-timeout>300</abandoned-connection-timeout>
<remove-abandoned>true</remove-abandoned>
<log-abandoned>true</log-abandoned>
<!-- 커넥션 유효성 검사 -->
<validation-query>SELECT 1 FROM DUAL</validation-query>
<test-on-borrow>true</test-on-borrow>
</database>
</data-source>
| 설정 항목 | 권장 값 | 설명 |
|---|---|---|
| abandoned-connection-timeout | 300 (5분) | 커넥션이 이 시간 동안 사용되지 않으면 자동 회수 |
| remove-abandoned | true | abandoned 커넥션 자동 제거 활성화 |
| log-abandoned | true | 회수된 커넥션 정보를 로그에 기록 |
| test-on-borrow | true | 커넥션 사용 전 유효성 검사 |
주의: abandoned-connection-timeout 값이 너무 작으면 정상적인 장기 트랜잭션도 회수될 수 있으니 적절한 값을 설정하세요.
주기적으로 커넥션 상태를 검사하여 좀비 커넥션(Zombie Connection)을 예방합니다.
<database>
<!-- 유효성 검사 쿼리 -->
<validation-query>SELECT 1 FROM DUAL</validation-query>
<!-- 커넥션 대여 시 검사 -->
<test-on-borrow>true</test-on-borrow>
<!-- 커넥션 반환 시 검사 (선택) -->
<test-on-return>false</test-on-return>
<!-- 유휴 커넥션 주기적 검사 -->
<test-while-idle>true</test-while-idle>
<time-between-eviction-runs-millis>60000</time-between-eviction-runs-millis>
</database>
</data-source>
time-between-eviction-runs-millis: 60000 (1분마다 유휴 커넥션 검사)
애플리케이션의 동시 사용자 수와 DB 작업 비율을 고려하여 적절한 Pool 크기를 설정합니다.
최대 커넥션 수 = (동시 사용자 수 × DB 작업 비율) + 여유분
// 예시: 동시 사용자 1000명, DB 작업 10%, 여유 20%
max-pool-size = (1000 × 0.1) × 1.2 = 120
<database>
<!-- 소규모 애플리케이션 (동시 사용자 ~100명) -->
<max-pool-size>30</max-pool-size>
<min-pool-size>5</min-pool-size>
<!-- 중규모 애플리케이션 (동시 사용자 ~500명) -->
<max-pool-size>100</max-pool-size>
<min-pool-size>20</min-pool-size>
<!-- 대규모 애플리케이션 (동시 사용자 1000명+) -->
<max-pool-size>200</max-pool-size>
<min-pool-size>50</min-pool-size>
<!-- 유휴 커넥션 타임아웃 (10분) -->
<max-idle-time>600000</max-idle-time>
</database>
</data-source>
설정만으로는 부족합니다. 코드 레벨에서의 근본적인 개선과 지속적인 모니터링이 필요합니다.
Java 7 이상에서는 try-with-resources를 사용하여 자동으로 리소스를 닫을 수 있습니다.
public List<User> getUsers() throws SQLException {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
pstmt = conn.prepareStatement("SELECT * FROM users");
rs = pstmt.executeQuery();
// 데이터 처리...
return userList;
} catch (SQLException e) {
throw e;
} finally {
// 복잡하고 누락되기 쉬운 close() 처리
if (rs != null) try { rs.close(); } catch (SQLException e) {}
if (pstmt != null) try { pstmt.close(); } catch (SQLException e) {}
if (conn != null) try { conn.close(); } catch (SQLException e) {}
}
}
public List<User> getUsers() throws SQLException {
List<User> userList = new ArrayList<>();
try (
Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users");
ResultSet rs = pstmt.executeQuery()
) {
while (rs.next()) {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
userList.add(user);
}
return userList;
} // 자동으로 close() 호출됨
}
장점: try 블록을 벗어나면 자동으로 역순으로 close() 호출, 예외 발생 시에도 안전
Spring Framework를 사용한다면 JdbcTemplate을 활용하여 커넥션 관리를 완전히 위임할 수 있습니다.
@Repository
public class UserRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
// ✅ 커넥션 관리를 Spring이 자동 처리
public List<User> getUsers() {
String sql = "SELECT * FROM users";
return jdbcTemplate.query(sql, (rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
return user;
});
}
}
커넥션 사용률이 임계치를 초과하면 즉시 알림을 받을 수 있도록 구현합니다.
public class ConnectionMonitorService {
@Autowired
private DataSource dataSource;
// 5분마다 실행
@Scheduled(fixedRate = 300000)
public void checkConnectionPool() {
try {
int activeConnections = getActiveConnectionCount();
int maxConnections = getMaxConnectionCount();
double usageRatio = (double) activeConnections / maxConnections;
// 80% 이상 사용 시 경고
if (usageRatio > 0.8) {
String message = String.format(
"DB Connection Pool 경고: %.1f%% 사용 중 (%d/%d)",
usageRatio * 100, activeConnections, maxConnections
);
sendAlert(message);
}
} catch (Exception e) {
logger.error("Connection monitoring failed", e);
}
}
}
JEUS 서버의 JVM 설정을 최적화하여 간접적으로 Connection Leak 완화할 수 있습니다.
# JVM 힙 메모리 설정
java.option=-Xms2048m -Xmx4096m
# G1GC 사용 (권장)
java.option=-XX:+UseG1GC
java.option=-XX:MaxGCPauseMillis=200
# GC 로그 활성화
java.option=-Xlog:gc*:file=/logs/gc.log:time,uptime:filecount=5,filesize=10M
근본적 해결은 아니지만, 정기적인 재시작으로 누적된 문제를 해소할 수 있습니다.
# 매주 일요일 새벽 3시 JEUS 재시작
0 3 * * 0 /usr/local/jeus/bin/stopServer -host localhost -u admin -p password
5 3 * * 0 /usr/local/jeus/bin/startServer -host localhost
A: 애플리케이션의 가장 긴 트랜잭션 시간 + 여유분으로 설정하세요.
① 일반적인 웹 애플리케이션: 300초 (5분)
② 배치 작업이 있는 경우: 600초 (10분)
③ 대용량 처리 시스템: 1800초 (30분)
주의: 너무 짧으면 정상 트랜잭션도 중단되고, 너무 길면 Leak 탐지가 늦어집니다.
A: 부하 테스트를 통해 최적값을 찾아야 합니다.
시작 기준
최대 크기 = (CPU 코어 수 × 2) + 디스크 수
// 예시: 8코어 CPU, SSD 1개
min-pool-size = 8
max-pool-size = (8 × 2) + 1 = 17
이 값을 기준으로 부하 테스트하며 조정하세요.
A: finally 블록에서 반드시 close() 호출하되, 유틸리티 메서드를 만들어 사용하세요.
public class JdbcUtils {
public static void closeQuietly(Connection conn) {
if (conn != null) {
try { conn.close(); }
catch (SQLException e) {
logger.error("Connection close failed", e);
}
}
}
}
// 사용 예시
Connection conn = null;
try {
conn = dataSource.getConnection();
// 작업 수행
} finally {
JdbcUtils.closeQuietly(conn);
}
A: 즉시 조치 방법은 다음과 같습니다.
① JEUS 서버 재시작 (가장 빠른 방법)
$JEUS_HOME/bin/stopServer -host localhost -u admin -p password
# 서버 시작
$JEUS_HOME/bin/startServer -host localhost
② DB 세션 강제 종료
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;
③ DataSource 재시작 (JEUS 관리 콘솔)
- 리소스 > JDBC > DataSource 선택
- "재시작" 버튼 클릭
A: JEUS 환경에서 사용할 수 있는 모니터링 도구들입니다.
| 도구 | 특징 | 용도 |
|---|---|---|
| JEUS 관리 콘솔 | 기본 제공, 무료 | 실시간 커넥션 상태 확인 |
| JMX Console | JVM 레벨 모니터링 | 상세 메트릭 수집 |
| Scouter APM | 오픈소스, 한국어 지원 | 전체 성능 모니터링 |
| Jennifer | 상용, 강력한 기능 | 엔터프라이즈 모니터링 |
A: Spring Boot는 HikariCP를 기본 사용하며, application.properties로 설정합니다.
# Connection Pool 크기
spring.datasource.hikari.maximum-pool-size=100
spring.datasource.hikari.minimum-idle=20
# Leak 탐지 설정
spring.datasource.hikari.leak-detection-threshold=60000
# 커넥션 타임아웃
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.max-lifetime=1800000
HikariCP는 JEUS보다 더 강력한 Leak 탐지 기능을 제공합니다.
A: log-abandoned=true 설정 후 로그를 확인하세요.
[WARN] Connection abandoned:
java.sql.SQLException: Connection has been abandoned
at com.example.service.UserService.getUsers(UserService.java:45)
at com.example.controller.UserController.list(UserController.java:30)
# 스택 트레이스에서 문제 코드 위치 파악 가능
스택 트레이스를 보고 어느 코드에서 close()를 누락했는지 정확히 파악할 수 있습니다.
JEUS 서버의 Connection Leak 문제는 여러 요소가 복합적으로 작용하여 발생합니다. 단일 해결책은 없으며, 다층적 접근이 필요합니다.
핵심 해결 전략
① JEUS 설정: abandoned-connection-timeout, remove-abandoned 활성화
② 코드 개선: try-with-resources 패턴 적용
③ 모니터링: 실시간 알림 시스템 구축
④ 예방: 정기적인 코드 리뷰와 부하 테스트
즉시 적용 가능한 조치
① domain.xml에 abandoned 설정 3줄 추가
② 모든 JDBC 코드를 try-with-resources로 변경
③ 커넥션 사용률 모니터링 스케줄러 추가
Connection Leak은 완전히 제거하기보다는 지속적인 관리가 중요합니다. 설정과 코드 개선으로 누수를 최소화하고, 모니터링으로 조기에 발견하여 대응하는 것이 최선의 방법입니다.
이 글에서 제시한 방법들을 단계적으로 적용하면 안정적인 JEUS DB 서버 환경을 유지할 수 있습니다. 정기적인 성능 테스트와 부하 테스트를 통해 잠재적 문제를 사전에 발견하고 대응하세요.
끝.
'Development > Web' 카테고리의 다른 글
| [Web] Apache Kafka 완벽 가이드 (0) | 2025.10.24 |
|---|---|
| [Web] Spring Boot 3.4 + AWS 환경 Tomcat vs JBoss WildFly 성능 비교 분석 (3) | 2025.04.07 |
| [Web] Spring Dispatcher Servlet 정의와 동작 원리 (0) | 2022.09.24 |
| [Web] Spring Framework vs Spring Boot 핵심 차이점 완벽 비교 (0) | 2022.09.24 |
| [Web] Spring Framework 3가지 핵심 특징 - IoC, DI, AOP 완벽 이해 (0) | 2022.09.24 |