상황에 따른 Hikari CP의 커넥션 풀 상태가 궁금해졌습니다.
@Transactional 어노테이션에 따라 active 가 되고 언제 다시 idle로 돌아가는지 알아보기 위해 모니터링을 위한 준비를 했습니다.
다음과 같은 클래스 구조에서 확인해 보겠습니다.
다음과 같이 테스트 합니다.
1. 먼저 facade 클래스의 메서드가 실행되면 Thread.sleep으로 많은 일을 처리했다고 가정합니다.
2. @Transactional 어노테이션이 걸려있는 서비스의 메서드를 호출합니다.
각 과정 사이에 HikariCP 로그를 찍어 Connection pool 상태를 확인합니다.
자세한 소스코드는 다음 github를 참고해 주세요.
먼저 HikariCP를 모니터링하기 위해 application.yml 에 다음 설정을 추가합니다.
소스코드
application.yml
spring:
datasource:
hikari:
register-mbeans: true
pool-name: hikari
HikariPoolMXBean을 사용하기 위해 다음과 같이 빈을 등록해 줍니다.
package com.infitry.laboratory.config;
import com.zaxxer.hikari.HikariPoolMXBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
@Configuration
public class HikariConfig {
@Bean
public HikariPoolMXBean hikariPoolMXBean() throws MalformedObjectNameException {
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("com.zaxxer.hikari:type=Pool (hikari)");
return JMX.newMBeanProxy(mBeanServer, objectName, HikariPoolMXBean.class);
}
}
위 그림의 실제 비즈니스 로직이 동작하는 Facade 클래스입니다.
ConnectionPoolFacade.java
package com.infitry.laboratory.service.transaction.connectionpool;
import com.zaxxer.hikari.HikariPoolMXBean;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class ConnectionPoolFacade {
private final ConnectionPoolService connectionPoolService;
private final HikariPoolMXBean hikariPoolMXBean;
private static final int LATENCY = 3000;
public void doSomethingBefore() throws Exception {
printCurrentConnectionPool("1");
sleep();
printCurrentConnectionPool("2");
connectionPoolService.whenGetConnectionInTransactional();
printCurrentConnectionPool("3");
}
public void doSomethingAfter() throws Exception {
printCurrentConnectionPool("1");
connectionPoolService.whenGetConnectionInTransactional();
printCurrentConnectionPool("2");
sleep();
printCurrentConnectionPool("3");
}
private static void sleep() throws InterruptedException {
log.info("start sleep!!!");
Thread.sleep(LATENCY);
log.info("end sleep!!");
}
private void printCurrentConnectionPool(String id) {
log.info("{}, (total={}, active={}, idle={}, waiting={})", id,
hikariPoolMXBean.getTotalConnections(), hikariPoolMXBean.getActiveConnections(),
hikariPoolMXBean.getIdleConnections(), hikariPoolMXBean.getThreadsAwaitingConnection());
}
}
ConnectionPoolService.java
package com.infitry.laboratory.service.transaction.connectionpool;
import com.infitry.laboratory.persistence.jpa.MemberRepository;
import com.zaxxer.hikari.HikariPoolMXBean;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ConnectionPoolService {
private final MemberRepository memberRepository;
private final HikariPoolMXBean hikariPoolMXBean;
@Transactional
public void whenGetConnectionInTransactional() throws Exception {
printCurrentConnectionPool();
memberRepository.findAll();
Thread.sleep(2000);
}
private void printCurrentConnectionPool() {
log.info("in transaction, (total={}, active={}, idle={}, waiting={})",
hikariPoolMXBean.getTotalConnections(), hikariPoolMXBean.getActiveConnections(),
hikariPoolMXBean.getIdleConnections(), hikariPoolMXBean.getThreadsAwaitingConnection());
}
}
테스트
먼저 doSomethingBefore()를 실행해 보면?
1. 처음엔 idle connection 이 5개입니다.
2. sleep 이후 에도 마찬가지로 active connection 이 없습니다.
3. 트랜잭션이 실행되는 순간 idle connection 이 4개, active connection이 1개가 됩니다.
4. 트랜잭션이 종료된 후 idle connection 이 다시 5개, active connection이 1개가 됩니다.
doSomethingAfter() 도 마찬가지로 실행됩니다.
정리
다음 결과로 트랜잭션 범위에 대한 중요성을 깨달을 수 있습니다.
트랜잭션 범위를 넓게 잡고 트랜잭션 범위 안에서 시간이 오래 걸리는 작업이 있다면 ex) API 호출
hikari CP의 connection을 사용하여 반환하지 않습니다.
결국 커넥션이 모두 소진되어 waiting 이 늘어 갈 것이고 오랜 시간 커넥션을 얻지 못한 요청은 오류가 발생할 것입니다.
많은 사용자의 요청을 처리하기 위해서는 트랜잭션의 범위를 설정하는데 항상 주의가 필요한 것 같습니다.
'백엔드 > Spring Boot' 카테고리의 다른 글
스프링부트 3.x 에서 트레이싱 기능 추가하기 (0) | 2024.04.07 |
---|---|
Spring, Spring Boot 버전 별 접미사 (0) | 2022.07.18 |
logback 취약점 (CVE-2021-42550) (0) | 2021.12.21 |
웹 페이지 성능 최적화 (0) | 2021.11.22 |