[Spring Database] Connection Pool과 Data Source
데이터베이스 커넥션을 획득할 때는 다음과 같은 과정을 거친다.
1. 애플리케이션 로직에서 데이터베이스 드라이버를 통해 획득 가능한 커넥션 조회
2. 데이터베이스 드라이버에서는 TCP/IP 커넥션을 연결한다. (3 way handshake 같은 네트워크 동작 사용)
3. 커넥션이 연결되면 데이터베이스에 ID, PW 및 부가정보를 전달한다.
4. 데이터베이스에서는 전달받은 정보를 바탕으로 내부 인증을 완료하고 데이터베이스 세션을 생성한다.
5. 데이터베이스에서 커넥션 생성이 완료됐다는 응답을 보내고, 데이터베이스 드라이버가 클라이언트에게 커넥션 객체를 반환한다.
이렇게 커넥션을 새로 만드는 작업은 과정도 복잡하고 시간도 많이 소모된다 -_-.
매번 TCP/IP 커넥션을 새로 생성하기 위해 리소스를 사용하고, 시간 지연이 생겨 응답 속도에 영향을 미친다.
이런 문제를 해결하는 방법이 없을까?
커넥션을 미리 생성해 놓고 사용하는 커넥션 풀을 도입해 해당 문제를 해결한다.
커넥션 풀에 들어있는 커넥션들은 TCP/IP 로 데이터베이스와 커넥션이 연결돼있기때문에 언제든지 SQL을 데이터베이스로 전달할 수 있다.
이제 애플리케이션 로직에서 데이터베이스 드라이버를 통해 새로운 커넥션을 획득하는 대신, 커넥션 풀에 있는 미리 생성된 커넥션들을 객체 참조로 가져다가 사용하면 된다. (커넥션 풀의 커넥션들도 내부적으로 드라이버를 사용한다)
커넥션 풀에 커넥션을 요청하면 커넥션 풀은 자신이 가지고 있는 커넥션 하나를 객체를 통해 반환하고, 모두 사용하고 나면 커넥션을 종료하는 대신 다음에 다시 사용할 수 있도록 사용한 커넥션을 커넥션 풀에 반환한다.
(커넥션이 살아있는 상태로 반환하고, 객체는 매번 새로 생성한다)
커넥션 풀에 포함될 커넥션의 수는 서버와 데이터베이스의 스펙에 따라 다르기 때문에 성능 테스트를 통해서 적절한 수를 정해야 한다.
커넥션 풀을 통해 얻을 수 있는 이점이 너무나 크기에 실무에서는 항상 사용하는 편이고, 스프링 부트에서는 기본 커넥션 풀으로 hikariCP를 제공하니 직접 구현하지 말고 가져다가 사용하자.
드라이버를 통해 직접 커넥션을 얻거나, 여러 가지 커넥션 풀을 사용하거나..
커넥션을 획득하는 방법으로는 여러 가지가 있다.
그런데 원래는 드라이버를 통해서 직접 커넥션을 얻어왔었는데, 커넥션 풀을 통해 커넥션을 얻는 방법으로 교체하면 의존관계가 변경되기 때문에 커넥션을 획득하는 애플리케이션 코드도 함께 수정해야 한다.
스프링은 이런 문제를 Data Source를 사용해 해결한다.
javax.sql.Datasource 인터페이스는 커넥션을 획득하는 방법을 추상화하는 인터페이스이다.
대부분의 커넥션 풀은 DataSource 인터페이스를 구현해 커넥션 획득 작업이 DataSource 인터페이스에만 의존하도록 할 수 있다.
DriverManager는 DataSource인터페이스를 구현해놓지 않았다.
DriverManager를 사용할 때는 스프링이 제공하는 DriverManagerDataSource 클래스를 사용해 커넥션을 받아오자.
@Test
void driverManager() throws SQLException {
Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
Connection con2 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
log.info("connection={}, class={}", con1, con1.getClass());
log.info("connection={}, class={}", con2, con2.getClass());
}
@Test
void dataSourceDriverManager() throws SQLException {
//DriverManagerDataSource - 항상 새로운 커넥션 획득
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
useDataSource(dataSource);
}
private void useDataSource(DataSource dataSource) throws SQLException {
Connection con1 = dataSource.getConnection();
Connection con2 = dataSource.getConnection();
log.info("connection={}, class={}", con1, con1.getClass());
log.info("connection={}, class={}", con2, con2.getClass());
}
DriverManager를 사용해서 커넥션을 얻는 예시와 DataSource를 사용해서 커넥션을 얻는 예시이다.
DriverManager를 사용할 때는 커넥션을 획득할 때 마다 파라미터로 관련 정보를 넘겨야 했지만, DataSource를 사용할 때는 처음 객체를 생성할 때 말고는 단순히 호출만 하면 된다.
즉, DataSource를 사용할 때는 파라미터로 넘겨야 하는 관련 정보들에 의존하지 않아도 된다.
이 부분을 설정과 사용의 분리라고 하는데, DataSource를 사용하면 설정하는 부분과 사용하는 부분을 명확하게 분리할 수 있다.
@Test
void dataSourceConnectionPool() throws SQLException, InterruptedException {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(URL);
dataSource.setUsername(USERNAME);
dataSource.setPassword(PASSWORD);
dataSource.setMaximumPoolSize(10);
dataSource.setPoolName("MyPool");
useDataSource(dataSource);
Thread.sleep(1000); //커넥션 풀에서 커넥션 생성 시간 대기
}
HikariCP를 통해 커넥션 풀을 사용한 예시이다.
커넥션 풀에 커넥션을 채우는 작업은 상대적으로 오래 걸린다.
따라서 커넥션 풀에서 커넥션을 생성하는 작업은 애플리케이션의 실행 속도에 영향을 주지 않기 위해 별도의 쓰레드에서 처리한다.
테스트를 통해 제대로 작동하는지 확인하는데, 별도의 쓰레드에서 동작하면 테스트가 먼저 종료되는 경우가 생길 수 있다.
Thread.sleep을 통해 대기 시간을 줘서 쓰레드 풀에 커넥션이 생성되는 로그를 확인하자.
@Slf4j
public class MemberRepositoryV1 {
private final DataSource dataSource;
public MemberRepositoryV1(DataSource dataSource) {
this.dataSource = dataSource;
}
//save()...
//findById()...
//update()....
//delete()....
private void close(Connection con, Statement stmt, ResultSet rs) {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(stmt);
JdbcUtils.closeConnection(con);
}
private Connection getConnection() throws SQLException {
Connection con = dataSource.getConnection();
log.info("get connection={}, class={}", con, con.getClass());
return con;
}
}
DBConnectionUtil을 사용하는 대신, 외부에서 DataSource를 주입받아서 사용하자.
DataSource로 커넥션을 획득하는 방법을 추상화해서 커넥션을 획득하는 방법이 변하더라도 바꿔야 하는 코드를 줄일 수 있다.
정리하면..
DataSource
데이터베이스 연결을 제공하는 객체이다.
커넥션 풀과 연결돼 이미 생성된 연결을 제공받거나 새로운 연결을 제공받아 사용할 수 있다.
Connection
데이터베이스와의 실제 연결을 나타낸다.
이 객체로 SQL을 실행하고 트랜잭션을 관리할 수 있다.
각 쓰레드는 독립적인 Connection 객체를 사용해 데이터베이스와 상호작용한다.
Connection Pool
Connection 객체의 생성은 오버헤드가 크다. 미리 생성된 Connection 객체를 재사용한다.
'Spring > Spring Database' 카테고리의 다른 글
[Spring Database] JdbcTemplate (0) | 2022.09.06 |
---|---|
[Spring Database] 데이터베이스 예외 처리 (0) | 2022.09.05 |
[Spring Database] Transaction AOP (1) | 2022.09.01 |
[Spring Database] Transaction / Lock (1) | 2022.09.01 |
[Spring Database] JDBC (0) | 2022.08.30 |
댓글
이 글 공유하기
다른 글
-
[Spring Database] 데이터베이스 예외 처리
[Spring Database] 데이터베이스 예외 처리
2022.09.05 -
[Spring Database] Transaction AOP
[Spring Database] Transaction AOP
2022.09.01 -
[Spring Database] Transaction / Lock
[Spring Database] Transaction / Lock
2022.09.01 -
[Spring Database] JDBC
[Spring Database] JDBC
2022.08.30