Scala を使用したデータベース接続プール
最終更新日 2024年05月03日(金)
Table of Contents
接続プールは、事前に作成された一連の再利用可能な接続オブジェクトを使用してデータベースに接続するために、ソフトウェアアプリケーションによって使用されるパターンです。新しい接続が必要になった場合は、このプールから既存の接続が取得されます。接続を使用しているスレッドが完了すると、その接続は、別のスレッドで使用できるようにプールに戻されます。このパターンにより、ネットワークトラフィックが減少し、新しい接続を作成するコストが抑えられ、さらにガーベジコレクターへの負荷が軽減されるためデータベースへの接続のオーバーヘッドが削減されます。
多くの Scala および Java アプリケーションフレームワークには、独自の接続プール API が含まれています。ただし、すべてのフレームワークを設定するために使用される原則は一般に同じです。この記事では、Java Database Connectivity (JDBC) API と Apache DBCP プールライブラリを使用してデータベース接続プールを作成する方法について学習します。
すでに Scala アプリケーションがある場合は、それをこの例で使用できます。それ以外の場合は、先に進む前に「Heroku スターターガイド (Scala)」の記事から単純なアプリケーションを作成します。また、「Java を使用して Heroku でリレーショナルデータベースに接続する」にも精通しておいてください。
Apache DBCP の使用
アプリケーションの build.sbt
ファイルを開き、次のライブラリを追加します。サンプルのスターターガイドアプリケーションを使用している場合は、postgresql 依存関係のアップグレードが必要になることがあります。
Apache DBCP 2 は、Java 7 および JDBC 4.1 とのみ互換性がありますJava 6 または JDBC 4 を使用している場合は、DBCP 1.4 を使用する必要があります。
libraryDependencies ++= Seq(
"org.postgresql" % "postgresql" % "9.3-1102-jdbc41",
"org.apache.commons" % "commons-dbcp2" % "2.0.1"
)
ここで、sbt を実行して、新しい jar ファイルをダウンロードしてインストールします。
$ sbt clean stage
...
[info] downloading http://repo1.maven.org/maven2/org/postgresql/postgresql/9.3-1102-jdbc41/postgresql-9.3-1102-jdbc41.jar ...
[info] [SUCCESSFUL ] org.postgresql#postgresql;9.3-1102-jdbc41!postgresql.jar (436ms)
[info] downloading http://repo1.maven.org/maven2/org/apache/commons/commons-dbcp2/2.0.1/commons-dbcp2-2.0.1.jar ...
[info] [SUCCESSFUL ] org.apache.commons#commons-dbcp2;2.0.1!commons-dbcp2.jar (332ms)
[info] downloading http://repo1.maven.org/maven2/org/apache/commons/commons-pool2/2.2/commons-pool2-2.2.jar ...
[info] [SUCCESSFUL ] org.apache.commons#commons-pool2;2.2!commons-pool2.jar (238ms)
...
[info] Done packaging.
[success] Total time: 5 s, completed Aug 18, 2014 10:35:38 AM
次に、接続を作成するクラスのソースコードを開きます。スターターガイドアプリケーションから作業している場合は、Server.scala
ファイルを開きます。ファイルの先頭に次のステートメントを追加します。
import java.sql.Connection
import java.sql.Statement
import org.apache.commons.dbcp2._
次に、データベース接続プールを含めて設定するために、新しいシングルトンオブジェクトを追加します。ファイルの末尾に次のコードを追加します。
object Datasource {
val dbUri = new URI(System.getenv("DATABASE_URL"))
val dbUrl = s"jdbc:postgresql://${dbUri.getHost}:${dbUri.getPort}${dbUri.getPath}"
val connectionPool = new BasicDataSource()
if (dbUri.getUserInfo != null) {
connectionPool.setUsername(dbUri.getUserInfo.split(":")(0))
connectionPool.setPassword(dbUri.getUserInfo.split(":")(1))
}
connectionPool.setDriverClassName("org.postgresql.Driver")
connectionPool.setUrl(dbUrl)
connectionPool.setInitialSize(3)
}
このメソッドでは、プールされていない接続の場合と同様に username
、password
、dbUrl
を取得します。その後、これらのパラメータを使用して connectionPool
を初期化し、connectionPool.setInitialSize(3)
を呼び出してプールの初期サイズを設定します。BasicDataSource
オブジェクトがこれらの接続をただちに自動的に作成した後、アプリケーションがトラフィックの受信を開始すると、その接続はすぐに使用できるようになります。
プールから接続を次のように取得できます。
val connection = Datasource.connectionPool.getConnection
val stmt = connection.createStatement()
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS ticks (tick timestamp)")
stmt.executeUpdate("INSERT INTO ticks VALUES (now())")
val rs = stmt.executeQuery("SELECT tick FROM ticks")
while (rs.next()) {
println("Read from DB: " + rs.getTimestamp("tick") + "\n")
}
接続オブジェクトが取得されたら、他の JDBC Connection
とまったく同じように使用できます。
接続プールの初期サイズの設定に加えて、その最大サイズや (接続が破棄され、新しい接続で置き換えられるまでの) 接続の最大有効期間、またはプールのサイズを調整するまで保持されるアイドル接続の最大数と最小数を設定することもできます。これらのすべてを、BasicDataSource クラスのメソッドを使用して設定できます。
接続プールを設定する方法について学習しましたが、設定するときにどのような値を使用するかを明確にすることは別のトピックで説明します。
接続プールの設定
プール内でウォーム状態に維持されるアイドル接続の数は、アプリケーションのサイズや性質によって異なります。多くのユーザーが、HTTP リクエストを処理するスレッドあたり 1 つの接続で十分であることに気付いています (HTTP リクエストを処理するスレッドが接続を使用する唯一のスレッドである場合)。アプリケーションが、接続を新しいスレッドにすぐに引き継げないほど非常に高いスループットを処理している場合は、さらに多くを必要とする可能性があります。または、すべての HTTP リクエストがデータベースへのアクセスを必要としているわけでない場合は、少なくて済む可能性があります。最終的には、本番環境の負荷の下でのアプリケーションのプロファイリングが適切なプールパラメータを決定するための最善の方法です。
開発環境では、データベースをチェックすることにより、アプリケーションによって使用される接続の数を確認できます。
$ psql -h localhost
psql (9.3.2)
Type "help" for help.
jkutner=# \q
これにより、開発データベースへの接続が開かれます。その後、次を実行して Postgres データベースへの接続の数を確認できます。
select count(*) from pg_stat_activity where pid <> pg_backend_pid() and usename = current_user;
これにより、そのデータベース上の接続の数が返されます。
count
-------
5
(1 row)
シミュレートされた本番環境の負荷の下で、これは必要なプールのサイズを適切に示しています。ただし、いくつかの制約があります。
データベース接続の最大数
Heroku では、マネージド Postgres データベースが提供されます。 階層型データベースごとにさまざまな接続制限があります。これは、Heroku Postgres アドオンのドキュメントにある一覧で見つけることができます。低い層のデータベースでは、高い層のデータベースより少ない接続が許可されます。データベースは、アクティブな接続の最大数に達すると、新しい接続を受け付けなくなります。これにより、アプリケーションが接続タイムアウトになり、例外が発生する可能性があります。
スケールアウトするときは、アプリケーションに必要なアクティブな接続の数に注意することが重要です。各 dyno で 5 つのデータベース接続が許可されている場合は、より堅牢なデータベースのプロビジョニングが必要になるまでに、4 つの dyno にしかスケールアウトできません。
これで、接続プールを設定する方法や、データベースで処理できる接続の数を見つける方法がわかったので、各 dyno に必要な接続の適切な数を計算する必要があります。
PgBouncer を使用した接続の制限
データベース接続の制限に達するまで、追加の dyno を使用して引き続きアプリケーションをスケールアウトできます。この時点に達する前に、PgBouncer buildpack を使用して、各 dyno に必要な接続の数を制限することをお勧めします。
PGBouncer では、データベーストランザクションで共有される接続のプールが保持されます。これにより、Postgres への接続 (通常は開いており、アイドル状態) が最小限に維持されます。ただし、トランザクションプーリングでは、名前付きプリペアドステートメント、セッションアドバイザリロック、リッスン/通知、またはセッションレベルで操作するその他の機能を使用できなくなります。詳細は、「PgBouncer buildpack FAQ for full list of limitations」(制限の完全なリストに関する PgBouncer buildpack の FAQ) を参照してください。
多くのフレームワークでは、PGBouncer を使用するためにプリペアドステートメントを無効にする必要があります。次に、他の buildpack を呼び出すカスタム buildpack を使用するようにアプリを設定します。
JDBC の場合、これには接続文字列への prepareThreshold=0
の追加が必要です。ただし、JDBC ドライバへのパッチの適用も必要になることがあります。
プリペアドステートメントを無効にするか、またはフレームワークでそれが使用されていないことを確認する前に続行しないでください。
$ heroku buildpacks:add heroku/pgbouncer
次に、アプリケーションを確実に実行可能にする必要があるため、言語固有の buildpack を追加する必要があります。Scala を使用しているため、次のようになります。
$ heroku buildpacks:add heroku/scala
ここで、PgBouncer を起動するように Procfile
を変更する必要があります。Procfile
で、コマンド bin/start-pgbouncer-stunnel
を web
エントリの先頭に追加します。そのため、Procfile
が
web: target/universal/stage/bin/scala-getting-started
であった場合は、次のようになります。
web: bin/start-pgbouncer-stunnel target/universal/stage/bin/scala-getting-started
結果を Git にコミットし、ステージングアプリでテストした後、本番環境にデプロイします。
デプロイ時、出力には次の内容が表示されます。
=====> Detected Framework: pgbouncer-stunnel
Scala、Java、JDBC、Apache DBCP を使用した接続プールについての詳細は、Apache Commons の Web サイトを参照してください。
この記事で使用されている例のソースコードは、GitHub で見つけることができます。