[데이터베이스] Database Connection Pool, DBCP
Database Connection Pool 이란?
편의를 위해 DBCP로 칭하도록 하겠습니다.
DBCP는 미리 연결된 Connection을 Connection Pool에 만들어두고, 필요에 의해 사용하고 반환함으로써 Connection을 재활용할 수 있게 해주는 기법을 의미한다.
DBCP는 연결 생성, 연결 재사용, 연결 제한, 연결 유지, 연결 검사와 같은 주요한 기능을 제공한다.
- 연결 생성 : Connection을 얼마나 생성할 지 지정할 수 있다.
- 연결 재사용 : 생성된 Connection을 반환받아 재사용할 수 있다
- 연결 제한 : DBCP 내부의 Connection을 얼마나 개방할 지 제한한다. 이를 통해서 시스템 부하를 관리할 수 있다.
- 연결 유지 : Connection이 사용되고 있지 않아도 일정시간 연결이 끊어지지 않도록 유지한다.
- 연결 검사 : Connection이 제대로 작동하고 있는지 주기적으로 검사한다.
동작 과정
Thread는 서버에서 처리하려는 요청의 로직이다. 요청이 DB와 관련이 있다면 DBCP에서 Connection을 얻어 DB와 소통한다.
동작 과정은 다음과 같다.
- 애플리케이션의 요청에 따라 DB에 접근하기 위해 DBCP에 Connection을 요청한다..
- 가져온 Connection을 통해 DB와 소통하여 로직을 처리한다..
- 사용을 마친 Connection을 다시 DBCP에 반환한다..
사용하는 이유
DB와 관련된 로직을 처리하는 과정 중에서 가장 비용이 많이 드는 것은 DB와의 Connection을 생성과 소멸이다. 초기에 Connection을 미리 생성해두고 재활용한다면 매 요청마다 DB의 Connection을 생성하지 않아도 되기 때문에 합리적이라고 할 수 있다. 또한 미리 생성된 Connection을 통해 접근하기 때문에 접근 시간도 단축된다.
정리하면 다음과 같은 장점이 있다.
- 매 요청마다 DB를 생성하지 않아도 되기 때문에 생성 비용을 줄일 수 있다.
- 미리 생성된 Connection을 이용하기 때문에 DB에 바로 접근이 가능해 접근 시간이 단축된다.
- Connection의 수를 조정해 DB에 대한 접근을 제어할 수 있어 부하를 제어할 수 있다.
DBCP는 애플리케이션의 성능과 효율을 크게 향상시킬 수 있는 기능이지만, 풀의 크기와 설정을 적절하게 설정하지 않으면 오히려 성능이 저하되거나 메모리가 낭비되는 부작용이 나타날 수 있다.
DBCP Example
아주 간단한 구현을 통해 DBCP의 유무에 따른 성능 차이를 비교해보자.
간단한 SELECT문을 사용하는 두 개의 메소드를 정의하고, 해당 메소드를 반복해서 호출하는 방식으로 시간을 측정한다. 각 메소드의 차이는 내부에서 Connection 생성 유무의 차이이다.
즉, 커넥션 풀의 사용유무라고 생각하시면 된다.
호출 시 Connection 생성
package com.example.dbtest.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class NotUseCpTestEx {
public static void main(String[] args) {
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
getData();
}
long end = System.nanoTime();
System.out.println("실행 시간: " + (end - start) / 1_000_000 + "ms");
}
private static void getData() {
String url = "jdbc:mysql://localhost:3306/testdb";
String username = "root";
String password = "0000";
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, username, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM test_table");
while (rs.next()) {
String name = rs.getString("name");
}
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 위 코드는 JDBC를 통해 getData() 메서드를 호출할 때마다 Connection을 생성한다.
- 위 코드는 실행 시간: 5276ms 를 출력한다.
Connection Pool 사용
package com.example.dbtest.db;
import org.apache.commons.dbcp2.BasicDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class ConnectionPoolEx {
public static void main(String[] args) {
BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/testdb");
ds.setUsername("root");
ds.setPassword("0000");
// 풀이 유지할 수 있는 최소 idle 연결 수
ds.setMinIdle(100);
// 풀이 유지할 수 있는 최대 idle 연결 수
ds.setMaxIdle(100);
// DBCP 내부의 전체 Connection에 존재하는 PreparedStatement의 갯수를 제한
ds.setMaxOpenPreparedStatements(100);
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
printData(ds);
}
long end = System.nanoTime();
System.out.println("실행 시간: " + (end - start) / 1_000_000 + "ms");
}
private static void printData(BasicDataSource ds) {
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM test_table")) {
while (rs.next()) {
rs.getString("name");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- 위 코드는 DBCP를 통해 미리 생성된 Connection을 얻어 DB에서 데이터를 조회한다.
- 위 코드는 실행 시간: 1080ms 를 출력한다.
두 상황을 비교했을 때의 성능 차이는 5배에 달한다. 결과를 토대로 보면 DB Connection의 생성과 소멸 비용이 굉장히 큰 비용이라는 것을 알 수 있다.
출처
https://hudi.blog/dbcp-and-hikaricp/
https://github.com/WeareSoft/tech-interview/blob/master/contents/db.md#데이터베이스-풀