Connection Pool
JDBC: 관계형 데이터베이스에 저장된 데이터를 접근 및 조작할 수 있게 하는 자바 API.
java application이 다양한 DBMS에 대해 일관된 API로 데이터베이스 연결, 검색, 수정, 관리 등을 할 수 있게 함.
네트워크상에 있는 데이터베이스에 접속할 수 있도록 해주는 데이터베이스 연결 기능 제공
JDBC Driver Manager : 자바 어플리케이션이 사용하는 db에 맞는 JDBC 드라이버 찾아서 로드
JDBC Driver : 각 데이터베이스 개발사에서 만든 데이터베이스 드라이버
Connection Pool : 데이터베이스에 접근할 때마다 새로운 커넥션을 만드는 것이 아닌, 어플리케이션 시작시 미리 커넥션 n개를 Pool(저장소)에 저장해놓고필요할 떄마다 꺼내 쓰고, 사용후 다시 반납하는 방식
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DbUtils {
public DbUtils(){
throw new IllegalStateException("Utility class");
}
public static Connection getConnection() {
Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://0.0.0.0:3306","academy_32","password");
} catch (SQLException e) {
throw new RuntimeException(e);
}
return connection;
}
}
(기존의 커넥션 획득 방법)
public class DbUtils {
// 인스턴스화 방지
private DbUtils() {
throw new IllegalStateException("Utility class");
}
private static final DataSource DATASOURCE;
static {
BasicDataSource basicDataSource = new BasicDataSource();
try {
// MySQL 드라이버 설정
basicDataSource.setDriver(new com.mysql.cj.jdbc.Driver());
} catch (SQLException e) {
throw new RuntimeException("MySQL 드라이버 로드 실패", e);
}
// DB 연결 URL, 사용자명, 비밀번호 설정
basicDataSource.setUrl("jdbc:mysql://0.0.0.0:3306/academy_32");
basicDataSource.setUsername("academy_32");
basicDataSource.setPassword("password");
// 풀 크기 설정
basicDataSource.setInitialSize(5);
basicDataSource.setMaxTotal(5);
basicDataSource.setMaxIdle(5);
basicDataSource.setMinIdle(5);
// 커넥션 유효성 검사
basicDataSource.setTestOnBorrow(true);
basicDataSource.setValidationQuery("select 1");
basicDataSource.setMaxWait(Duration.ofSeconds(2));
DATASOURCE = basicDataSource;
}
커넥션 풀을 사용하는 예시
javax.sql.DataSource 인터페이스를 각 라이브러리마다 구현하여 사용자는 이를 사용(BasicDataSource, HikariDataSource.. 이런것들이 커넥션 풀)
커넥션 풀은 어디에 존재하는가
1.자바코드에서 직접 생성시 (new BasicDataSource()같이), 프로세스 메모리 힙 영역에 존재
2.톰캣에서 직접 커넥션풀 사용하고싶다면 코드밖에서 설정해야함
2-1)톰캣에 DBCP2 라이브러리 추가
commons-dbcp2, commons-pool2, commons-logging, commons-collections4, javax.servlet-api 등의 jar 파일을 톰캣디렉터리위치/lib 에 복사
2-2) context.xml 또는 server.xml 등에 DBCP2 직접 설정
톰캣디렉터리위치/conf/context.xml 또는 개별 웹앱의 META-INF/context.xml에 아래의 예시같이 작성
<Context>
<Resource name="jdbc/MyDB"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.commons.dbcp2.BasicDataSourceFactory"
driverClassName="cohttp://m.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/mydb"
username="myuser"
password="mypassword"
maxTotal="50"
maxIdle="20"
minIdle="5"
maxWaitMillis="10000"
validationQuery="SELECT 1"
testOnBorrow="true"/>
</Context>
2-3) 자바 코드에서 아래와 같이 사용
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/MyDB"); <- 2-2번에서의 리소스명과 매핑되어야함.
java:comp/env/는 JNDI의 표준 네이밍 컨벤션에 따른 것이며, 톰캣(JNDI 구현체 포함)에서는 반드시 이 prefix를 붙여야 애플리케이션에서 context.xml에 설정한 자원을 찾을 수 있음.
최적화
Connection Pool Size == Tomcat Thread Pool Size
톰캣을 이용할때, Connection Pool사이즈를 톰캣의 Thread Pool사이즈로 맞춰주는 것이 좋음
WAS의 쓰레드는 요청을 받아서 DB작업이 필요한 경우 Connection Pool에서 커넥션을 가져가 사용함. 이때 가져갈 커넥션이 없으면 대기하게 된다. 지연발생
톰캣이 처리가능한 최대요청수와 커넥션 풀의 최대 커넥션수를 일치시키면, Thread가 DB커넥션을 기다리지 않고 즉시 사용할 수 있어 성능병목을 줄일 수 있음.
커넥션 풀의 사이즈와 쓰레드풀의 사이즈를 같이할때, 이론적으로 모든 요청에 DB 커넥션을 제공할 수 있지만 다음과 같은 문제들이 발생할 수 있음
- 모든 요청이 DB를 사용하지 않음 (이미지, css, 정적 페이지 등)
- 요청에 따라 가벼운 작업 또는 무거운 작업이 있을 수 있고 이들의 DB사용시간은 다름
- DB 자체가 감당할 수 있는 커넥션 수에 제한이 있음. 대부분의 DBMS는 동시에 처리 가능한 커넥션 수에 제한이 있음
(mysql의 max_connection = 151) 커넥션을 너무 많이 열면 DB 성능이 급격히 저하됨
트래픽 기반 튜닝
얼마나 많은 요청이 DB를 사용하는가?
요청별 평균/최대 응답 시간은?
초당 몇건의 요청이 들어오는가?
DB커넥션은 얼마나 자주, 얼마나 오래 점유되는가?
등의 정보 바탕으로 스레드 수와 커넥션 수를 탄력적으로 설정하는 것이 중요
분석툴
APM(ApplicationPerformanceMonitoring)툴: Pinpoint, New Relic, Datadog, Prometheus + Grafana
JDBC Driver 로그 : 커넥션 생성/반납, 쿼리시간 측정
DB 모니터링 : mysql SHOW PROCESSLIST명령어