Forest Gump?

Failed to obtain JDBC Connection의 이유 찾아 떠나기 본문

카테고리 없음

Failed to obtain JDBC Connection의 이유 찾아 떠나기

code1010 2023. 7. 6. 01:07

개요 

프로젝트를 진행 중, 간헐적으로 DB peak를 치는 현상이 발생했다 .

점점 사용하는 aws rds cpu 퍼센테이지가 상승 곡선을 그리면서 95프로 이상 alert이 자주 오길래(ㅠㅠ),

정말 위험함을 온몸으로 느끼고 해결 방안을 찾아봤다.

 

1차 임시 해결

 

메인 rds는 오라클 하나만 쓰고 있고 멀티 AZ 전략도 취하고 있지 않아서, 일단 무지성 클래스 유형 업그레이드를 했다. 

서비스는 해야하니까 올리긴 했지만, 평균 70-80사이였던 DB 평균 부하 30퍼센트대로 하락한걸 보고 마음이 많이 놓였다.

역시 돈이 최고다.. 라는 일그러진 생각이 들다가 근본적인 해결법이 아니라, 부하 피크 이유 분석을 시작했다.

 

원인

 

원인을 찾으려고, AWS RDS에서 제공하는 성능 계선 도우미에서 상위 호스트를 참조했다. 

요새 사용률이 오르고 있는 서비스 하나에서 유난히 많은 CPU사용량을 발견했다.

해당 서비스에 로그를 까보니, 아니나 다를까 다음과 오류가 간헐적으로 찍힌것을 발견했다. 

 

org.springframework.jdbc.CannotGetJdbcConnectionException: Failed to obtain JDBC Connection; nested exception is java.sql.SQLRecoverableException: IO 오류: Connection reset by peer, connect lapse 27640 ms., Authentication lapse 0 ms.
	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:153)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
	at jdk.internal.reflect.GeneratedMethodAccessor68.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)

 

콘솔에 찍힌 오류로는 JDBC connection 을 가져오지 못하는 에러가 표출됐지만,

 사실 JDBC 커넥션을 가져오지 못하는 이유는 너무 많아 원인 찾기에 어려움을 겪던 중 한줄기 빛을 발견했다!

  • spring 공식 문서에 나온 DriverManagerDataSource에 대한 설명
Class DriverManagerDataSource

NOTE: This class is not an actual connection pool; it does not actually pool Connections. It just serves as simple replacement for a full-blown connection pool, implementing the same standard interface, but creating new Connections on every call.


번역

참고: 이 클래스는 실제 연결 풀이 아닙니다. 실제로 연결을 풀링하지는 않습니다. 동일한 표준 인터페이스를 구현하지만 모든 호출에서 새 연결을 생성하는 본격적인 연결 풀을 간단히 대체하는 역할을 합니다.

 

문서에 따르면 실제 커넥션 풀을 가지고 있지 않고, 이 경우 요청마다 db Connection 이 증가하여 DB서버에 부하를 줄 수 있다고 한다.

 

해당 프로젝트를 찾아봤다. 그랬더니... 실제로 프로젝트에서 사용중이던 데이터소스 클래스는 DriverManagerDataSource

이며 설정값은 아래와 같았다.

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

 

요즘 대부분 부트를 사용하니,  당연히 해당 프로젝트도 스프링 기본 풀구조를 사용하고 있는 줄 알았던게 너무 안일한 생각이였다. 

 

2차 해결

기존 DriverManagerDataSource 로 적용되어 있던 프로젝트에 hikari cp를 적용했다.  

<bean id="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="registerMbeans" value="true"/> <!-- JMX 사용 -->
    <property name="maximumPoolSize" value="70"/>
    <property name="dataSourceProperties">
      <props>
        <prop key="implicitCachingEnabled">true</prop>
        <prop key="maxStatements">250</prop>
      </props>
    </property>
  </bean>

  <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
    <constructor-arg ref="hikariConfig"/>
  </bean>

 

 

변경된 코드를 rds 스냅샷 데이터 베이스에 부하 테스트를 진행하여 도입 전후 결과를 비교했다. 

진행했던 테스트 방법은 jmeter를 이용하여 api를 호출하는데 , 내부적인 로직으로 select 와 update 를 넣어

db connection 을 발생시키는 방식으로 진행했다.

아래는 jmeter 테스트에 대한 결과이다. 

 

요청 별 aws RDS 퍼센테이지 확인을 해봤다.

 

쓰레드 개수(개) 반복 횟수(회) 적용 전 rds cpu(%) 적용 후 rds cpu (%)
5000 1 42 2.8
7500 1 51 1.84
7500 2 75 2.6
7500 3 82 3.4
7500 5 76 4.64

 

결과

결과적으로 도입 후 현저히 낮아진 CPU를 볼 수 있었다 .. DB 피크로 마음 졸일 일을 덜하게 되서 너무 만족한다. 

실제로 서비스 사용량은 점점 많아짐에도, 안정적인 퍼센테이지를 유지하게 됐다.

 

참고 문서

 

 

   

 

[spring] db-connection(Feat.DriverManagerDataSource)

스프링개발을 하면서 data source에 개발시에는 org.springframework.jdbc.datasource.DriverManagerDataSource 사용하는경우가 많다.. 머 개발 하는 경우에는 그렇치만. 이게 실 서버에 적용이 된다면 어마어마한 사

hasiki.tistory.com

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/datasource/DriverManagerDataSource.html

 

DriverManagerDataSource (Spring Framework 6.0.10 API)

Set the JDBC driver class name. This driver will get initialized on startup, registering itself with the JDK's DriverManager. NOTE: DriverManagerDataSource is primarily intended for accessing pre-registered JDBC drivers. If you need to register a new drive

docs.spring.io