データベースにアプリケーションから接続する場合、多くの方がコネクションプーリングを行っていると思います。
Spring Bootの場合はデフォルトでHikariCPが有効になっているため、知らずにコネクションプーリングを利用しているということもあると思います。
コネクションプーリングは、SQL発行時に既存の接続確立済みのコネクションを使いまわすことによって、
データベースへの接続処理を省き、パフォーマンスを向上させるものですが、実際どれくらい効果があるのか、計測してみました。
コネクションプーリングの種類
コネクションプーリングの方式は大きく分けて「クライアント型」「サーバ型」の2種類があります。
よく使われているのはクライアント型で、HikariCPのようにアプリケーションがコネクションをプールします。
サーバ型は、アプリケーションとデータベースの間にコネクションプーリングを行うサーバをたてる方式です。プロキシ型と言った方がわかりやすいかもしれません。
アプリケーションは一切プーリングを行わず、毎回プーリングサーバに接続します。プーリングサーバはデータベースとの接続をプーリングしつつアプリケーションからのリクエストをデータベースに投げます。
代表的なコネクションプーリングサーバは「PgBouncer」です。
計測プログラム
計測は以下のJavaプログラム(Spring Boot)で行いました。
1万回 SELECT 1; を実行するメソッドを5回実行し、最後の3回の平均を計測結果とします。
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 |
import java.time.Duration; import java.time.LocalDateTime; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.jdbc.core.JdbcTemplate; @SpringBootApplication public class Application implements CommandLineRunner { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Autowired private DataSource ds; @Override public void run(String... args) throws Exception { long duration = 0l; exec(10000); exec(10000); duration += exec(10000); duration += exec(10000); duration += exec(10000); System.out.println("AVG: " + duration / 3 + "ms"); } private long exec(int count) { JdbcTemplate jt = new JdbcTemplate(ds); LocalDateTime start = LocalDateTime.now(); for (int i = 0; i < count; i++) { jt.execute("select 1"); } LocalDateTime end = LocalDateTime.now(); long duration = Duration.between(start, end).toMillis(); System.out.println(duration + "ms"); return duration; } } |
計測結果
コネクションプーリングなし、HikariCP、PgBouncerの3ケースで計測を行いました。
コネクションプーリングなしとPgBouncerの計測ではコネクションプーリングをオフにするため、
application.ymlのdatasourceの設定に
1 |
type: org.springframework.jdbc.datasource.DriverManagerDataSource |
を追加しました。
PostgreSQLのバージョンは 11.5 です。
さて、結果は。。。
なし | 37617ms |
---|---|
HikariCP | 3674ms |
PgBouncer | 29284ms |
HikariCPの結果が圧倒的ですね!コネクションプーリングなしと比べて約10倍速いです。コネクションプーリングは有効ですね。
PgBouncerの結果はコネクションプーリングしないよりはマシ、程度ですね。
アプリケーションとPgBouncerの接続確立コストがかなり高いようです。
この結果だけをみるとPgBouncerを導入するメリットがないようにみえますが、PgBouncerには非常に便利な機能「pause」があります。
pauseとは、一時的にアプリケーションからの新規接続要求を待たせる機能です。これによって、一時的にPostgreSQLに一切接続がない状態を作ることができ、この間にPostgreSQLを再起動することも可能です。pauseを解除すれば何事もなかったかのように接続が確立され、正常にSQLが実行されます。
そのほかにも、多くのアプリケーションから接続されるようなデータベースの場合に、一括してプーリングの管理ができるというのもメリットです。
PgBouncerの結果は確かにあまりよくないですが、1コネクションあたりでみれば数ミリ秒程度です。
構築するシステムの規模・特性によっては問題とならず、PgBouncerの機能は魅力的ではないでしょうか。