언어/Java

🌼 Connection Pool

뭉지(moonz) 2022. 4. 8. 13:49
반응형

👋 어쩌다 등장했나?

일반적인 데이터 연동과정은 웹 애플리케이션이 필요할 때마다 데이터베이스에 연결하여 작업하는 방식입니다.

더보기

일반적인 데이터베이스 연결 Life Cycle

아래 단계만 봐도 데이터베이스 연결은 상당한 비용이 소모되는 작업임을 알 수 있습니다.

  1. 데이터베이스 드라이버를 로드한 후 데이터베이스로의 연결 객체를 생성하여 open
  2. 데이터 조회/생성을 위해 TCP Socket open
  3. 소켓을 통해 데이터를 읽고 쓴다.
  4. 연결 close
  5. 소켓 close

아래 내용은 INSERT문을 수행하는 과정에서 필요한 비용의 비율을 나타냅니다. (MySQL 8.0 기준)  : 참고 링크

  • Connecting: (3)
  • Sending query to server: (2)
  • Parsing query: (2)
  • Inserting row: (1)
  • Inserting index: (1)
  • Closing: (1)

즉, Connecting에서는 WAS(구체적으로 수행 스레드)와 데이터베이스 사이를 연결하는 과정인데,
클라이언트와 서버 사이의 연결을 위해서는 3-way-handshaking이라는 작업이 필요합니다.

결론적으로, Connection을 생성하는 작업이 가장 큰 비용을 차지한다는 것입니다.

🌻 JDBC에 접근하는 객체를 생성해서 이를 이용해 DB에 요청하는 작업이 이루어지는데,

매번 요청할 때마다

JDBC Driver를 로드하고...
이를 이용해 Connection 객체를 생성한 후 연결하고...
PreparedStatement 객체를 생성하여, 이를 이용해서 sql쿼리를 실행하고 ...
PS 닫고, Connection을 닫습니다...

한 두개라면 상관이 없겠지만, 사용자가 많아지고 쿼리요청이 기하급수적으로 증가할 수록 위의 과정을 반복하는 것은 매우 비효율적입니다.

그리고 웹 애플리케이션의 요청 대부분이 DB 작업을 필요로 하므로,

JDBC 연결로 인한 성능 저하 👉 DBMS(database management system) 👉 전체 애플리케이션의 성능과 안정성 에 영향을 미칩니다.

 

이 문제를 해결하기 위해, 스레드가 DB 연결을 필요로 할 때마다 매번 생성/해제하는 것이 아닌,

웹 컨테이너가 로드됨과 동시에 연동할 데이터베이스와의 연결을 미리 설정해놓고, 요청이 들어왔을 때 데이터베이스와의 연결을 필요로하는 스레드들이 미리 pool에 존재하는 객체를 이용하면 빠르게 데이터베이스와 연동하여 작업을 할 수 있습니다.

결론

  1. Connection을 생성하는 과정은 3-way-handshaking을 해야 하기 때문에 시간상 비용이 비싼 작업입니다.
  2. 반복되는 Connection 생성을 줄이기 위해서 Connection Pool 방식을 활용합니다.
더보기

🌻 JDBC는 Java DataBase Connectivity의 약자로 자바 애플리케이션에서 데이터베이스에 접속할 수 있도록 하는 자바 API입니다.

JDBC는 데이터베이스에서 자료를 쿼리하거나 업데이트하는 방법을 제공합니다.

결국 클라이언트는 JDBC를 이용해 데이터베이스에 접근합니다.

 

🌼 Connection Pool이 뭐야?

In software engineering, a connection pool is a cache of database connections maintained so that the connections can be reused when future requests to the database are required. Connection pools are used to enhance the performance of executing commands on a database. Opening and maintaining a database connection for each user, especially requests made to a dynamic database-driven 
website application, is costly and wastes resources. In connection pooling, after a connection is created, it is placed in the pool and it is used again so that a new connection does not have to be established. If all the connections are being used, a new connection is made and is added to the pool. Connection pooling also cuts down on the amount of time a user must wait to establish a connection to the database.

위키피디아에 설명이 잘 되어있는데, 번역하면..

(JDBC를 이용해) 데이터베이스로의 요청이 요구될 때마다 connection이 생성되는 것이 아닌 재활용되도록, connection을 관리하는 데이터베이스 캐시를 Connection Pool라고 합니다.
user마다 database를 열고 관리하는 것은 비용적으로도 그렇고, 자원을 많이 낭비합니다.
그래서 connection이 생성되고 나면 Connection Pool에 배치되어서 새로운 connection을 만드는게 아닌 Pool에서 가져와 재사용하게 되면서 데이터베이스에 대한 쿼리 실행의 성능을 향상시키게 됩니다.
또한! 이는 사용자가 데이터베이스에 연결하기 위해 기다려야 하는 시간을 줄여줍니다.

+ 만약 모든 connection들이 사용되고 있다면, 새 connection이 만들어지고 pool에 추가됩니다. 하지만, 더이상 새 connection을 생성할 수 없다면 해당 클라이언트는 대기 상태로 전환이 되고, 커넥션이 반환되면 대기하고 있는 순서대로 커넥션이 제공됩니다.

 

결국 Connection Pooling은 데이터베이스와 연결된 상태를 유지하는 기술이자 라이브러리입니다.

 

예로, 스레드가 JDBC 를 이용해서 데이터베이스(MySQL, 다른 DB..)에 작업을 요청해야할 때마다 connection 객체를 생성하지 않고, Connection Pool에 있는 connection을 재사용합니다. 스레드 작업이 끝나고 다시 pool에 반환하면 다른 스레드들이 이를 재사용합니다.

(일단 pool로부터 connection을 빌려오면, 이를 빌려온 스레드만이 독점적으로 사용하게 됩니다.)

 

커넥션 풀을 사용하게 되면 더 간단한 사이클로 DB 요청이 가능해집니다.
1. 웹 컨테이너가 실행되면서 DB와 연결된 connection 객체를 미리 pool에 생성합니다.
2. 스레드가 필요할 때 (JDBC를 통해) pool에서 가져다 사용하고 반환합니다.

출처 : https://devbox.tistory.com/entry/JSP-%EC%BB%A4%EB%84%A5%EC%85%98-%ED%92%80-1

 

 

🦸 Connection Pool(DBCP)의 이점은?

  • 부하가 많이 걸리는 DB connection 맺는 작업 대신, 미리 생성한 Connection을 재활용함으로써 서버의 부하를 줄여줍니다.
  • 서버의 한정적인 리소스를 효율적으로 사용하여 jdbc 애플리케이션의 성능을 향상시킵니다. (이는 웹 애플리케이션의 성능까지도 영향을 미칩니다.)
    • 스레드가 DB 연결이 필요할 때마다 새 connection을 생성하면 애플리케이션의 리소스 사용량이 낭비될 수 있으며
      과부하 상태일 때는 애플리케이션에 예측 못한 동작이 발생할 수 있습니다.

⚠️주의 - Connection Pool 크기 값을 적절하지 설정하지 않는 경우...⚠️

  • Connection Pool의 크기가 작으면 Connection을 획득하기 위해 대기하는 Thread가 많아져 이에 따른 성능적인 저하가 발생할 수 있습니다.
  • 그렇다고 크기를 무제한으로 키운다하여 성능이 좋아지는 것도 아닙니다.
    • Connection Pool에서 Connection은 WAS 내 Thread가 가져가게 되는데, 결국 Thread Pool의 크기보다 Connection Pool의 크기가 커봤자 Thread가 사용하는 Connection 외에 남는 Connection은 메모리 공간만 차지하게 됩니다.
    • Thread Pool도 함께 증가시키는 방법을 떠올릴 수 있지만, Context Switching등 다양한 원인으로 성능적인 한계가 존재하게 된다고 합니다.

그러므로 각 애플리케이션의 로직이 가지고 있는 복잡도, 서버의 부하/리소스 상황에 맞게 커넥션 풀을 설정해주어야 합니다. ★

 

Hikari CP 공식문서에서는 적절한 Connection Pool 크기로, 아래 수식을 제공합니다. (단, 절대적인 수가 아닙니다.)
connection 갯수 = (논리 Cpu 개수 * 2) + DB 서버가 관리할 수 있는 동시 I/O 요청 수(=디스크 갯수)

 

 

📜 Connection Pool 설정 예제

Connection Pool에는 대표적인 오픈소스 라이브러리인 Apache의 Commons DBCP와 Tomcat-JDBC, BoneCP, HikariCP 등이 있습니다.

이번 글에서는 Spring에서 2.0이후 부터 기본 옵션으로 채택하고 있는 HikariCP를 알아보겠습니다.

 

자바 애플리케이션은 기본적으로 DataSource 인터페이스를 사용하여 커넥션 풀을 관리합니다.

Connection Pool에 대한 설정은

1. application.yml 설정 파일에서 spring.datasource 옵션을 통해 설정하거나

2. 직접 클래스로 설정할 수 있습니다.

 

이때 HirakiCP 설정 옵션 외에도 연결할 데이터베이스 정보(url, username, password, driver-class-name)도 함께 설정해줍니다.

더보기

application.yml 설정파일에서 옵션으로 설정하는 예제

# CP
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/TESTDB?serverTimeZone=UTC&CharacterEncoding=UTF-8
    username: root
    password: 
    hikari:
      maximum-pool-size: 10
      connection-timeout: 5000
      connection-init-sql: SELECT 1
      validation-timeout: 2000
      minimum-idle: 10  # Connection Pool에서 유지 관리하는 최소 연결수
      idle-timeout: 600000  # 연결을 위한 지속 시간
      max-lifetime: 1800000 # 연결이 닫힌 후 pool에서의 connection의 최대 수명
    driver-class-name: com.mysql.jdbc.Driver  # mysql connector java를 추가해야 인식한다.

 

클래스로 설정하는 예제

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.SQLException;

public class HikariCPDataSource {
    private static HikariConfig config = new HikariConfig();
    private static HikariCPDataSource ds;

    static {
        config.setJdbcUrl("jdbc:mysql://localhost:3306/TESTDB?serverTimeZone=UTC&CharacterEncoding=UTF-8");
        config.setUsername("root");
        config.setPassword("{비밀번호}");
        // 옵션 설정
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        config.setMaximumPoolSize(10);
        config.setMinimumIdle(10);
        config.setValidationTimeout(2000);
        config.setConnectionTimeout(5000);
        config.setIdleTimeout(2000);
        config.setConnectionTimeout(5000);
        ds = new HikariDataSource(config);
    }

    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }

    private HikariCPDataSource(HikariConfig config){}
}

 

🦿​ 설정 옵션

설정할 수 있는 옵션은 매우 많습니다. 그중 해당 HikariCP github에서 ✅자주 사용되는 것으로 표기한 옵션들 중 몇가지에 대해서 작성하였습니다.

  • autoCommit : pool에서 반환된 connection의 자동 커밋 동작 여부 (default: true)
  • connectionTimeout : 클라이언트가 pool에 connection 요청 시 대기하는 최대 시간 (default: 30000, 30초)
    • 설정 시간 초과 시 SQLException이 발생합니다.
  • idleTimeout : pool에서 connection이 유휴 상태로 유지할 수 있는 최대 시간 (default: 600000, 10분)
    • 해당 옵션은 minimumIdle < maximumPoolSize인 경우에만 적용됩니다.
    • pool에 있는 connection이 minimumIdle에 도달할 경우, 이후에 반환되는 connection에 대해서는 바로 반환하지 않고 idleTimeout만큼 유휴 상태로 있다가 폐기됩니다.
  • maxLifetime : pool 내 connection의 최대 유지 시간 (default: 1800000, 30분)
    • 사용중인 연결은 작업이 완료되면 페기, 유휴 커넥션은 바로 폐기됩니다.
  • connectionTestQuery : legacy이므로 사용 비추. connection이 pool로부터 클라이언트에게 제공되기 바로 전 수행하여 연결이 활성화되어있는지 체크하는 쿼리 (default: none)
  • keepaliveTime : HikariCP가 connection이 살아있도록 시도하는 빈도 수(maxLifetime보다 작아야함)
  • maximumPoolSize : 유휴 연결과 사용중인 연결 모두 포함해서 pool이 할당받을 수 있는 최대 크기.
    • pool이 최대 크기에 도달했고 유휴 가능한 connection도 없는 경우, connectionTimeout 동안 getConnection() 호출이 차단됩니다. connectionTimeout 시간 후에는 SQLException이 발생합니다.
  • poolName : CP에 대한 사용자 정의 이름
  • minimumIdle : HikariCP가 pool에서 유지하려고하는 최소 유휴 연결 수 (default : maximumPoolSize와 동일한 크기)
    • 만약 minimumIdle을 설정하지 않았으면 maximumPoolSize 로 고정된 pool로 동작합니다. 그런 경우, idleTimeout이 적용되지 않습니다.

 

참고

 

공부하며 작성하여 부족하거나 잘못된 부분이 있을 수 있으니
참고 부탁드립니다.
피드백과 댓글은 환영입니다.
반응형