接続プール¶
接続が確立されると、通常はTCP接続またはソケットが開かれます。ソケットは一度に1つのステートメントを処理します。プログラムが多くのクエリを同時に実行する必要がある場合、またはデータベースを使用しようとする同時リクエストを処理する場合、アクティブな接続が複数必要になります。
データベースはそれを使用するアプリケーションとは別のサービスであるため、接続が切断されたり、サービスが再起動されたり、プログラムが気にしないようなことが発生する可能性があります。
これらの問題に対処するために、通常は接続プールが適切なソリューションです。
crystal-db
でデータベースを開くと、接続プールが既に動作しています。DB.open
は単一の接続だけでなく、接続プール全体を管理するDB::Database
オブジェクトを返します。
DB.open("mysql://root@localhost/test") do |db|
# db is a DB::Database
end
db.query
、db.exec
、db.scalar
などを使用してステートメントを実行する場合、アルゴリズムは次のようになります。
- プール内で使用可能な接続を見つける。
- 必要に応じて、可能な限り作成する。
- プールが新しい接続の作成を許可されていない場合、接続が使用可能になるまで待つ。
- ただし、時間がかかりすぎる場合は、この待機を中止する必要があります。
- プールからその接続をチェックアウトする。
- SQLコマンドを実行する。
DB::ResultSet
が生成されない場合は、接続をプールに戻します。それ以外の場合は、ResultSetが閉じられたときに接続がプールに戻されます。- ステートメントの結果を返す。
接続を作成できない場合、またはステートメントの実行中に接続が失われた場合、上記の処理が繰り返されます。
再試行ロジックは、ステートメントが
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から現在時刻を出力しますが、接続が失われたり、サーバー全体が数秒間ダウンした場合でも、例外を発生させることなくプログラムは実行されます。
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