ActiveRecordのconnectionを読んだ

メモ

 

ActiveRecordのconnectionは、ConnectionHandling moduleのconnectionメソッドを呼んでいる。

このメソッドの中で、ConnectionHanlderのretrieve_connectionが呼び出され、最終的には、ConnectionHandlerのインスタンス変数(Hash)から、引数で渡された識別子をもとにConnectionPoolのインスタンスを取り出し、このConnectionPool#connectionを実行する。

ConnectionPoolは{スレッド=>接続}のHashで接続をキャッシュしている。(thread_cached_conns)

カレントスレッドに接続がなければ、ConnectionPoolから接続を取り出す(check out)。
checkout(正確にはacquire_connection)には3パターン。

1. ConnectionPoolが持っているConnectionのキュー(スレッドセーフ)から使用可能なものを取り出す。

2. ConnectionPoolのキャパシティ(config/database.ymlのpoolの値)を超えていなければ、新しい接続を作成する。

3. Connectionのキューに使用可能な接続が来るまで待つ。

 

2.の新しい接続のパターンについて読む。

ConnectionPoolが持つ接続の数と今から接続しようとしている数がConnectionPoolのキャパシティを超えていないかを確認する。(今から接続しようとしている数はインスタンス変数で、最初は0。新しい接続を作成し終わるまで+1される。これにより、同時に複数の接続が作成される際も、ConnectionPoolの接続の最大数を超えないようにしている。)

ここで超えなければ、new_connectionメソッドが呼ばれる。

この中で、ConnectionPoolが持つDBのconfigをもとに、対象のadapterのメソッドが呼ばれる。

 

ここでは、mysqlを想定する。

mysqlの場合、mysql2_connectionメソッドが呼ばれる。

ここで、ConnectionAdapters::Mysql2Adapterのインスタンスが作成される。
ここまで「接続」と呼んできたのは、このConnectionAdapters::Mysql2Adapterのインスタンス

Mysql2Adapterのインスタンス変数のpoolをself(ConnectionPoolのインスタンス)にセットして、ConnectionPoolが持つ接続の配列に、Mysql2Adapterのインスタンスを追加する。

 

Mysql2Adapter#leaseを呼び出し、その「接続」をを使用するスレッドを指定する。

もし、この「接続」がカレントスレッドや他のスレッドに呼び出された際は、エラーとなる。

これにより、複数のスレッドが、1つの「接続」を使うことを防いでいる。

 

最終的に、ActiveRecord::Base#connectionは、この接続を返す。
同時に、ConnectionPoolはこの接続をキャッシュする。

ActiveRecordのestablish_connectionを読んだ

ActiveRecordのestablish_connectionを読んだのでメモ

 

やっていることは、複数のConnectionPoolを管理するConnectionHandlerのインスタンス変数(Hash)のvalueにデータベースの接続情報などConnectionPoolの情報を管理するオブジェクトをセットして、ConnectionPoolのインスタンスを取り出せる状態をつくること。
これにより、複数のDBに接続する際に、ConnectionPoolがインスタンス変数を確認して、キーをもとに正しいConnectionPoolを提供できるようになる。

 

読んだときのメモ

ActiveRecord::Base.establish_connectionは

ActiveRecord::ConecctionHandling.establish_connectionを呼ぶ

この中でActiveRecord::ConnectionAdapters::ConnectionHandlerのestablish_connectionを呼ぶ

ConnectionHandlerは複数のConnectionPoolを管理している。

複数DBを使用する際に、DBごとにConnectionPoolを持ち、それらの複数のConnectionPoolをConnectionHandlerが管理する。

ConnectionHandler#establish_connectionの中で、
databaseのconfigをもとにConnectionAdapters::PoolConfigのインスタンスが生成される。

PoolConfigはインスタンス変数として、各connectionを識別するためのconnection_specification_nameを持ち、デフォルトだと'primary'になる。PoolConfigは他にもdb_configというインスタンス変数を持つ。これは、config/database.ymlで設定したHashなどが入る。
ConnectionPoolをまとめるConnectionHandlerのインスタンスは、owner_to_pool_managerをConcurrent::Mapで持つ。keyがPoolConfigのインスタンスのconnection_specification_name、valueがPoolManagerのインスタンス

establish_connectionの際に、valueが存在しなければ、新たにPoolManagerのインスタンスが作成される。
PoolManagerは、name_to_pool_configというHashを管理するためのClass。

キーには識別するための名前が入り(デフォルトで'default')、valueにPoolConfigが入る。

establish_connectionの中で、PoolManagerのname_to_pool_configもセットされる。

最後に、PoolConfigインスタンスのpoolメソッドが呼ばれる。

この中でConnectionPoolのインスタンスが生成される。

ConnectionPoolはインスタンス変数として、PoolConfigのインスタンスや、そのインスタンスのdb_configなどを持つ。

ConnectionPoolのinitializerの中で、ConnectionAdapters::ConnectionPool::Reaperのインスタンスが作成され、runメソッドが呼ばれる。
Reaperはpoolsとthreadsをインスタンス変数として持つ。poolsにはConnectionPoolのインスタンスが入る。threadsには、スレッドが入り、設定したfrequency秒間に一度生きているConnectionPoolのインスタンスに対してreapメソッドとflushメソッドを実行する。

ConnectionPoolのインスタンスがなくなれば、そのスレッドを終了させる。

ここでinitializeされたPoolConnectionのインスタンスは、PoolConfigのpoolで参照できる。