💋 Connection Pool이란?
✔️ 등장 배경
- 많은 애플리케이션에서 데이터베이스를 활용한다.
- 하지만, 데이터베이스에 연결하는 과정은 굉장히 비용이 많이 든다.
- 네트워크를 처리(TCP로 연결을 생성하고 종료)하고, 연결이 필요한 객체(SocketFactorv, Socket 등등)를 만들고 JVM GC에서 처리하는 과정은 굉장히 느리다.
- 데이터베이스와의 연결 비용을 줄이기 위해서
커넥션 풀
이라는 개념이 등장했다.
✔️ 개념
- 쉽게 말해서, 커넥션을 미리 만들어주고 빌려주는 것
- 미리 DB에 연결된 객체를 만들어놓고, 이것들을 Connection Pool이라고 하는 컨테이너에 넣어둔다.
- DB와 연결 요청이 들어올 때, 커넥션 풀에서 연결 객체를 꺼내서 주고, 다 사용하면 재사용할 수 있게 다시 커넥션 풀에 반환한다.
connection pool은 미리 일정한 수의 데이터베이스 커넥션을 만들어두고, 애플리케이션이 데이터베이스에 연결을 요청할 때마다 이 커넥션을 제공해줍니다. 이렇게 함으로써, 매번 데이터베이스에 커넥션을 만들고 끊는 비용을 줄이고, 효율적으로 데이터베이스와의 통신을 처리할 수 있습니다. 예를 들어, 학생 정보를 조회하는 기능을 실행할 때, DataSource는 connection pool에서 사용 가능한 커넥션을 가져와서 데이터베이스에 쿼리를 실행하고, 결과를 가져옵니다. 이후에는 해당 커넥션을 다시 connection pool에 반환합니다. 이렇게 함으로써, 많은 요청이 동시에 발생해도 효율적으로 데이터베이스와의 연결을 관리할 수 있습니다.
Connection Pool을 사용한다면, 애플리케이션이 많은 요청을 처리할 때도 효율적으로 데이터베이스와의 연결을 관리할 수 있습니다. 연결풀을 사용하여 데이터베이스 연결을 미리 생성하고 재사용함으로써, 매번 연결을 만들고 끊는 비용을 줄이고, 성능을 향상시킬 수 있습니다.
✔️ 종류
HikariCP, Tomcat pooling DataSource, Commons DBCP2, Oracle UCP 등 정말 다양한 커넥션 풀이 존재합니다.
다양한 이름들이 등장했지만, 각자 커넥션 풀을 다른 회사에서 다양한 방법으로 구현한 것에 불과하기 때문에, 커넥션 풀 라이브러리는 공통된 인터페이스
를 제공합니다.
이 인터페이스로는 ConnectionPool
, DataSource
, ConnectionFactory
등이 있습니다.
잠시 DataSource, ConnectionFactory가 뭔지 간략하게 알아보고 넘어갑시다!
💋 DataSource란?
✔️ 개념
데이터베이스와 연결하는 데 필요한 정보를 제공하는 인터페이스입니다. 데이터 소스는 데이터베이스에 접근하기 위한 드라이버, URL, 사용자 이름, 암호 등을 포함합니다. 개발자는 데이터 소스를 사용하여 데이터베이스와의 연결을 설정하고, 커넥션 풀에서 커넥션을 가져올 수 있습니다.
✔️ [개인적 궁금증] 데이터베이스는 왜 외부 API 호출과 다르게 특별히 ‘연결’이라는 것을 할까?
문득 이런 생각이 들었습니다. 결국 웹 애플리케이션과 데이터베이스는 서로 다른 시스템인데, 그러면 외부 API를 호출하는 것과 데이터베이스의 API(주로 SQL문이 되겠죠)를 호출하는 것은 어떤 차이가 있는 걸까요?
호출하면 되는 것 뿐인데, 왜 특별히 연결
이라는 작업을 거쳐야만 하는 걸까요?
아래와 같은 이유가 있을 것 같다는 결론을 내리게 되었습니다.
1. 데이터 보안
- 데이터베이스는 중요한 정보를 저장하는 곳이기 때문에 접근 제어와 보안이 필요합니다.
- 데이터베이스 연결을 통해 인증 및 권한 부여를 수행하여, 인가되지 않은 사용자가 데이터베이스에 접근하는 것을 방지할 수 있습니다.
2. 데이터의 일관성과 동시성 제어
- 여러 개의 사용자가 동시에 데이터베이스에 접근하는 경우, 데이터의 일관성과 동시성 제어가 필요합니다.
- 데이터베이스 연결을 통해 트랜잭션 관리와 락(lock)을 사용하여 데이터의 일관성을 유지하고, 동시에 여러 사용자가 데이터를 안전하게 수정할 수 있도록 합니다.
3. 효율적인 리소스 관리
- 데이터베이스 연결은 데이터베이스 서버와의 통신을 설정하는 과정입니다.
- 이를 통해 웹 애플리케이션은 데이터베이스와의 연결을 최적화하고, 리소스를 효율적으로 관리할 수 있습니다.
- 데이터베이스 연결을 효율적으로 관리함으로써, 불필요한 리소스 낭비를 방지하고 성능을 향상시킬 수 있습니다.
따라서 데이터베이스 연결은 데이터의 보안, 일관성, 동시성, 효율성을 유지하기 위해 필요한 과정입니다.
💋 ConnectionFactory란?
ConnectionFactory
는 커넥션 객체를 생성
하는 역할을 하는 유틸리티와 같은 성격을 지닌 클래스입니다.
ConnectionFactory
는 DataSource
로부터 필요한 정보를 받아와 새로운 커넥션 객체를 생성하고 반환합니다. ConnectionFactory
는 일반적으로 싱글톤 패턴으로 구현되어 하나의 인스턴스만을 생성하고 관리합니다.
(참고)
ConnectionFactory
는 단순 유틸리티 클래스라기보다는, 팩토리 디자인 패턴을 사용하여 인터페이스를 정의했기 때문에, 구체적인 팩토리 클래스를 구현할 수도 있습니다.
ConnectionFactory
는 데이터베이스 연결에 필요한 정보를 받아와서 새로운 커넥션 객체를 생성하고 반환하는 역할을 수행합니다. 이를 통해 개발자는 직접 커넥션 객체를 생성 및 관리하는 복잡한 과정을 처리하지 않고도 편리하게 데이터베이스 연결을 관리할 수 있습니다.
💋 Spring Boot와 Connection Pool
앞서 설명한 다양한 종류의 Connection Pool 구현체 중에서, 스프링 부트는 성능과 동시성 면에서 우수한 성능을 보이는 HikariCP
를 우선적으로 선택합니다. 만약 HikariCP를 사용할 수 없다면, Tomcat pooling DataSource, Commons DBCP2, Oracle UCP 등을 차례대로 사용하게 됩니다.
또한, spring-boot-starter-jdbc
또는 spring-boot-starter-data-jpa
를 사용하는 경우에는 기본적으로 HikariCP에 대한 의존성이 자동으로 추가됩니다.
spring.datasource.type
속성을 설정하여 원하는 커넥션 풀을 직접 지정할 수도 있습니다. 특히, Tomcat 컨테이너에서 애플리케이션을 실행하는 경우에는 기본적으로 tomcat-jdbc
가 제공되므로, spring.datasource.type
속성을 사용하여 다른 커넥션 풀을 설정할 수 있습니다.
또한, 추가적인 커넥션 풀을 수동으로 구성할 수도 있습니다. 이 경우에는 DataSourceBuilder
를 사용하여 직접 DataSource
를 정의해야 하며, 자동 구성(auto-configuration)은 적용되지 않습니다. DataSourceBuilder를 통해 지원되는 커넥션 풀로는 HikariCP, Tomcat pooling Datasource, Commons DBCP2, Oracle UCP & OracleDataSource, Spring Framework의 SimpleDriverDataSource, H2 JdbcDataSource, PostgreSQL PGSimpleDataSource, C3P0 등이 있습니다.
우리는 이중에서, 스프링 부트가 주로 사용하는 HikariCP
에 대해서 조금 더 알아봅시다.
💋 HikariCP
의 동작 과정
아래 내용은 우아한 기술블로그의 글을 토대로 작성했습니다.
✔️ 일반적인 쿼리의 실행 과정
굉장히 간추려진 쿼리는 아래와 같이 커넥션을 얻어서 실행되고, 할 일을 마친 뒤에는 리소스 누수를 막기 위해서 close
하는 과정을 거칩니다.
Connection connection = null;
PreparedStatement preparedStatement = null
try {
connection = hikariDataSource.getConnection(); // 커넥션 받아오기
preparedStatement = connection.preparedStatement(sql);
preparedStatement.executeQuery(); // 쿼리 실행하기
} catch(Throwable e) {
throw new RuntimeException(e);
} finally {
if (preparedStatement != null) {
preparedStatement.close(); // 리소스 누수를 막기 위해서 자원을 close함.
}
if (connection != null) {
connection.close(); // 여기서 connection pool에 반납됩니다.
}
}
위의 코드에서 HikariCP
가 담당(호출)하는 부분은, hikariDataSource.getConnection()
과 connection.close()
입니다.
각각 커넥션을 받아오고, 반환하는 메서드인데 자세히 살펴봅시다.
✔️ HikariCP
로부터 Connection
받아오기
HikariCP
에서는 내부적으로 ConcurrentBag
이라는 구조체를 이용해 Connection
을 관리합니다.
외부에서 HikariPool.getConnection()
을 호출하게 되면, 내부적으로는 ConcurrentBag.borrow()
을 호출하고, 커넥션을 요청한 쓰레드가 이전에 사용한 동일한 커넥션이 있는지 먼저 방문 내역을 살펴보고, 최대한 동일한 커넥션을 주려고 한다.
하지만, 그 커넥션이 현재 다른 쓰레드에서 사용중이라면 커넥션 풀에 현재 사용 가능한 상태의 커넥션이 있는지 보고, 전체 커넥션이 모두 사용중인 경우에 쓰레드를 handoffQueue
로 보내서 대기하도록 한다.
HikariCP의 default Connection timeout은 30초이므로, 30초가 지나도록 다른 Thread가 쓰고 반납한 Connection을 얻지 못한다면 Timeout이 나서 예외가 발생하게 된다.
✔️ HikariCP
에 Connection
반환하기
JDBC의 connection.close()
메서드는 아래의 과정을 거쳐 현재 열려있는 데이터베이스 연결을 닫습니다.
- 현재 진행 중인 트랜잭션이 커밋되지 않았다면, 자동으로 롤백됩니다.
- 데이터베이스 연결에 대한 리소스(네트워크 연결, 메모리 등)가 해제됩니다.
- 연결에 대한 모든 작업(쿼리 실행, 트랜잭션 관리 등)이 중단됩니다.
- 해당 연결로부터 가져온 모든
Statement
,PreparedStatement
,ResultSet
등의 자원도 닫히게 됩니다.
즉, connection.close()
를 호출하면 현재 사용 중인 데이터베이스 연결이 완전히 종료되며, 다시 사용할 수 없게 됩니다.
하지만, 커넥션 풀을 구현한 HikariCP
의 경우에는 프록시 객체를 사용해서, 커넥션을 완전히 종료하지 않고, 커넥션을 커넥션 풀로 반환하도록 구현합니다.
HikariCP
에서 사용하는 프록시 객체인 ProxyConnection
은 실제 Connection
객체와 동일한 인터페이스를 구현하지만, close()
메서드를 오버라이드하여 커넥션을 닫지 않고 반환 풀에 반환하도록 합니다.
커넥션은 커넥션 풀에 반환되고, 이후에 handOffQueue
에서 커넥션을 받으려고 기다리는 쓰레드가 있다면, 해당 쓰레드에 커넥션을 주게 된다.
이제 실제로 HikariCP를 사용하는 방법에 대해서 알아봅시다.
💋 HikariCP
의 초기화
HikariCP DataSource
를 만드는 다양한 방법이 존재합니다.
✔️ HikariConfig
클래스를 사용
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons");
config.setUsername("bart");
config.setPassword("51mp50n");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
HikariDataSource ds = new HikariDataSource(config);
HikariConfig
클래스를 사용하여 HikariCP
를 구성하고 데이터베이스 연결에 필요한 속성을 설정합니다. 예를 들어, JDBC URL, 사용자 이름, 비밀번호 등을 설정할 수 있습니다. 그리고 이러한 구성을 기반으로 HikariDataSource
를 생성합니다.
✔️ HikariDataSource
를 직접 생성하고 속성 설정
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:mysql://localhost:3306/simpsons");
ds.setUsername("bart");
ds.setPassword("51mp50n");
...
HikariDataSource
를 직접 생성하고 속성을 설정합니다. 이 방법은 HikariConfig
를 사용하지 않고 간단하게 데이터베이스에 연결하는 방법입니다.
✔️ properties 파일을 사용하여 HikariCP
를 구성
// Examines both filesystem and classpath for .properties file
HikariConfig config = new HikariConfig("/some/path/hikari.properties");
HikariDataSource ds = new HikariDataSource(config);
도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
'Computer Science > Database' 카테고리의 다른 글
[DB] Relational Database Constriants: 개념, implicit constraint와 explicit constraint, 종류 (0) | 2023.12.03 |
---|---|
[DB] 데이터베이스 용어 정리: DBMS, metadata, data models, schema, state, three-schema architecture 등등 (0) | 2023.12.02 |
[DB] 클러스터링 인덱스: 인덱스의 개념, MySQL에서 리프 노드에 메모리 주소가 아닌 Primary Key를 저장하는 이유 (2) | 2023.08.31 |
[DB/H2] H2 데이터베이스 버전 확인 (0) | 2023.06.29 |
[DB/H2] H2 데이터베이스 설치부터 실행까지 (0) | 2023.06.15 |