현재 getOrCreateUser(소셜 로그인) 메서드는 DB 커넥션 두 개가 필요하다.
@Transactional
getOrCreateUser ( ) {
1. id token 검증 - 공개키 생성 및 조회
2. user 조회
가입된 유저 -> 조회 결과 반환
미가입 유저 -> createUser 메서드 호출 ( REQUIRES_NEW )
3. access token 발급
}
max-thread-pool = 10인 상태에서, getOrCreateUser 메서드를 실행하면 10개의 스레드가 커넥션을 하나씩 점유하게 된다. 이때 모든 요청이 미가입된 유저였다면, 모든 스레드가 REQUIRES_NEW 메서드들 실행해야한다. 이렇게 되면, 모두가 서로가 가진 DB 커넥션을 기다리지만 획득할 수 있는 idle 상태의 커넥션이 없어서 리소스 고갈 문제가 발생한다.
확인해보자. max-thread-pool <= 20일 때, 다음 테스트는 DB 커넥션 타임 아웃으로 실패하게 된다.
int NUMBER_OF_THREADS = 20;
ExecutorService executorService = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch doneLatch = new CountDownLatch(NUMBER_OF_THREADS);
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
executorService.submit(() -> {
try {
startLatch.await();
authService.getOrCreateUser(requestDto); // 미가입 유저라고 가정
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
doneLatch.countDown();
}
});
}
startLatch.countDown(); // 20개 스레드가 authService.getOrCreateUser(requestDto) 실행 시작
doneLatch.await(); // doneLatch count가 0이 될 때까지 대기
max-thread-pool = 21부터는 테스트가 통과한다.
spring :
datasource:
hikari:
maximum-pool-size: 21
해결
불필요한 Transaction을 제거했다.

// @Transactional 제거
getOrCreateUser ( ) {
1. id token 검증 - 공개키 생성 및 조회
2. user 조회
가입된 유저 -> 조회 결과 반환
미가입 유저 -> createUser 메서드 호출 ( REQUIES_NEW -> REQUIRED로 변경 )
3. access token 발급
}
여담
원래 getOrCreateUser 메서드와 createUser 메서드는 같은 트랜잭션 소속이었다.
모종의 이유로 코드 리팩토링 과정에서 createUser 메서드에 REQUIRES_NEW를 사용하여 트랜잭션을 분리하게 되었고, 테스트 코드 작성 중 커넥션 고갈 문제를 인지했다.
이 문제를 해결하는 방법으로 커넥션 풀 사이즈를 조정하는 방법을 고민하다가, 뒤늦게 getOrCreateUser 메서드의 @Transactional의 필요성에 대해 생각하게 되었다.
✔️ 트래픽 기반 적절한 커넥션 풀 사이즈를 설정하는 방법
- 실제 상용 서비스였다면 모니터링 데이터를 기반으로 최적의 커넥션 풀 크기를 조정할 수 있었겠다. 그러나 실제 서비스가 아니라서, 가능하면 다른 방법을 시도해보고 싶었다.
✔️ 트랜잭션을 분리하지 않는 방법
- createUser 메서드는 닉네임 중복 시 예외를 던진다. 공개키를 생성하고 조회하는 작업의 비용은 비싼데, 닉네임을 재생성할 때마다 해당 작업을 반복하는 상황은 최대한 피하고 싶었다.
✔️minimum idle size= max-connection-pool size + 1 로 설정하는 방법
- 성능 상의 이유로 권장되지 않는 방법이다.
구글링을 해보면 이와 관련된 문제를 다루는 포스트들이 꽤 보인다. REQUIRES_NEW의 사용은 신중해야겠다...
Spring Transaction REQUIRES_NEW Propagation 지옥 (with Mybatis Local session cache)
무분별하게 설정 된 REQUIRES_NEW Propagation으로 인한 Connection Deadlock 현상
medium.com
https://techblog.woowahan.com/2663/
HikariCP Dead lock에서 벗어나기 (실전편) | 우아한형제들 기술블로그
1부 HikariCP Dead lock에서 벗어나기 (이론편)은 잘 보셨나요? 2부 HikariCP Dead lock에서 벗어나기 (실전편)에서는 실제 장애 사례를 기반으로 장애 원인을 설명하고 해결 사례를 공유하고자 합니다. 그
techblog.woowahan.com
'트러블슈팅' 카테고리의 다른 글
testImplementation 'org.testcontainers:rabbitmq:1.20.4' 2 vulnerabilities found in dependency (0) | 2025.01.01 |
---|---|
[DockerCompose]Error: "failed to create task for container: Unavailable: error reading from server (0) | 2024.10.27 |
[Redis Error] Unable to send PING command over channel (0) | 2024.10.22 |
springboot Could not move temporary workspace 오류 (0) | 2024.05.01 |
org.hibernate.sql 미작동 (0) | 2024.01.09 |