커넥션 풀
커넥션 풀은 데이터베이스 연결을 미리 만들어 두고 요청이 빌려 쓰고 반납하게 하는 기법입니다. DB 연결을 새로 맺는 데는 네트워크 연결, 인증, 세션 준비 비용이 들기 때문에, 요청마다 새로 맺지 않고 열린 연결을 재사용해 이 비용을 줄입니다.
풀은 연결 수에 상한을 두는 역할도 합니다. 동시에 DB로 가는 요청이 풀 크기를 넘으면 나머지는 기다리게 해서 DB 서버를 보호합니다. 이 토픽에서는 풀이 연결을 빌려 주고 돌려받는 과정, 풀 크기가 부족하거나 너무 클 때의 문제, 연결을 반납하지 않는 누수를 시각화로 확인합니다.
핵심 개념
연결 재사용
풀은 열린 연결을 보관하다 요청이 오면 빌려 주고, 끝나면 돌려받아 다음 요청에 다시 줍니다. 연결을 새로 만드는 비용을 줄입니다.
핵심 개념
풀 크기
풀이 동시에 빌려 줄 수 있는 연결의 최대 수입니다. 크기를 넘는 요청은 연결이 반납될 때까지 기다립니다.
핵심 개념
대기와 시간 초과
연결이 모두 사용 중이면 요청은 줄을 서고, 정해진 시간 안에 얻지 못하면 오류로 실패합니다. HikariCP에서는 connectionTimeout이 이 한도입니다.
핵심 개념
반납
풀에서 받은 연결의 close()는 연결을 끊지 않고 풀에 돌려줍니다. 빌린 연결은 반드시 반납해야 합니다.
핵심 개념
연결 누수
빌린 연결을 반납하지 않는 문제입니다. 쌓이면 풀이 비어 새 요청이 연결을 얻지 못하는 풀 고갈로 이어집니다.
핵심 개념
HikariCP
가볍고 빠른 자바 커넥션 풀 구현체입니다. 스프링 부트는 HikariCP가 클래스패스에 있으면 이를 기본 풀로 사용합니다.
커넥션 풀의 배경
웹 서비스가 요청마다 DB 연결을 새로 맺으면 요청이 몰릴 때 연결 생성이 병목이 되고, DB 서버의 연결 수 한도를 쉽게 넘깁니다. 자바에서는 JDBC의 DataSource 뒤에 풀을 두는 구조로 이 문제를 풀어 왔습니다.
자바 진영에는 Apache DBCP, C3P0, Tomcat JDBC Pool, HikariCP 같은 구현체가 있습니다. 스프링 부트 2.0부터 HikariCP가 기본 풀이 되었고, 설정은 spring.datasource.hikari 아래 속성으로 합니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
커넥션 풀을 쓰는 이유는?
DB 연결을 새로 맺는 비용을 줄이려고 미리 만든 연결을 재사용하고, 동시 연결 수를 제한해 DB를 보호하기 위해서입니다.
면접 질문
풀 크기를 무작정 크게 잡으면 어떻게 되나요?
DB 서버의 자원 부담과 경합이 늘어 성능이 오히려 떨어질 수 있습니다. 코어 수와 쿼리 특성을 고려해 적정 크기를 잡고 측정으로 조정합니다.
면접 질문
스프링 부트의 기본 커넥션 풀은?
HikariCP입니다. 스프링 부트 2.0부터 기본이고, maximum-pool-size 같은 속성으로 크기를 조정합니다.
면접 질문
연결 누수는 왜 위험하고 어떻게 막나요?
반납되지 않은 연결이 쌓이면 풀이 비어 새 요청이 실패합니다. try-with-resources 같은 구조로 항상 반납하고, 누수 감지 설정으로 오래 반납되지 않는 연결을 찾습니다.
활용 분야
함께 보면 좋은 토픽
참고 자료
SQL — PostgreSQL에서 연결 수와 상태 확인
SHOW max_connections; -- 서버가 받는 최대 동시 연결 수 (보통 기본 100) SELECT state, count(*) FROM pg_stat_activity WHERE datname = current_database() GROUP BY state; -- active / idle / idle in transaction 별 연결 수 SELECT pid, now() - xact_start AS open_time, query FROM pg_stat_activity WHERE state = 'idle in transaction' ORDER BY xact_start; -- 트랜잭션을 연 채 놀고 있는 연결 (반납이 늦는 후보)
PostgreSQL은 연결마다 서버 프로세스를 하나씩 띄우므로 max_connections로 동시에 받을 수 있는 연결 수를 제한하고, 기본값은 보통 100입니다. 애플리케이션 서버가 여러 대일 때는 서버마다 가진 풀 크기를 모두 더한 값이 이 한도를 넘지 않아야 합니다. pg_stat_activity의 state로 지금 연결이 쿼리를 실행 중인지(active), 놀고 있는지(idle), 트랜잭션을 연 채 놀고 있는지(idle in transaction) 볼 수 있습니다. 세 번째 쿼리에서 idle in transaction 연결이 오래 남아 있다면 연결을 쥔 채 트랜잭션을 끝내지 않는 코드가 있다는 신호입니다. state 값은 이 셋만 있는 것은 아니며, 권한이 없으면 다른 사용자 세션의 열은 NULL로 보일 수 있습니다. 시각화의 연결 누수 탭이 보여 주는 문제와 이어집니다.
Python — 큐로 만든 연결 풀
import queue class Pool: def __init__(self, size): self.q = queue.Queue() for i in range(size): self.q.put(f'conn{i + 1}') # 미리 만들어 둔 연결 def get(self, timeout): return self.q.get(timeout=timeout) # 빈 연결이 없으면 대기, 시간 초과 시 queue.Empty def put(self, conn): self.q.put(conn) # 반납: 연결은 끊지 않고 되돌려 놓음 pool = Pool(3) held = [pool.get(0.1) for _ in range(3)] print(held) try: pool.get(0.1) except queue.Empty: print('4번째 요청: 시간 초과') pool.put(held.pop(0)) print('반납 뒤 재사용:', pool.get(0.1)) # ['conn1', 'conn2', 'conn3'] # 4번째 요청: 시간 초과 # 반납 뒤 재사용: conn1
풀의 핵심을 큐로 옮긴 장난감 모델입니다. 시작할 때 연결 3개를 큐에 넣어 두고, 요청은 get으로 하나씩 꺼내 쓰고 put으로 돌려놓습니다. 연결 3개를 모두 빌린 뒤 네 번째 요청은 빈 연결이 없어 기다리다 시간 초과가 나고, conn1을 반납하자 바로 다음 요청이 같은 conn1을 받아 갑니다. 시각화의 풀의 동작, 풀 크기 탭과 같은 결과입니다. 실제 풀은 연결이 끊어졌는지 확인하고, 필요하면 새 연결을 만들며, 스레드 안전을 위한 처리를 더 합니다. 여기서는 큐가 스레드 안전한 것에 기대어 가장 단순한 형태만 보였습니다.
Java — HikariCP 설정과 안전한 반납
HikariConfig cfg = new HikariConfig(); cfg.setJdbcUrl("jdbc:postgresql://localhost:5432/app"); cfg.setMaximumPoolSize(10); // 풀 크기 (기본값 10) cfg.setConnectionTimeout(30_000); // 연결을 얻으려 기다리는 한도 ms (기본값 30초) cfg.setLeakDetectionThreshold(2_000); // 이보다 오래 반납 안 되면 로그 (기본값 0 = 꺼짐) try (HikariDataSource ds = new HikariDataSource(cfg)) { try (Connection conn = ds.getConnection(); PreparedStatement ps = conn.prepareStatement("SELECT 1")) { ps.executeQuery(); } // 블록을 나가면 close()가 연결을 풀에 반납 } // Spring Boot: spring.datasource.hikari.maximum-pool-size=10
HikariCP는 자바 풀 구현체이고 스프링 부트는 HikariCP가 있으면 기본으로 사용합니다. 풀 크기를 정하는 maximumPoolSize, 연결을 기다리는 한도인 connectionTimeout, 반납되지 않는 연결을 로그로 알려 주는 leakDetectionThreshold를 설정할 수 있습니다. 괄호 안의 기본값은 HikariCP 문서에 적힌 값입니다. 설정 값은 서비스 환경에 맞춰 정해야 합니다. try-with-resources로 Connection을 열면 예외가 나도 블록을 나갈 때 close()가 호출됩니다. 풀이 준 Connection의 close()는 연결을 끊지 않고 풀에 반납하므로, 이를 빠뜨리지 않는 것이 연결 누수를 막는 가장 확실한 방법입니다.
Q1
커넥션 풀을 사용하는 주된 이유는?
Q2
커넥션 풀 크기를 무작정 크게 잡으면 생기는 문제는?
Q3
스프링 부트가 기본으로 사용하는 커넥션 풀 구현체는?