[SpringBoot] 결제 로직(4) - Unable to acquire JDBC Connection

포테이토웅·2024년 7월 20일
0

springboot-결제로직

목록 보기
4/6

🎇 이슈 발생

저번 게시글에서 REQUIRED_NEW를 이용해 트랜잭션을 분리해 데드락을 해결하였습니다.
그러나 동시성 제어 편에서 작성한 테스트 코드를 실행시키니 다른 오류가 발생했습니다.

위 오류의 발생 원인을 알기 위해서 우선 Connection Pool에 대해 알아보겠습니다.


❓ Connection Pool이란?

Connection Pool은 웹 애플리케이션과 같은 다중 사용자 환경에서 데이터베이스 연결을 효율적으로 관리하기 위해 사용됩니다.

클라이언트와 웹 어플레케이션에서 데이터베이스 연결을 위해 미리 일정수의 Connection 객체를 만들어 Pool에 담아둡니다. 추후 사용자의 요청이 발생하면 Pool에서 생성되어 있는 Connection 객체를 넘겨주고 처리가 끝나면, 실행된 상태로 Connection 객체를 다시 Pool에 반환하여 보관합니다.

자세한 내용은 우아한형제들 - HikariCP 글을 참고해주세요!


❓ HikariCP란?

HikariCP는 JDBC 연결 풀링을 위한 높은 성능을 제공하는 경량화된 커넥션 풀링(Connection Pooling) 라이브러리입니다. SpringBoot 2.0부터 기본 커넥션 풀링 라이브러리로 사용되고 있습니다.
HikariCP는 빠른 성능, 낮은 메모리 사용량, 그리고 다양한 설정 옵션을 제공하여 애플리케이션의 데이터베이스 연결 관리를 효율적으로 도와줍니다.


🛠️ 이슈 분석

@Transactional
public TossPaymentPayTransaction modifyProductStockQuantity(Map<Long, Integer> transactionMap, final String orderId) {
    // 결제 트랜잭션 정보 조회
    TossPaymentPayTransaction tossPaymentPayTransaction = tossPaymentPayTransactionRepository.findTossPaymentPayTransactionAndPayTransactionAndProductByOrderId(orderId)
        .orElseThrow(() -> new CustomException(ErrorCode.PAY_TRANSACTION_NOT_FOUND));

    // 상품 재고량 수정
    List<PayTransaction> payTransactions = tossPaymentPayTransaction.getPayTransactions();
    for (PayTransaction payTransaction : payTransactions) {
        final long productId = payTransaction.getProduct().getProductId();
        userProductService.decreaseProductQuantityWithLock(productId, payTransaction.getQuantity());
        transactionMap.put(productId, payTransaction.getQuantity());
    }
    return tossPaymentPayTransaction;
}

modifyProductStockQuantity 메소드가 실행되면 반복문을 돌면서 새로운 트랜잭션을 열어(REQUIRED_NEW) 재고량을 감소시킵니다. 이때, 새로운 트랜잭션이 만들어질 때마다 새로운 Connection이 생성됩니다. 따라서 해당 메소드를 실행하면 2개의 Connection이 필요하게 됩니다.

HikariCP의 기본 Connection Pool의 크기는 10입니다.

만약 10명의 사용자가 동시에 결제를 진행하게 되면, modifyProductStockQuantity가 실행되면서 각각 하나의 Connection을 가져가기 때문에 Connection Pool에는 남는 Idle Connection이 없습니다. 이때, 새로운 Connection을 열어야 하지만, Connection을 얻을 수 없어서 Idle Connection이 생길 때까지 대기하게 됩니다. 그러나 다른 메소드들도 같은 상태에 있기 때문에 아무도 Connection을 획득할 수 없게 됩니다.


📖 해결 방법

이러한 문제를 해결하기 위해서는 마법의 공식을 이용해 Maximum pool size를 설정해야합니다.

마법의 공식

pool size = Tn x (Cm - 1) + 1

  • Tn : 전체 Thread 갯수
  • Cm : 하나의 Task에서 동시에 필요한 Connection 수

저는 10명의 사용자가 있으며, 하나의 Task에서 2개의 Connection이 필요하므로 pool size = 10 x (2 - 1) + 1 = 11이 됩니다.

spring: 
	datasource:
        hikari:
            pool-name: hikari
            maximum-pool-size: 11

이렇게 HikariCP의 maximum-pool-size를 수정하고 테스트 코드를 실행하면 정상적으로 통과됩니다.


📗 참고 자료

https://techblog.woowahan.com/2663/

profile
주경야독

0개의 댓글