스프링 부트로 서버를 개발하다 보면 데이터베이스 연결을 관리하는 Connection Pool을 자주 마주치게 됩니다. Thread Pool에 이어 Connection Pool에 대해 알아보겠습니다.
먼저 전체적인 개념을 이해해보겠습니다:
Pool 패턴은 비용이 많이 드는 리소스를 미리 생성해두고 재사용하는 디자인 패턴입니다.
이전 글에서 다룬 Thread Pool도 같은 패턴을 사용합니다:
Thread Pool과 Connection Pool은 비슷하면서도 다른 특징을 가집니다:
// Thread Pool 사용 예
@GetMapping("/users")
public List<User> getUsers() {
// 1. Tomcat Thread Pool의 스레드가 요청을 처리
return userRepository.findAll(); // 2. 이 때 Connection Pool에서 연결을 가져와 사용
}
주요 차이점:
새로운 데이터베이스 연결을 만드는 과정은 많은 비용이 듭니다:
이러한 과정을 매 요청마다 반복하면 성능이 크게 저하됩니다.
스프링 부트 2.0부터는 HikariCP가 기본 Connection Pool로 채택되었습니다.
뛰어난 성능
단순한 설정
HikariCP의 주요 구성요소:
HikariDataSource
├── Connection Pool (ConcurrentBag)
│ ├── idle connections (사용 가능한 연결들)
│ └── in-use connections (사용 중인 연결들)
└── Connection Factory
└── 새로운 연결 생성
HikariCP의 주요 설정값과 기본값:
spring:
datasource:
hikari:
maximum-pool-size: 10
minimum-idle: 10
idle-timeout: 600000
connection-timeout: 30000
max-lifetime: 1800000
각 설정의 의미:
실제 서비스에 맞는 설정 방법을 알아보겠습니다.
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: user
password: pass
hikari:
maximum-pool-size: 50
minimum-idle: 20
idle-timeout: 300000 # 5분
일반적인 권장사항:
1. CPU 코어 수를 고려
2. 데이터베이스 서버의 최대 연결 수 확인
3. 실제 부하 테스트로 검증
개발 중에는 로그를 통해 Connection Pool의 상태를 확인할 수 있습니다:
spring:
datasource:
hikari:
pool-name: "MyPool" # 로그에서 구분하기 쉽게 풀 이름 지정
leak-detection-threshold: 30000 # 커넥션 누수 감지 (30초)
logging:
level:
com.zaxxer.hikari.HikariConfig: DEBUG
com.zaxxer.hikari.pool.HikariPool: DEBUG
이렇게 설정하면 HikariCP의 동작 상태를 로그로 확인할 수 있습니다:
Thread Pool과 Connection Pool이 어떻게 함께 동작하는지 살펴보겠습니다.
하나의 요청 처리 과정:
@Service
public class UserService {
@Transactional
public User createUser(UserDto dto) {
// 1. Tomcat Thread Pool의 스레드가 이 메소드를 실행
// 2. 트랜잭션 시작 - Connection Pool에서 연결 획득
// 3. DB 작업 수행
// 4. 트랜잭션 종료 - Connection Pool에 연결 반환
return userRepository.save(new User(dto));
}
}
Thread Pool과 Connection Pool은 서로 균형이 중요합니다:
Thread Pool이 Connection Pool보다 너무 클 때:
Connection Pool이 Thread Pool보다 너무 클 때:
균형잡힌 설정의 예:
일반적인 데이터베이스 요청 처리 흐름:
코드로 보면:
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable Long id) {
// Tomcat Thread Pool의 스레드
try {
// Connection Pool에서 연결 획득
return userRepository.findById(id)
.orElseThrow(() -> new NotFoundException());
} finally {
// Connection은 자동으로 Pool에 반환
}
}
이렇게 설정한 Thread Pool과 Connection Pool은 서로 밀접하게 연관되어 동작하며,
두 Pool의 적절한 설정은 애플리케이션의 성능에 큰 영향을 미칩니다.
이상으로 스프링 부트 서버의 Connection Pool에 대해 개략적으로 알아보았습니다.