コンテンツへスキップ

接続プール

接続が確立されると、通常はTCP接続またはソケットが開かれます。ソケットは一度に1つのステートメントを処理します。プログラムが多くのクエリを同時に実行する必要がある場合、またはデータベースを使用しようとする同時リクエストを処理する場合、アクティブな接続が複数必要になります。

データベースはそれを使用するアプリケーションとは別のサービスであるため、接続が切断されたり、サービスが再起動されたり、プログラムが気にしないようなことが発生する可能性があります。

これらの問題に対処するために、通常は接続プールが適切なソリューションです。

crystal-dbでデータベースを開くと、接続プールが既に動作しています。DB.openは単一の接続だけでなく、接続プール全体を管理するDB::Databaseオブジェクトを返します。

DB.open("mysql://root@localhost/test") do |db|
  # db is a DB::Database
end

db.querydb.execdb.scalarなどを使用してステートメントを実行する場合、アルゴリズムは次のようになります。

  1. プール内で使用可能な接続を見つける。
    1. 必要に応じて、可能な限り作成する。
    2. プールが新しい接続の作成を許可されていない場合、接続が使用可能になるまで待つ。
      1. ただし、時間がかかりすぎる場合は、この待機を中止する必要があります。
  2. プールからその接続をチェックアウトする。
  3. SQLコマンドを実行する。
  4. DB::ResultSetが生成されない場合は、接続をプールに戻します。それ以外の場合は、ResultSetが閉じられたときに接続がプールに戻されます。
  5. ステートメントの結果を返す。

接続を作成できない場合、またはステートメントの実行中に接続が失われた場合、上記の処理が繰り返されます。

再試行ロジックは、ステートメントがDB::Databaseを通じて送信された場合にのみ発生します。DB::ConnectionまたはDB::Transactionを通じて送信された場合は、特定の接続オブジェクトの使用が期待されているため、再試行は実行されません。

設定

プールの動作は、接続URIのクエリ文字列として表示される可能性のあるパラメータセットから設定できます。

名前 デフォルト値
initial_pool_size 1
max_pool_size 0(無制限)
max_idle_pool_size 1
checkout_timeout 5.0(秒)
retry_attempts 1
retry_delay 1.0(秒)

DB::Databaseが開かれると、initial_pool_size個の接続が最初に作成されます。プールはmax_pool_sizeを超える接続を保持しません。プールに接続を返したり解放したりすると、既にmax_idle_pool_size個のアイドル接続がある場合は閉じられます。

max_pool_sizeに達していて接続が必要な場合、既存の接続が使用可能になるまで最大checkout_timeout秒間待ちます。

接続が失われた場合、または確立できない場合は、最大retry_attempts回、それぞれretry_delay秒間待機して再試行します。

サンプル

次のプログラムはMySQLから現在時刻を出力しますが、接続が失われたり、サーバー全体が数秒間ダウンした場合でも、例外を発生させることなくプログラムは実行されます。

sample.cr
require "mysql"

DB.open "mysql://root@localhost?retry_attempts=8&retry_delay=3" do |db|
  loop do
    pp db.scalar("SELECT NOW()")
    sleep 0.5
  end
end
$ crystal sample.cr
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:57
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:57
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:58
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:58
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:59
db.scalar("SELECT NOW()") # => 2016-12-16 16:36:59
# stop mysql server for some seconds
db.scalar("SELECT NOW()") # => 2016-12-16 16:37:06
db.scalar("SELECT NOW()") # => 2016-12-16 16:37:06
db.scalar("SELECT NOW()") # => 2016-12-16 16:37:07