본문 바로가기
Spring/스프링 기본 원리

[Spring DataSource] Connection Pool, DataSource 란?

by Taler 2022. 9. 7.

웹 애플리케이션을 개발한다고 할 때 데이터베이스는 거의 필수적이다. 데이터베이스에 데이터를 넣고, 특정 데이터를 읽어오기 위해서는 먼저 데이터베이스에 연결되어있어야 한다. SpringBoot로 프로젝트를 구성한다면 그냥 application.yml에 메타정보를 등록하면 알아서 해주니까 데이터베이스에 어떤 과정을 거쳐서 연결되는지 알 수가 없다.

 

spring:
  datasource:
    hikari:
        driver-class-name: org.postgresql.Driver
        jdbc-url: "jdbc:postgresql://127.0.0.1:5433/testdb"
        username: root
        password: root
        
.

 

하지만 Spring의 properties 파일이나 yml 파일로 설정을 구성할 때도 위와 같은 datasource라는 이름을 볼 수는 있다. 해당 설정 값을 읽고 스프링에서 자동으로 DataSource를 생성해서 관리해주고 있었던 것. 즉, 웬만해서는 직접 구성할 일은 없다.

 

하지만, 필자는 하나의 Spring 프로젝트에 여러 개의 데이터베이스를 연결해야 하는 프로젝트를 진행하고 있다. 해당 프로젝트를 General한 툴로 고도화시킬 생각인데, DB가 변경될 때마다 설정 값을 모두 변경하는 것은 생각보다 귀찮다. 또한 2개 이상의 DB를 연결하는 것도 생각보다 이질적인 부분이 많았다.

 

사실 나중에 알게된 거지만, 실제로 현업에서는 2개 이상의 데이터베이스를 연결해 사용하는 경우가 많고 (최소 Redis와 RDBMS는 사용한다.) 때문에 Configuration 코드를 사용해서 직접 데이터베이스를 생성해 사용하는 것이 대부분이라고 한다. 즉, 알아두면 좋긴 할 것이다.

 

약간 샌 것 같은데, 본론으로 들어가보자. 아니, 본론으로 들어가기 전에 배경지식인 먼저 DB Connection, Connection Pool에 대해서 알아보고 DataSource가 어떤 과정으로 Connection을 생성해 관리하는지 알아보자.

 

Connection Pool


일단 DB Connection은 데이터베이스와의 연결을 뜻한다는 것은 직관적으로 알 수 있다. 여느 언어나 프레임워크가 그렇듯, DB와 상호작용 하기 위해서는 연결이 되어있어야 하는데, 그게 바로 Connection이다.

 

Java에는 DB Connection을 지원하는 JDBC라는 호환성이 뛰어나고 편리한 API가 있다. Hibernate, JPA, iBatis 등의 Data Access API들은 이 JDBC API를 사용해 DB에 접근하는 것.

 

JPA와 JDBC, DB 사이의 관계를 나타내주는 좋은 그림 (출처: https://ultrakain.gitbooks.io/jpa/content/chapter1/chapter1.3.html)

 

이 JDBC라는 편리한 API는 DB에 접근하기 위해 Connection 오브젝트를 사용한다. 그렇다면 Connection Pool은 무엇이고 왜 사용하는 것일까?

 

Connection Pool이 없을 때는 DB에 접근할 때마다 메타정보를 사용해 DB와 Connection하고, 그 Connection을 통해 DB와 상호작용한 뒤 끊었다. 즉, DB connection을 연결하고, 끊고를 반복했는데 Spring을 한 번 켤 때 볼 수 있듯, DB와 연결하는 작업은 생각보다 오래 걸린다.

 

Max_Pool_Size를 1로 지정했을 때 Connection 생성에 걸린 시간. 약 0.18초가 소요됐다.

Connection 한 개를 연결하기 위해서 0.18초가 소요됐다. 물론 Connection 생성 과정은 멀티스레딩으로 진행되기 때문에 10개의 Connection을 연결하는 데도 0.3초가 소요된다. 참고로 아래 코드에서 살펴보겠지만, 위 로그가 Connnection Pool 생성 전/후에 찍은 로그이기 때문에 해당 방법을 통해서 Connection 생성 시간을 측정하는 것은 타당하다고 할 수 있다.

 

Hikari의 경우 약 0.3초가 소요된다. 따로 개수를 지정하지 않으면 Default_Pool_size인 10개의 Connection이 생성된다.

DB에 접근할 때마다 이만큼의 오버헤드가 기본으로 추가된다는 것은 상당히 비효율적이다.

 

디자인 패턴에는 싱글톤(Singleton) 패턴이 있다. 싱글톤 패턴은 객체를 오직 1개만 생성하고, 이를 재활용하는 것을 의미한다. 객체의 개수는 여러 개로 지정할 수 있지만 싱글톤의 핵심은 instance를 미리 생성해서 그걸 가져다 사용하고, 반납하는 방식으로 사용하는 것이다. 이미 생성된 인스턴스를 사용하기 때문에 메모리도 효율적으로 쓸 수 있고, 속도도 더 빨라지게 된다.

 

이처럼 DB Connection에도 이런 싱글톤의 미학을 적용해서 미리 Connection Instance를 생성해서 저장해두고, 이를 대여해서 Connection을 사용하고, 다시 반납하는 방식으로 Connection을 관리하고자 하는 요구사항이 생겨났고, Connection Pool이 탄생하게 된다.

 

Connection Pool (CP)
DB Connection을 미리 만들고, 해당 Connection을 관리한다.

 

CP는 그 이름에서 짐작할 수 있듯, 저장해둔 Connection들을 모아두고 관리하는 Pool Instance이다. Connection Pool은 보통 createConnection, getConnection, evictConnection(returnConnection) 등의 Connection을 생성하고 대여하고, 반납하는 등의 메소드들을 포함한다. 일반적으로 CP instance의 생성자에 Connection을 생성하는 코드가 포함되어 있어, 생성과 동시에 DB Connection을 만들게 된다.

 

CP를 사용한다면 DB 연결 시간이 획기적으로 줄어드는 것을 확인할 수 있다. (CP를 사용하지 않는 DataSource도 있다.)

배경지식은 충분해진 것 같으니, 이제 DataSource에 대해서 알아보자.

 

 

DataSource


사용자의 요청이 빈번하게 일어나는 서비스에서는 사용자의 요청 마다 Connection 객체를 사용한다. 최근 Java에서 DB 접근은 효율성을 고려해서 Connection을 미리 생성해 Connection Pool에 저장해두고, 이를 재사용하는 형식으로 DB Connection을 구현됐다고 언급한 바 있다. 이 때 DB 연결 정보를 저장하고, Connection을 생성하고, Connection Pool에 등록하고, 관리하는 역할을 하는 것이 바로 DataSource이다.

 

DataSource
DB와의 연결을 미리 생성하고, 그것을 관리하는 역할을 하는 오브젝트.

 

간단히 DataSource는 DB Connection을 관리하는 인터페이스라고 생각하면 된다. 보통 getConnection() 등의 메소드를 지원하며, JPA 등 DB에 접근하고자 하는 툴들은 해당 메소드들을 통해서 DB Connection을 가져와 사용하고, 반납한다. 중요한 것이 getConnection파트이기 때문에 DataSource를 공부한다면, getConnection 메소드만 고려하면 된다고 한다.

 

본격적으로 DataSource에 대해서 알아보자.

 

생성

DataSource의 선언은 아래와 같이 이루어진다.

Multi Database를 위한 Spring Configuration Code

 

Hikari DataSource에 대한 설정 코드이긴 하지만, 대부분의 DataSource가 위 코드와 같이 Driver, Url, Username, Password 등을 입력받는다.

Max Pool Size(setMaximumPoolSize), Timeout, Minimum Pool size(setMinimumIdle) 등 추가로 입력할 수 있는 값들도 있다.

 

아무튼 저렇게 Configuration 값을 세팅해서 DataSource에 넘겨주면, DataSource는 해당 메타 데이터들을 토대로 DB와의 Connection을 생성하고, 이를 자신의 Connection Pool에 따로 저장한다. 또한, DataSource에서 해당 Connection을 가져와 사용할 수 있도록 getConnection이라는 메소드를 고정적으로 제공한다.

 

DataSource의 근간이되는 interface. 위 설계도의 구현체는 반드시 getConnection을 구현해야 한다.

 

종류

  • SimpleDriverDataSource: Connection Pool을 사용하지 않기 때문에 비효율적이다.
  • SingleConnectionDataSource: Connection을 하나만 만들어두고 이를 재활용한다. SimpleDriverDataSource보다는 효율적이지만, 두 개 이상의 스레드가 동작하는 엔터프라이즈 환경에서는 사용하지 않는 것이 좋다.
  • Apache Common DBCP: Apache 재단에서 만든 유명한 DB Connection Pool 라이브러리. 스프링부트 2.0 이전에는 Default DataSource로 사용됐다.
  • HikariCp: 스프링부트 2.0 이후에 표준이 된 DataSource. 오픈소스이고 안정적이며 빠르다.

 

사용법

DataSource의 Connection은 다음과 같이 사용할 수 있다.

public Optional<List<User>> findAll() {
    List<User> Users = new ArrayList<>();
    try {
        Connection connection = dataSource.getConnection();
        PreparedStatement statement = connection.prepareStatement(SELECT_ALL_SQL);
        ResultSet resultSet = statement.executeQuery();

        while (resultSet.next()) {
            mapToUser(users, resultSet);
        }
    }
    catch (SQLException e) {
        throw new RuntimeException(e);
    }

    return Optional.of(Users);
}

 

마치며


원래는 application.yml이나 application.properties 파일 명시하기만 하면 알아서 생성됐으며, JPA 등의 구현체가 알아서 JDBC 단을 추상화해줬기 때문에 DataSource에 대해서 크게 신경쓸 일이 없었다. 하지만 실제로 프로젝트를 진행하면서 DB와 Connection을 실제로 어떻게 연결되는지 알고 있다면, 그리고 어떤 값들을 조절할 수 있는지 정도까지 알고 있다면 좀 더 통제할 수 있는 범위가 넓어질 것이다.

 

통제할 수 있는 변인이 많아지고, 불확실한 것들이 적어지는 것이 실력을 말하는 것 같다.

댓글