-
커넥션 풀과 데이터소스김영한의 스프링/데이터베이스 2024. 9. 11. 21:16
커넥션 풀
커넥션을 만드는 일은 과정도 복잡하고 시간도 많이 소모 되는 일이다. 그래서 커넥션을 미리 생성해두고 관리하는 방법이 커넥션 풀이다.
커넥션 풀의 이해
데이터베이스 커넥션을 획득을 할 때 어떤 과정이 일어날까?
- 애플리케이션 로직은 DB 드라이버를 통해 커넥션을 조회한다.
- DB 드라이버는 DB와 TCP/IP 커넥션을 연결한다. -> 이 과정에서 3 WAY HANDSHAKE 연결을 한다.(쉽게 생각하면 네트워크 연결 준비라고 생각하면된다.)
- DB 드라이버는 TCP/IP 커넥션이 연결되면 아이디와 패스워드 기타 부가정보를 DB에 전달한다.
- 인증을 완료하고 내부에 DB 세션을 생성한다.
- DB는 커녁션 생성이 완료 되었다는 응답을 보내고 DB 드라이버는 커넥션 객체를 생성해서 클라이언트에 반환한다.
크게 이렇게 5단계를 거치는데 이 과정은 복잡하고 시간이 많이 걸린다. 내가 DB에 접근을 하려고 할때마다 저렇게 TCP/IP 연결해서 인증을 받고 커넥션을 생성하고 받아야한다. 고객이 애플리케이션을 사용할 때, SQL 실행하는 시간 뿐만 아니라 커넥션을 새로 만드는 시간이 추가되기 때문에 결과적으로 응답 속도에 영향을 미치게 된다. 이런 단점을 해결하기 위한 것이 커넥션 풀이다.
커넥션 풀을 사용하면 다음 단계로 커넥션을 획득하게 된다.
- 애플리케이션을 시작하는 시점에 커넥션 풀은 필요한 만큼 커넥션을 미리 확보해서 풀에 보관한다. 즉, 위에 TCP/IP 연결, 인증, 커넥션 생성을 미리 해놓는 것이다.
- 커넥션 풀이 들어 있는 커넥션은 TCP/IP로 DB와 커넥션이 연결되어 있는 상태이기 때문에 애플리케이션 로직에서 이제 DB 드라이버를 통해 새로운 커넥션을 획득하는 것이 아니라 이미 생성되어 있는 커넥션을 객체 참조로 가져다 쓴다.
- 커넥션을 모두 사용하고 나면 커넥션을 종료하지 않고 다음에 다시 사용할 수 있도록 커넥션 풀에 반환한다.
커넥션 풀 정리
- 커넥션 풀 숫자는 서비스의 특징과 애플리케이션 서버 스펙, DB 서버 스펙에 따라 다르기 때문에 성능 테스트를 통해 결정한다.
- 커넥션 풀은 서버당 최대 커넥션 수를 제한할 수 있다. 그래서 DB에 무한정 연결이 생성된느 것을 막아주어 DB를 보호하는 효과가 있다.
- 커넥션 풀 오픈 소스는 cpmmons-dbcp2, HikariCP 등이 있는데 주로 HikariCP를 사용한다. 스프링 부트에서는 기본 커넥션 풀로 HikariCPfmf 제공한다.
이제 데이터 소스가 뭔지 알아보자!
DataSource 이해
커넥션을 얻는 방법에는 JDBC의 DriverManager를 직접 사용하거나, 커넥션 풀을 사용하는 등 다양한 방법이 존재한다.
애플리케이션 로직에서 커넥션을 이용할 때 처음에는 DriverManager를 사용하다가 나중에 HikariCp 커넥션 풀로 커넥션을 획득하려면 중간에 커넥션을 조회하는 코드를 바꿔야할 것이다. 하지만 자바는 JDBC 처럼 DataSource라는 인터페이스를 제공하여 커넥션을 획득하는 방법을 추상화 해놓았다.
//DataSource 핵심 기능 public interface DataSource{ Connection getConnetion() throws SQLException; }
개발자는 DBCP2 커넥션 풀, HikariCP 커넥션 풀 등의 코드를 직접 의존하는 것이 아니라 이제 DataSource 인터페이스만 의존하도록 애플리케이션 로직을 작성하면 된다. 만약 중간에 바꾸고 싶다면 해당 구현체로 갈아끼우기만 하면 된다.
참고
DriverManager는 DataSource 인터페이스를 사용하지 않는다. 따라서 DriverManager는 직접 사용해야한다. 따라서 DriverManger를 사용하다가 DataSource 기반의 커넥션 풀을 사용하도록 변경하면 관련 코드를 다 고쳐야한다. 스프링은 이런 문제를 해결하기 위해서 DriverManager도 DataSource를 통해서 사용할 수 있도록 DriverManagerDataSource라는 DataSource를 구현한 클래스를 제공한다.
이제 예제를 통해 몸소 느껴보자! ㅎㅎ
DataSource 예제
1. DriverManager를 통해서 커넥션을 획득
public class ConnectionTest { @Test void driveManager() throws SQLException { Connection connection1= DriverManager.getConnection(URL,USERNAME,PASSWORD); Connection connection2= DriverManager.getConnection(URL,USERNAME,PASSWORD); log.info("connection={}, class={}",connection1,connection1.getClass()); log.info("connection={}, class={}",connection2,connection2.getClass()); } }
2. 스프링이 제공하는 DriverManagerDataSource를 통해 커넥션 획득
public class ConnectionTest { @Test void dataSourceDriveManager() throws SQLException { DataSource dataSource=new DriverManagerDataSource(URL,USERNAME,PASSWORD); useDataSource(dataSource); } private void useDataSource(DataSource dataSource) throws SQLException { Connection connection1=dataSource.getConnection(); Connection connection2=dataSource.getConnection(); log.info("connection={}, class={}",connection1,connection1.getClass()); log.info("connection={}, class={}",connection2,connection2.getClass()); } }
두 방법 모두 내가 DB에 접근을 할 때마다 커넥션을 생성하여 반환한다. 그렇다면 차이점은 뭘까? 바로 파라미터 차이이다! DriverManager를 사용할 때 커넥션을 가져오려면 가져올 때마다 URL, USERNAME, PASSWORD 파라미터를 계속 전달해야한다. 하지만 DataSource 인터페이스를 사용하면 처음 객체를 생성할 때만 파라미터를 넘기고 커넥션을 획득할 때는 단순히 getConnection 메서드를 호출하면 된다. 이는 사용과 설정을 완전히 분리한 것이다. DataSource는 객체를 생성하는 부분과 사용하는 부분을 완벽하게 분리해놨다.
3. 커넥션 풀을 통해 커넥션을 획득
public class ConnectionTest { @Test void dataSourceConnectionPool() throws SQLException, InterruptedException { HikariDataSource dataSource=new HikariDataSource(); dataSource.setJdbcUrl(URL); dataSource.setUsername(USERNAME); dataSource.setPassword(PASSWORD); dataSource.setPoolName("MyPool"); dataSource.setMaximumPoolSize(10); useDataSource(dataSource); //별도의 쓰레드를 사용해서 커넥션 풀에 채우기 때문에 늦게 출력됨 Thread.sleep(1000); } private void useDataSource(DataSource dataSource) throws SQLException { Connection connection1=dataSource.getConnection(); Connection connection2=dataSource.getConnection(); log.info("connection={}, class={}",connection1,connection1.getClass()); log.info("connection={}, class={}",connection2,connection2.getClass()); } }
21:11:27.171 [Test worker] INFO com.zaxxer.hikari.pool.HikariPool -- MyPool - Added connection conn0: url=jdbc:h2:tcp://localhost/~/test user=SA 21:11:27.172 [Test worker] INFO com.zaxxer.hikari.HikariDataSource -- MyPool - Start completed. 21:11:27.185 [MyPool connection adder] DEBUG com.zaxxer.hikari.pool.HikariPool -- MyPool - Added connection conn1: url=jdbc:h2:tcp://localhost/~/test user=SA 21:11:27.185 [Test worker] INFO h.jdbc.connection.ConnectionTest -- ... 21:11:27.608 [MyPool connection adder] DEBUG com.zaxxer.hikari.pool.HikariPool -- MyPool - After adding stats (total=9, active=2, idle=7, waiting=0) 21:11:27.618 [MyPool connection adder] DEBUG com.zaxxer.hikari.pool.HikariPool -- MyPool - Added connection conn9: url=jdbc:h2:tcp://localhost/~/test user=SA 21:11:27.655 [MyPool connection adder] DEBUG com.zaxxer.hikari.pool.HikariPool -- MyPool - After adding stats (total=10, active=2, idle=8, waiting=0)
실행을 해보면 풀 이름과 최대 풀의 수(10)을 확인할 수 있다. 커넥션 풀에 커넥션을 채우는 것은 상대적으로 오래 걸리기에 애플리케이션을 실행할 때 커넥션 풀을 채울 때까지 마냥 기다리고 있으면 애플리케이션 실행 시간이 늦어진다. 그렇기에 별도의 쓰레드를 사용해서 커넥션 풀에 커넥션을 채운다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1/dashboard
스프링 DB 1편 - 데이터 접근 핵심 원리 강의 | 김영한 - 인프런
김영한 | 백엔드 개발에 필요한 DB 데이터 접근 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니
www.inflearn.com
'김영한의 스프링 > 데이터베이스' 카테고리의 다른 글
[Spring] 스프링 트랜잭션 전파 (1) 2024.10.04 [Spring] 스프링의 예외 처리 (2) 2024.10.02 [Spring] 스프링의 트랜잭션 문제 해결 (1) 2024.10.02 트랜잭션 (0) 2024.09.12 JDBC (0) 2024.08.31