MAGAZINE
ルーターマガジン
ActiveRecord にてデータベースへ再接続するには?

はじめに
皆さんこんにちは。エンジニアの Hodoshima です。
今回は、システムの運用について、データベースとの接続が切れてしまった場合の処置について考える必要があったため、そのお話をします。
データベースの再接続について
データベースを利用した、システムを運用していくと、人的ミスや自然災害によるデータベースサーバーの停止などによって、一度は繋がっていたシステムとデータベースの間の接続が切れてしまう場合があります。
その際に、コード側で再接続の設定をしないと、接続が切れてしまったら最後、データベースと停止させないと再接続でいないようなシステムになってしまう可能性があります。
この記事では、そのようになりうる具体例といくつの再接続の方法を紹介しています。
環境について
この記事では以下の環境で検証を行いました。
- Ruby: version 3.1.3
- ActiveRecord: version 7.2.2.1 (bundler による管理)
- MariaDB: 10.6.20 (brew による管理)
なお、今回は ActiveRecord 単体でのデータベースとの再接続の話とします。
接続が切れる再現
実験用のコードとして以下のコードを書きました (接続先データベース名を rootering
にしています)。
require 'active_record'
ActiveRecord::Base.establish_connection(
adapter: 'mysql2',
database: 'rootering',
host: 'localhost',
username: 'root',
password: '',
)
Time.zone_default = Time.find_zone! 'Tokyo'
ActiveRecord.default_timezone = :local
class Item < ActiveRecord::Base
def to_s
"#{name} #{price}"
end
end
puts Item.first.to_s
puts 'データベースを再起動してください。再起動後にエンターキーを押してください。'
_ = gets
puts Item.first.to_s
この実験用コードでは最初に items テーブルから 1 つのレコードを取得し、文字列を生成します。 その後、データベースを再起動した後に同じことができるかどうかの検証です。
指示に従ってデータベースを再起動させると、2 回目のレコード取得で Server has gone away (Mysql2::Error::ConnectionError)
となり、データベースに接続できないエラーとなります。
% bundle exec ruby program.rb
鉛筆 30
データベースを再起動してください。再起動後にエンターキーを押してください。
...
/Users/xxx/.rbenv/versions/3.1.3/lib/ruby/gems/3.1.0/gems/mysql2-0.5.6/lib/mysql2/client.rb:151:in `_query': Server has gone away (Mysql2::Error::ConnectionError)
...
ここから、データベースの再接続を行うための方法を紹介していきます。
方法 (1) 接続設定から再接続を行う
データベースとの接続設定に reconnect: true
を追加することで、データベースとの接続が切れた際に再接続をしてくれます。
ActiveRecord::Base.establish_connection(
adapter: 'mysql2',
database: 'rootering',
host: 'localhost',
username: 'root',
password: '',
reconnect: true
)
% bundle exec ruby program.rb
鉛筆 30
データベースを再起動してください。再起動後にエンターキーを押してください。
鉛筆 30
一見、この解決方法でも良さそうに見えますが、実はこの方法は非推奨とされていることが多いです。
その理由としては、以下で挙げられているように、気づかずに再接続してしまうため、想定外の挙動を生み出しやすいことが主となっています。
- 接続セッションに依存していた場合、接続によってリセットされた変数等に気づかずに想定外の SQL を実行してしまう。
- トランザクションの途中だった場合、データベース側はロールバックされるが、コード側ではロールバックされないため、中途半端な操作が残ってしまう。
方法 (2) reconnect! によって明示的に再接続をする。
ActiveRecord::Base.connection.reconnect!
によって、データベースと再接続をすることができます。
puts Item.first.to_s
puts 'データベースを再起動してください。再起動後にエンターキーを押してください。'
_ = gets
ActiveRecord::Base.connection.reconnect!
puts Item.first.to_s
実際に、ActiveRecord::Base.connection.reconnect!
を追加することにより、明示的にデータベースとの再接続を行うができます。
% bundle exec ruby program.rb
鉛筆 30
データベースを再起動してください。再起動後にエンターキーを押してください。
鉛筆 30
最後に
以上のことから、接続時に reconnect: true
オプションをつける場合と ActiveRecord::Base.connection.reconnect!
で明示的に再接続をする 2 つの方法についてのうちどちらを使うかの判断基準として、以下のように考えられます。
- 接続時に
reconnect: true
オプションをつける (但し、非推奨とされていることがある)- サービスをダウンすることが特に許されない場合
- ActiveRecord からのデータベースへの操作が全て軽く、操作の途中で接続が切れてしまう可能性が低い場合
ActiveRecord::Base.connection.reconnect!
で明示的に再接続を行う- 接続に依存しているロジックを使っている場合
- トランザクションを使っており、データの整合性を保証する必要がある場合
皆さんも「アプリとデータベースの接続が切れてしまう」という例外に対する処置を考えて、より快適な運用を目指してみてください。
付録
items テーブル定義
CREATE TABLE `items` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`price` int(11) NOT NULL,
`created_at` datetime DEFAULT current_timestamp(),
`updated_at` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
items テーブルレコード作成 SQL
INSERT INTO items (name,price) VALUES ('鉛筆',30), ('消しゴム',100), ('ボールペン',200);
CONTACT
お問い合わせ・ご依頼はこちらから