이전 포스트에 JDBC를 이용해서 DB를 사용하고자 할 때, 가장 먼저 해야하는 일은 DB 커넥션을 얻는 일이었다.
DB 커넥션을 얻을 때는 다음과 같은 과정을 거친다.
위와 같이 커넥션을 새로 만드는 과정은 복잡하고, 시간도 많이 소모된다.
만약 애플리케이션에서 DB를 사용할 때마다 커넥션을 새로 생성한다면, SQL을 실행하는 시간뿐만 아니라 커넥션을 새로 만드는 시간이 추가되기 때문에 결과적으로 응답속도에 영향을 준다. 응답속도가 느려지면, 애플리케이션을 사용하는 고객에게도 UX 측면에서 좋지않다.
이런 문제를 한번에 해결하는 아이디어가 바로 커넥션을 미리 생성해두고 사용하는 커넥션 풀이라는 방법이다.
커넥션 풀은 이름 그대로 커넥션을 관리하는 풀(미리 생성된 커넥션이 모여있는 풀이라고 생각하면 된다)이다.
참고.
커넥션 풀의 풀은 흔히 운영체제에서 사용되는 쓰레드 풀의 풀과 일맥상통하는 의미를 갖고 있다고 생각하면 된다.
애플리케이션을 시작하는 시점에 커넥션 풀은 필요한 만큼 DB 커넥션을 미리 확보해서 풀에 보관한다.
커넥션 풀에 들어있는 DB 커넥션은 TCP/IP로 이미 DB와 연결되어 있는 상태이기 때문에 언제든지 즉시 SQL을 DB에 전달할 수 있다.
커넥션 풀을 사용하면 애플리케이션에서 DB를 사용할 때 새로운 커넥션을 생성하여 획득하는 것이 아니라, 커넥션 풀을 통해 이미 생성되어 있는 커넥션을 가져다 쓴다. 애플리케이션이 커넥션 풀에 커넥션을 요청하면 커넥션 풀은 자신이 가지고 있는 커넥션 중 하나를 반환한다.
여기서 알아야하는 중요한 점은 애플리케이션이 커넥션을 모두 사용한 후의 과정이다. 이전에는 커넥션을 모두 사용한 후에 해당 커넥션을 종료했었다. 커넥션 풀을 사용하면 애플리케이션이 커넥션을 모두 사용하고 나서 커넥션을 종료하는 것이 아니라, 다음에 다시 사용할 수 있도록 해당 커넥션을 그대로 커넥션 풀에 반환한다. 커넥션은 여전히 살아있는 상태로 유지된다.
참고.
적절한 커넥션 풀의 숫자는 서비스의 특장과 애플리케이션 서버 스펙, DB 스펙을 고려하여 결정한다.
커넥션 풀로 서버당 최대 커넥션 수를 제한하여 DB에 무한정 연결이 생성되는 것을 막아 DB를 보호한다.
커넥션 풀이 주는 이점이 매우 크기에, 실무에는 항상 기본으로 사용한다. (쓰레드 풀도 기본으로 사용하는 것처럼)
스프링 부트는 기본으로 hikariCP를 사용한다.
커넥션을 얻는 방법은 앞서 언급한 DriverManager를 직접 사용하거나, 커넥션 풀을 사용하는 등 다양한 방법이 존재한다.
만약 애플리케이션을 개발할 때 DriverManager를 통해 커넥션을 획득하다가 커넥션 풀을 사용하는 방법으로 변경하려면 어떻게 해야할까? 혹은 hikariCP를 사용하다가 또다른 커넥션 풀 라이브러리로 변경하는 경우도 있을 것이고, DriverManager나 커넥션 풀을 사용하지 않는 또 다른 방법으로 바꾸게되는 경우도 있을 것이다.
이럴때마다 커넥션을 획득하는 코드를 변경해야한다면, JDBC가 없었을 때 각 벤더별 DB를 학습해야했던 것과 비슷한 상황이 되어버린다. 자바에서는 이런 문제를 해결하기 위해 DataSource라는 인터페이스를 제공한다. DataSource는 '커넥션을 획득하는 방법을 추상화' 하는 인터페이스이다. 그러므로, 이 인터페이스의 핵심 기능은 커넥션 조회이다.
대부분의 커넥션 풀은 DataSource 인터페이스를 이미 구현해두었다. 따라서 개발자는 특정 커넥션 풀 구현체의 코드에 의존하는게 아니라, DataSource 인터페이스에만 의존하도록 애플리케이션 로직을 작성하면 된다.
참고.
DriverManager는 DataSource 인터페이스를 사용하지 않는다. 따라서 DriverManager를 사용하다가 DataSource 기반의 커넥션 풀을 사용하려면 관련 코드를 전부 고쳐야한다. 이런 문제를 해결하기 위해 스프링은 DriverManager도 DataSource를 통해서 사용할 수 있도록 DriverManagerDataSource라는 DataSource를 구현한 클래스를 제공한다.
자바는 DataSource를 통해 커넥션을 획득하는 방법을 추상화했다. 이제 애플리케이션 로직은 DataSource 인터페이스에만 의존하면 된다. 게다가 스프링이 제공하는 DriverManagerDataSource를 이용하면 DriverManager를 사용하다가 커넥션 풀을 사용하도록 코드를 변경해도 애플리케이션 로직은 변경하지 않아도 된다. DataSource의 구현체만 변경해주면 되는 것이다.
실제 애플리케이션 로직에서는 아래의 코드처럼 데이터 소스를 사용하면 된다.
public class ExClass {
void dataSourceEx(){
DataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
Connection con = dataSource.getConnection();
... 커넥션을 이용한 DB 실제 사용 코드 ...
}
}
참고.
애플리케이션을 실행할 때 커넥션 풀을 채울 때까지 마냥 대기한다면 애플리케이션 실행 시간이 늦어진다.
따라서, 풀이 커넥션을 생성하는 일은 애플리케이션 실행 속도에 영향을 주지않기 위해 별도의 쓰레드에서 작동한다.
참고.
최대 풀 개수를 넘는 커넥션 요청이 오면, 사용중이었던 커넥션이 풀에 반환될 때까지 요청이 block 되고, block 되어 있는 동안 timeout이 발생하면 커넥션을 얻는데 실패한다.
데이터 소스를 사용함으로써 OCP와 DI를 적절하게 만족할 수 있다.