application.yaml에 hikari와 관련된 설정을 별 생각없이 했었고, connection pool에 대한 이해도도 낮았습니다. 그런데, 얼마 전 connection pool의 중요성을 알게 되는 상황이 생겼습니다. 트래픽이 급증하는 상황에서 connection 이 없어서 스레드는 계속 대기하고 서비스가 엄청 느려지는 상황이 발생했습니다. 뿐만 아니라 속도가 느린 API가 계속해서 connection을 잡아먹고 있어서 다른 가벼운 API들이 수행되지 못하는 경우도 있었구요. 그래서 그 때 connection pool이 궁금해졌습니다. "connection pool은 뭐고, connection은 어떨 때 가져오는거지?" 그래서 이를 이해하고자 글을 쓰게 됐습니다.
Spring에서는 성능과 동시성 때문에, 사용 가능한 connection pool 중에서 우선순위가 높은 걸 사용한다고 합니다.
Spring에서는 HikariCP를 가장 선호하고 대부분의 경우 사용하기 때문에, 이 글에서는 Hikari를 기준으로 살펴볼 것입니다.
빠르고 간단한 JDBC connection pool 라이브러리인데요. spring-boot-starter-jdbc 나 spring-boot-starter-data-jpa starters 를 사용하면, 자동으로 dependency에 포함되기 때문에, 추가할 필요가 없습니다.
HikariCP는 다음과 같은 설정값들을 가지는데, 디폴트값이 있기 때문에 필요한 것들만 수정해서 쓰시면 됩니다.
| 설정 | 설명 | 디폴트값 |
|---|---|---|
| connectionTimeout | pool에서 connection을 얻기까지 대기하는 시간의 최댓값 | 30000(30초) |
| idleTimeout | connection이 pool에서 유휴상태로 유지될 수 있는 시간의 최댓값 | 600000(10분) |
| keepaliveTime | 유휴 connection이 유지될 수 있도록 keep-alive 패킷을 보내는 주기 | 120000(2분) |
| maxLifetime | connection의 최대 수명 주기 | 18000000(30분) |
| minimumIdle | 유휴 connection의 최소 개수 | 10개 |
| maximumPoolSize | connection의 최대 개수 | 10개 |
| poolName | connection pool의 이름 | auto-generated |
wait_timeout으로 인해 만들어둔 connection들이 죽지 않도록 keepaliveTime 에 설정된 주기마다 데이터베이스로 살아있다는 신호를 보냅니다. 미처 신호를 늦게 보내서 이미 connection이 종료된 후일 수 있지만, 어차피 minimumIdle만큼 유지되도록 생성될 것이기 때문에 걱정할 것 없습니다.고려할 것은 많겠지만, 공식 문서에서 권장하는 하나의 조건을 설명하고 넘어가볼까 해요!
minimumIdle은 별도로 설정하지 않고, maximumPoolSize와 동일하게 설정하여 pool size를 고정하는 것을 추천합니다.
minimumIdel이 maximumPoolSize보다 작다면, connection 개수는 유동적으로 움직이게 됩니다. 필요한 곳이 많다면, maximum까지 만들었다가 끝나면 minimum까지 줄어들겠죠.
그렇게 되면, 필요할 때마다 생성되어야하기 때문에 응답이 늦어질 수 있습니다. 공식 문서에서 추천하는 방식대로라면 minimum과 maximum을 동일하게 설정하여 고정되면, 항상 동일한 커넥션 개수를 유지하게 됩니다. 물론 이건 상황에 따라 달라질 수 있겠지만, 일반적으로는 고려해보면 좋을 요소인 것 같습니다.
JPA와 MyBatis에서 모두 테스트해봤을 때, 둘 다 @Transactional 어노테이션 사용 여부에 따라 connection 획득 시점이 달라진 걸 확인할 수 있었습니다.
| 상황 | Connection 가져오는 시점 |
|---|---|
| @Transactional 없음 | 실제 SQL 실행 시점 (즉, Statement 실행 시) |
| @Transactional 있음 | 트랜잭션이 시작되는 즉시 Connection을 가져옴 |
왜 @Transactional 어노테이션 유무에 따라 connection 획득 시점이 달라질까?
획득 시점이 달라지는 이유에 앞서 connection을 획득하는 과정을 먼저 설명드릴게요!
트랜잭션이 시작되면, 아래와 같은 과정을 통해 connection을 가져오게 됩니다.
AbstractPlatformTransactionManager는 기존에 트랜잭션이 있으면 참여하고, 없으면 새로운 트랜잭션 시작JpaTransactionManager 의 doBegin() 메서드를 호출메서드에서 connection을 가져오는 부분은 바로 여기입니다! 즉, 참여하고 있는 트랜잭션이 없을 경우, 트랜잭션을 시작하고 그 때 호출되는 doBegin() 메서드에서 새로운 connection을 얻어오는거죠.

그래서 이게 무슨 상관이냐! 왜 @Transactional 어노테이션 사용 유무에 따라 달라지는 것이냐! 이제 설명드리겠습니다.
@Transactional 을 사용하든 안하든 트랜잭션이 생성될 때 connection을 가져오는 것은 맞는데요. 트랜잭션이 시작되는 시점이 다르기 때문입니다.
| 상황 | Transaction이 시작되는 시점 |
|---|---|
| @Transactional 없음 | 실제 SQL 실행 시점 (즉, Statement 실행 시) |
| @Transactional 있음 | 메서드가 수행되는 시점에 Connection을 가져옴 |
테스트로 확인해볼까요? 저는 아래와 같이 테스트를 위해 코드를 작성했습니다.
@Slf4j
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final ConnectionPoolMonitorService connectionPoolMonitorService;
public String getUserNameById(long id) {
log.info("-------- [UserService getUserNameById START] --------");
connectionPoolMonitorService.printConnectionPoolStatus();
Optional<User> user = userRepository.findById(id);
if (user.isEmpty()) {
throw new IllegalArgumentException("id에 해당하는 사용자가 없습니다.");
}
String userName = user.get().getName();
log.info("-------- [UserService getUserNameById END] --------");
return userName;
}
}

어노테이션이 붙지 않은 경우 서비스가 시작되었다는 로그까지 찍히고, findById() 를 실행시킬 때 getTransaction() 이 호출됩니다. 반면 어노테이션이 붙은 경우, 서비스단의 메서드를 호출할 때 바로 트랜잭션이 시작됩니다.

너무 당연하게 알고 계신 분들도 있으시겠지만, 저는 실제로 테스트해보면서 확인한 건 처음이라 넣어봤습니다.
그럼 여기서 생각해볼만한 거리가 있어요! @Transactional 어노테이션을 사용하는 경우, 서비스 메서드가 시작되는 시점에 connection을 가져오는 게 효율적일까?🧐
아래와 같은 상황들을 고려해봅시다. 이런 경우에는 실제 쿼리를 수행하려고 할 때 connection을 가져오는 게 효율적일 수도 있습니다. connection은 한정되어있기 때문에 너무 오래잡고 있는 건 좋지 않기 때문입니다.
connection을 가져올 때 proxy로 감싼 객체를 반환해서 statement가 만들어질 때 connection을 가져올 수 있도록 해준다고 합니다.
자세한 사용방법은 생략하도록 하겠습니다. 더 궁금하신 분은 아래 문서 참고하시기 바랍니다.
https://docs.spring.io/spring-framework/docs/6.2.3/javadoc-api/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.html
차근차근 너무 글을 잘 정리해주셔서 이해가 잘 되네요! 감사합니다.