Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Kuma's Curious Paradise

[유레카] 떠오르는대로(!) 정리하는 유레카 수업 1 : DB 커넥션 풀은 무엇일까? 본문

스프링

[유레카] 떠오르는대로(!) 정리하는 유레카 수업 1 : DB 커넥션 풀은 무엇일까?

쿠마냥 2024. 9. 6. 10:56

₩DB 커넥션 풀(Connection Pool)은 데이터베이스와의 연결을 미리 여러 개 생성해 두고, 애플리케이션이 필요할 때마다 이 연결을 받아와 쓰고, 사용 후에는 반납하여 연결을 재사용하는 기법이다.

 

새로운 커넥션을 매번 생성하지 않고 재사용하여 리소스와 성능, 두 마리 토끼를 잡는다. 

 

아래는 오라클에서 jdbc 커넥션 풀을 설명하기 위해 그린 그림이다. 커넥션 풀에서 미리 연결을 쥐고 있다가, 필요할 때마다 대출(?)해 준다. 커넥션의 최소, 최대 개수, 커넥션 대기 시간 등을 지정할 수 있기 때문에 애플리케이션 부하를 효율적으로 관리할 수 있다. 

 

스프링부트에서는 기본적으로 HikariCP를 사용한다. 빠르고 가볍고 성능이 뛰어나다고 알려져 있다. DB I/O 작업이 지연되어 커넥션이 모두 점유되고, 커넥션이 부족해질 경우 아래와 같은 에러 메시지를 만나기도 한다. 히카리CP는 leak detection처럼 커넥션 누수를 감지하는 기능이나 fault toleranc와 같이 오류를 허용하는 등 좀 더 세밀하고 전문적인 커넥션 관리가 가능하다. 

Caused by: java.sql.SQLTransientConnectionException: HikariPool-2 - Connection is not available, request timed out after 30003ms.
    at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:696)
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:181)
    at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:146)
    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:128)
    ...

 

 

톰캣 서버에서도 기본적으로 커넥션풀을 제공한다. Tomcat JDBC Pool이다. 멀티 스레드 환경에서 HikariCP만큼 빠르지는 않다고. 

hikariCP가 도입되기 전, 톰캣 기반 애플리케이션들은 Tomcat JDBC Pool을 사용했다고 한다. 스프링부트 2.0부터 HikariCP가 기본 커넥션 풀로 채택되면서 전환이 되었다.

 


 

HikariCP가 도입되기 전, 톰캣의 커넥션 풀을 어떻게 관리했을까? 코드와 함께 살펴보자. 

 

[DBManager]

// 톰캣이 관리하는 커넥션 풀(data source)에서 커넥션 객체 얻고 다 쓰면 반납
public class DBManager {
    public static Connection getConnection() {
        Connection con = null;
        try {
            Context context = new InitialContext();
            DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/madangdb");
            con = ds.getConnection(); // 톰캣이 관리하는 커넥션풀로부터 객체 얻는 라인. 반납은 알아서.
        } catch (Exception e) {
            e.printStackTrace();
        }

        return con;
    }

    public static void releaseConnection (PreparedStatement pstmt, Connection con) {
        try {
            pstmt.close();
            con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void releaseConnection (ResultSet rs, PreparedStatement pstmt, Connection con) {
        try {
            rs.close();
            pstmt.close();
            con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

 

위의 DBManager 클래스는 데이터베이스와의 연결을 관리한다. 톰캣(Tomcat) 서버가 관리하는 커넥션 풀을 통해 데이터베이스 연결을 가져오고, 사용 후에 해당 연결을 해제하는 것이 주 업무. 

 

getConnection() 부분을 자세히 보자. 

public static Connection getConnection() {
    Connection con = null;
    try {
        Context context = new InitialContext();
        DataSource ds = (DataSource) context.lookup("java:comp/env/jdbc/madangdb");
        con = ds.getConnection();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return con;
}

 

  • getConnection() 메서드는 static으로 되어 있어, 어디서든 DBManager.getConnection()를 호출하여 Connection 객체를 얻을 수 있다. 이 객체는 데이터베이스와 실제로 연결된 객체로, JDBC의 중요한 인터페이스다. 쿼리 실행과 트랜잭션을 관리한다. 
  • 먼저 new InitialContext() 를 호출하여 Context 객체를 호출한다. Context객체는 애플리케이션 서버(톰캣)와 외부 자원(여기서는 데이터베이스)를 연결하는 객체이다.  
  • 이후 lookup()을 호출하여 DataSource를 찾는다. "java:comp/env/jdbc/madandb"는 톰캣이 관리하는 데이터소스(여기서는 JDBC)의 경로다. 이 부분을 통해 톰캣은 데이터 소스를 찾을 수 있다. 
  • 여기서 "java:comp/env/jdbc/madandb"는 톰캣 내에서 정의된 경로로, 해당 리소스에 접근하기 위해 필요한 부분(실제 mysql 서버 url, dbUserName, dbPassword 등은 톰캣의 conf 폴더 내 context.xml에 적어둔다.
  • 이제 DataSource를 통해 톰캣의 커넥션 풀에서 Connection 객체를 얻어올 수 있다. DataSource는 JDBC에서 제공하는 인터페이스로, 데이터베이스와의 연결에 대한 '설정'을 관리한다. 주로 서버가 커넥션 풀을 관리할 때 사용된다. 

 

아래쪽에 적힌 DB 관련 내용들

 

 

 

이제 연결을 해제하는 부분을 살펴보자. 오버로딩 되어 있는데, 그중 밑에 것을 가져왔다. 

    public static void releaseConnection (ResultSet rs, PreparedStatement pstmt, Connection con) {
        try {
            rs.close();
            pstmt.close();
            con.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

 

  • releaseConnection()은 ResultSet, PreparedStatement, Connection 객체를 모두 close() 하여 리소스를 반환한다. 
  • releaseConnection을 할 때는 쿼리 실행과 관련된 모든 객체들을 닫아주어야 리소스 낭비가 발생하지 않는다. ResultSet(쿼리 실행 결과로 반환된 데이터를 담는 객체), PreparedStatement(쿼리를 미리 컴파일하고 실행을 준비하는 객체. sql문을 파라미터로 받는다.) 또한 닫아주어야 한다. 

 

자, 그럼