본문 바로가기
Spring|Spring-boot

스프링 DB (2)

by oncerun 2022. 8. 15.
반응형

 간단히 이론을 보고 머릿속으로 상상하고 사용하는 것과 이해하는 것에 많은 차이가 있는 부분이 커넥션이라고 생각한다.

커넥션 관리라는 것이 생각보다 쉽지 않은 과정이고 앱과 데이터베이스의 연결로 인해 발생하는 오류가 생각보다 많이 발생하기 때문이다.

이에 대해 앱과 데이터베이스 간의 상호작용, 각 의미를 기억한다면 추후 분명하게 도움이 될 거라 생각한다.

커넥션 풀

 우선 왜 커넥션 풀(Connection Pool)이 필요한지 알아야 한다.

우리는 한정된 자원 안에서 동작하는 앱을 만든다. 무한한 자원이란 없다. 그중 애플리케이션은 데이터베이스와 수많은 상호작용을 해야 한다. 왜냐하면 모든 데이터가 데이터베이스에 있기 때문이다.

 

커넥션은 말 그대로 애플리케이션과 데이터베이스가 소통하기 위한 연결 통로를 의미한다. 이를 생성하기 위해서는 어쩔 수 없이 자원을 점유하게 된다. 

 

 앱과 데이터베이스의 연결과정에는 대게 TCP/IP을 통해 연결하기에 연결하는 데 3 way handshake가 들어가고 정보를 전달 후 4 way handshake를 통해 연결을 종료한다. 

또한 DB드라이버를 호출하여 연결하고자 하는 데이터베이스를 찾고 접속 및 SQL 실행을 위한 정보를 전달하는 앱 내부에서 사용하는 자원도 있을 것이다.

연결했다면 데이터베이스에서 세션을 생성하고 실행하여 정보를 뽑는 데이터베이스 내부 부하도 당연히 발생한다.

여기까지가 하나의 커넥션을 연결하고 반납하는 과정이다. 

 

클라이언트 수가 상당수 이상을 넘어가게 된다면 어떻게 될까? 좋지 않은 경험을 하게 될 것이다.

이를 해결하기 위한 여러 방법 중 하나가 바로 커넥션 풀이다. 

 

앱 초기화 시점에 데이터베이스와 JDBC로 연결해놓은 객체들을 여러 개 생성해 특정 자료구조에 모아두는 것이다.

그리고 이를 통해 필요할 때 객체 참조를 통해 즉시 데이터베이스에게 정보 요청을 할 수 있다. 

커넥션 풀은 단지 성능에만 영향을 미치는 것이 아니다. 관리한다는 관점으로 보면 보안적으로 또는 확장에 유용할 수 있고 비정상적인 요청 수를 제한함으로써 오류를 방지할 수 있는 것이다.

 

 실제로 많은 프로젝트에서 기본 값으로 커넥션 풀을 사용한다. 다만 커넥션의 수, 제한, 초기 값 등등 설정하는 것은 서버 스펙, 서비스 사용자의 수를 토대로 계산하기에 고정 값이란 없다. 

 

이러한 여러 이점을 제공하는 커넥션 풀은 개념적으로 되게 단순하다. 그렇기에 직접 구현도 가능하지만

김영한 님은 추가적으로 커넥션 풀 오픈소스에 대해 알려주셨다. 

아마 레거시 프로젝트의 라이브러리 의존성을 확인해보면 commons-dbcp2, tomcat-jdbc pool. HikariCP로 나뉠 것이다. 

난 레거시를 많이 접해서 commons-dbcp2를 사용하는 프로젝트를 많이 봤는데, 이제는 hikariCP를 주로 사용한다고 한다. 

 

이제 커넥션 풀의 필요성과 동작 과정과 종류에 대해 알아보았다. 그럼 또 하나 알아야 할 것은 연결을 취득하는 방법에 대한 고민이다.

 

데이터베이스 커넥션을 클라이언트가 취득하는 방법은 지금까지 배운 대로라면 두 가지이다. 

순수 JDBC를 사용해 DriverManager를 통해 얻는 방법과 커넥션 풀에서 커넥션을 가져오는 방법이다.

여기서 문제가 하나 존재한다. 클라이언트가 구현체에 직접적으로 의존하게 된다는 점이다.

DIP (Dependency Inversion Principle)을 위반하게 되면 건강한 코드 베이스가 악화될 가능성이 존재한다.

그래서 자바는 javax.sql.DataSource를 제공한다. 

 

DataSource 인터페이스의 주요 기능은 단 한 가지 getConnection 메서드이다.

public interface DataSource  extends CommonDataSource, Wrapper {

  Connection getConnection() throws SQLException;
  
}

 

즉 커넥션을 얻는 방법에 대한 추상화를 통해 만약 커넥션 취득의 방법이 변경되더라도 유연하게 대처할 수 있도록 대비할 수 있다.

 

이제 코드를 비교할 차례다. 

 

(DriverManagerDataSource는 스프링에서 제공하는데,  DriverManager를 사용해 DataSource를 사용할 수 있도록 한 클래스이다. DriverManager는 DataSource를 구현하지 않았기 때문)

 

 

1. DriverManager

Connection conn1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);

2. DriverManagerDataSource

스프링 공식홈페이지에서는 다음과 같이 말한다.

You should use the DriverManagerDataSource and SimpleDriverDataSource classes (as included in the Spring distribution) only for testing purposes! Those variants do not provide pooling and perform poorly when multiple requests for a connection are made.

SimpleDriverDataSource, DriverManagerDataSource는 풀링을 제공하지 않고, 멀티 요청에 대한 성능 저하가 있기 때문에 테스트 용도로만 사용하라고 권고한다. !

 

아마 이는 요청마다 커넥션을 만들기 때문일 것이다.

DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
Connection conn1 = dataSource.getConnection();

3. ConnectionPool

HikariDataSource dataSource = new HikariDataSource();
settingDataSource(dataSource);
useDataSource(dataSource);

 

더보기


   @Test
    void driverManager() throws SQLException {
        Connection conn1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        Connection conn2 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        assertThat(conn1). isNotEqualTo(conn2);
    }

    @Test
    void dataSourceDriverManager() throws SQLException {
        DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
        useDataSource(dataSource);
    }

    @Test
    void dataSourceConnectionPool() throws SQLException, InterruptedException {
        HikariDataSource dataSource = new HikariDataSource();
        settingDataSource(dataSource);
        useDataSource(dataSource);
        Thread.sleep(1000);
    }

    private void settingDataSource(HikariDataSource dataSource) {
        dataSource.setJdbcUrl(URL);
        dataSource.setUsername(USERNAME);
        dataSource.setPassword(PASSWORD);
        dataSource.setMaximumPoolSize(20);
    }


    private void useDataSource(DataSource dataSource) throws SQLException {
        Connection conn1 = dataSource.getConnection();
        Connection conn2 = dataSource.getConnection();
        assertThat(conn1).isNotEqualTo(conn2);
    }

 

* DriverManager를 사용하여 얻는 커넥션은 매번 새로운 커넥션을 얻는다.

 

크게 다른 점은 보이지 않는다. DataSource의 책임 자체가 크지 않기 때문이다.

다만 코드에 대해 기억해야 할 부분이 있는데, 커넥션 풀의 커넥션을 채우기 위해선 별도의 스레드를 활용해 애플리케이션 초기화 과정에 영향을 최소화한다는 점이다.

만약 커넥션 풀의 유휴 커넥션이 없는데 요청이 온다면 해당 요청은 특정 설정 시간만큼 대기하게 된 후

그래도 커넥션을 얻을 수 없다면 요청이 실패한다.

이 과정에서는 해당 스레드가 블락 상태가 되어버리기 때문에 설정 값에 대한 신중한 설정이 필요하다.

 

마무리로 정리하자면 DataSource는 커넥션을 획득하는 방법을 추상화한 인터페이스, 커넥션 풀은 커넥션을 관리하는 특정 구조, JDBC는 자바에서 데이터베이스를 다루기 위한 인터페이스라고 할 수 있다.

반응형

'Spring|Spring-boot' 카테고리의 다른 글

Spring DB (4)  (1) 2022.09.19
스프링 DB (3)  (0) 2022.09.05
스프링 DB (1)  (0) 2022.08.15
파일 업로드 및 다운로드  (0) 2022.01.26
Spring-boot ExceptionResolver  (0) 2022.01.15

댓글