본문 바로가기

Spring/Spring

스프링 부트 2.0의 default DBCP, hikariCP가 그렇게 빠르다던데? (hikariCP 저만의 성능 테스트를 해봤습니다.)

반응형

HikariCP

hikariCP는 스프링 부트 2.0부터 default JDBC connection pool이다. 

hikariCP github사이트에 가보면 엄청 빠르고, 가볍고, 신뢰할 수 있다고 설명한다.

심지어 "zero-overhead"라며 엄청나게 높은 성능을 강조하고 벤치마크 결과도 보여준다.

<출처 : https://github.com/brettwooldridge/HikariCP>

원하면 벤치마크 테스트 소스도 가져가서 직접 돌려보라고도 하는데 자세한건 모르겠고 나만의 테스트 환경을 만들어서 테스트 해보기로 했다.


스프링 부트에서 hikariCP 성능 테스트

PC 사양도 중요하고 인터넷 사양도 중요하고 로컬 DB냐 원격 DB냐도 갈리고 여러 환경이 중요한 것으로 아는데 크게 생각 안하고 단순히 직접 경험해본다에 중점을 두었다.

먼저 hikariCP는 스프링부트 2.0부터 default로 설정되어있는 DBCP라는 점을 이용해서 똑같은 코드를 2.0.3 버전에서 한번 돌려보고, 1.5.14 버전에서 돌려보는 것으로 실험했다.

(참고로 1.5.14 버전은 default jdbc connection pool은 apache tomcat jdbc pool 이다.)

[프로젝트 구조]

-> HikariApplication.java는 그대로 두고 "tester.java"라는 컨트롤러 하나 만들고 "application.yml", "pom.xml"만 수정했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring: 
  application:
    name: SpringBootJdbc
  datasource: 
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 1234
    driver-class-name: com.mysql.jdbc.Driver
    hikari: 
      pool-name: hikari-cp
      maximum-pool-size: 30
      minimum-idle: 2
      data-source-properties: 
          cachePrepStmts: true
          prepStmtCacheSize: 250
          prepStmtCacheSqlLimit: 2048
          useServerPrepStmts: true


[application.yml]

-> data-source-properties를 이용한 옵션은 MySQL에서 성능을 보려면 설정해줘야 한다고 공식 github에 나와서 적용했다.

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
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
</dependencies>


[pom.xml]

-> dependency 부분만 가져왔다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.example.demo.controller;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
 
import javax.sql.DataSource;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class tester {
        
    @Autowired
    DataSource dataSource;
    
    @GetMapping("/")
    public String helloworld() throws SQLException, InterruptedException{
        Thread[] thread = new Thread[1000];
        Runnable[] run = new Runnable[1000];
        for(int j=0;j<1000;j++) {
            run[j] = new Runnable() {
                @Override
                public void run() {
                    try {
                        for(int i=0;i<100;i++) {
                            Connection con = dataSource.getConnection();
                            PreparedStatement psmt = con.prepareStatement("SELECT * FROM USER");
                            ResultSet rs = psmt.executeQuery();
                            //psmt.close();
                            con.close();
                        }
                    } catch (SQLException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            };
        }
        long start = System.currentTimeMillis();
        for(int i=0;i<1000;i++) {
            thread[i] = new Thread(run[i]);
            thread[i].start();
            thread[i].join();
        }
        long end = System.currentTimeMillis();
        System.out.println(dataSource);
        System.out.println(end-start);
        return "hello";
    }
}
 


[test.java]

-> @Autowired 로 DataSource를 가져왔다.

DB 커넥션을 연결하고, select문 쿼리 한번 날리고, 커넥션을 닫는 과정을 100번씩 반복하는 쓰레드를 1000개를 만들어서 실행시켰다. (참고로 select문으로 조회하는 DB 테이블의 레코드는 2개만 있다.)

[결과]

-> 결과적으로 약 12초정도 걸렸다. 실험이 적절한지도 의문인데 결과도 의문이다... 단순하게보면 10만번 connection이 있었고 10만번의 조회 쿼리가 있었을 뿐인데 너무나 오래걸렸기 때문이다.

1000개의 쓰레드가 풀 사이즈인 30개의 커넥션을 가지고 경쟁하는 상황에서 빠른 사용과 반납으로 처리가 되게 코드를 짠건지 의심스럽다. (100%는 아니어도 코드가 순차적인 것 뺴면얼추 비슷하게는..)

아무튼 결과는 hikariCP에서 말하는 성능 때문에 갖고 있던 기대에 비해 못 미쳤다.

[비교 결과]

-> org.apache.tomcat.jdbc.pool 의 결과다. 약 14초정도 걸렸다.

2초정도 차이가 나는 것이 스프링1.5.14 -> 스프링 2.0.3으로 개선된 효과일 수도 있고.. 실제 hikariCP의 효과일 수도 있고, hikariCP에서 특별하게 주었던 설정 값 때문일 수도 있다.

오늘 1차적으로 실험한 조건?은 엉망일 수 있다.

하지만 그럼에도 불구하고 약간의 성능상의 이득이 생겼다. 또한 spring boot에서 공식으로 지정할 만큼 강력한 dbcp임은 틀림없다.

또한, github에서 설명하듯이 다양한 옵션들이 제공되고 있으니 활용하면 훨씬 더 나은 성능을 가져갈 수 있을 듯하다. 나중에 하나하나 읽어보면서 적용해보면 좋을 듯하다.


참고 사이트 : 

https://github.com/brettwooldridge/HikariCP

반응형