CS/DB

[데이터베이스] Database Connection Pool, DBCP

BongChun 2023. 7. 4. 11:55

Database Connection Pool 이란?

편의를 위해 DBCP로 칭하도록 하겠습니다.

 

DBCP는 미리 연결된 Connection을 Connection Pool에 만들어두고, 필요에 의해 사용하고 반환함으로써 Connection을 재활용할 수 있게 해주는 기법을 의미한다.

 

DBCP는 연결 생성, 연결 재사용, 연결 제한, 연결 유지, 연결 검사와 같은 주요한 기능을 제공한다.

  • 연결 생성 : Connection을 얼마나 생성할 지 지정할 수 있다.
  • 연결 재사용 : 생성된 Connection을 반환받아 재사용할 수 있다
  • 연결 제한 : DBCP 내부의 Connection을 얼마나 개방할 지 제한한다. 이를 통해서 시스템 부하를 관리할 수 있다.
  • 연결 유지 : Connection이 사용되고 있지 않아도 일정시간 연결이 끊어지지 않도록 유지한다.
  • 연결 검사 : Connection이 제대로 작동하고 있는지 주기적으로 검사한다.

동작 과정

Thread는 서버에서 처리하려는 요청의 로직이다. 요청이 DB와 관련이 있다면 DBCP에서 Connection을 얻어 DB와 소통한다.

 

동작 과정은 다음과 같다.

  1. 애플리케이션의 요청에 따라 DB에 접근하기 위해 DBCP에 Connection을 요청한다..
  2. 가져온 Connection을 통해 DB와 소통하여 로직을 처리한다..
  3. 사용을 마친 Connection을 다시 DBCP에 반환한다..

사용하는 이유

DB와 관련된 로직을 처리하는 과정 중에서 가장 비용이 많이 드는 것은 DB와의 Connection을 생성과 소멸이다. 초기에 Connection을 미리 생성해두고 재활용한다면 매 요청마다 DB의 Connection을 생성하지 않아도 되기 때문에 합리적이라고 할 수 있다. 또한 미리 생성된 Connection을 통해 접근하기 때문에 접근 시간도 단축된다.

 

정리하면 다음과 같은 장점이 있다.

  • 매 요청마다 DB를 생성하지 않아도 되기 때문에 생성 비용을 줄일 수 있다.
  • 미리 생성된 Connection을 이용하기 때문에 DB에 바로 접근이 가능해 접근 시간이 단축된다.
  • Connection의 수를 조정해 DB에 대한 접근을 제어할 수 있어 부하를 제어할 수 있다.

DBCP는 애플리케이션의 성능과 효율을 크게 향상시킬 수 있는 기능이지만, 풀의 크기와 설정을 적절하게 설정하지 않으면 오히려 성능이 저하되거나 메모리가 낭비되는 부작용이 나타날 수 있다.

DBCP Example

아주 간단한 구현을 통해 DBCP의 유무에 따른 성능 차이를 비교해보자.

 

간단한 SELECT문을 사용하는 두 개의 메소드를 정의하고, 해당 메소드를 반복해서 호출하는 방식으로 시간을 측정한다. 각 메소드의 차이는 내부에서 Connection 생성 유무의 차이이다.

 

즉, 커넥션 풀의 사용유무라고 생각하시면 된다.

호출 시 Connection 생성

package com.example.dbtest.db;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class NotUseCpTestEx {
    public static void main(String[] args) {

        long start = System.nanoTime();

        for (int i = 0; i < 1000; i++) {
            getData();
        }

        long end = System.nanoTime();

        System.out.println("실행 시간: " + (end - start) / 1_000_000 + "ms");
    }

    private static void getData() {

        String url = "jdbc:mysql://localhost:3306/testdb";
        String username = "root";
        String password = "0000";

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");

            Connection conn = DriverManager.getConnection(url, username, password);
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT * FROM test_table");

            while (rs.next()) {
                String name = rs.getString("name");
            }

            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 위 코드는 JDBC를 통해 getData() 메서드를 호출할 때마다 Connection을 생성한다.
  • 위 코드는 실행 시간: 5276ms 를 출력한다.

Connection Pool 사용

package com.example.dbtest.db;

import org.apache.commons.dbcp2.BasicDataSource;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class ConnectionPoolEx {

    public static void main(String[] args) {
        BasicDataSource ds = new BasicDataSource();

        ds.setUrl("jdbc:mysql://localhost:3306/testdb");
        ds.setUsername("root");
        ds.setPassword("0000");
        // 풀이 유지할 수 있는 최소 idle 연결 수
        ds.setMinIdle(100);
        // 풀이 유지할 수 있는 최대 idle 연결 수
        ds.setMaxIdle(100);
        // DBCP 내부의 전체 Connection에 존재하는 PreparedStatement의 갯수를 제한
        ds.setMaxOpenPreparedStatements(100);

        long start = System.nanoTime();

        for (int i = 0; i < 1000; i++) {
            printData(ds);
        }

        long end = System.nanoTime();

        System.out.println("실행 시간: " + (end - start) / 1_000_000 + "ms");
    }

    private static void printData(BasicDataSource ds) {
        try (Connection conn = ds.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery("SELECT * FROM test_table")) {

            while (rs.next()) {
                rs.getString("name");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  • 위 코드는 DBCP를 통해 미리 생성된 Connection을 얻어 DB에서 데이터를 조회한다.
  • 위 코드는 실행 시간: 1080ms 를 출력한다.

 

두 상황을 비교했을 때의 성능 차이는 5배에 달한다. 결과를 토대로 보면 DB Connection의 생성과 소멸 비용이 굉장히 큰 비용이라는 것을 알 수 있다.

 

출처

https://hudi.blog/dbcp-and-hikaricp/

https://github.com/WeareSoft/tech-interview/blob/master/contents/db.md#데이터베이스-풀