activerecord-mysql-reconnect 0.2.0

activerecord-mysql-reconnect 0.2.0をリリースしました。

これは何?

ActiveRecordMySQLにアクセスできなくなったときに、間隔を空けながらリトライするためのライブラリです。 MHAでのフェイルオーバー時にRails側でエラーを出さないために作りました。

使い方は以下の通り。

require 'activerecord/mysql/reconnect'

ActiveRecord::Base.establish_connection(
  adapter: 'mysql2', host: '127.0.0.1', username: 'root', database: 'employees')
ActiveRecord::Base.execution_tries = 3

class Employee < ActiveRecord::Base; end

p Employee.count
system('sudo /etc/init.d/mysqld restart')
p Employee.count

上記の例だと、二回目のcountを実行する前にmysqlを再起動していますが、規定の回数を超えない限り何回も再試行してエラーにならないようにします。

また、おまけ的な機能ですが retryable_transaction というメソッドを使うと、こけたときにブロックの先頭からリトライしてくれます。*1

ActiveRecord::Base.retryable_transaction do
  Employee.create(emp_no: 1, birth_date: Time.now, first_name: 'Scott', last_name: 'Tiger', hire_date: Time.now)
  Employee.create(emp_no: 2, birth_date: Time.now, first_name: 'Scott', last_name: 'Tiger', hire_date: Time.now)
  Employee.create(emp_no: 3, birth_date: Time.now, first_name: 'Scott', last_name: 'Tiger', hire_date: Time.now)
end

それreconnect: trueで(ry

はい、その通り。MySQLのCのAPIには自動再接続のためのオプションがあります。ActiveRecordもmysql2もそれをサポートしてます。

個人的に勘違いしてまして、てっきりサーバの元のプロセスが生きていないと再接続できないものだと思っていたんですが、コードを読んだりテストしてみたりした限りでは、サーバが再起動されてもちゃんと再接続してくれますね。

じゃあそれでOKかというと、実際にMHAで masterha_master_switch を実行するとエラーが出ます。 1番多かったエラーは「Access denied for user 'xxx'@'xxx'」でした。

これはフェイルオーバー用のスクリプトの実装にもよると思うのですが、僕の使ったスクリプトだとstopのタイミングでユーザーをドロップしてるんですよね。 (当たり前ですが)「Access denied」の場合はreconnectは無意味のようでした。

ちなみに上記リンク先のスクリプトはperlのサンプルスクリプトをだいたいそのままRubyにポートしたものです。

また、深追いはしていないのですが「Lost connection to MySQL server during query」もすこし出ていました。試行回数が一回だけだから状況によってはエラーになるのかな…などと考えています。

EC2環境で複数のサーバを立ち上げてactiverecord-mysql-reconnectのテストをしましたが、試行回数が適切に設定されていればエラーになることはなかったです。

その他

いくつかセッションが切れちゃって大丈夫かなというメソッドがあったのですが、ざっと見た感じ大丈夫そうでした。*2

last_id

my_ulonglong STDCALL mysql_insert_id(MYSQL *mysql)
{
  return mysql->insert_id;
}

affected_rows

my_ulonglong STDCALL mysql_affected_rows(MYSQL *mysql)
{
  return mysql->affected_rows;
}

quote_string / escape

それはさておき

RailsでMHA使っている例はいくつかあると思うんですが、フェイルオーバー時のアプリ側のエラーはみなさんどうしているのかな…

*1:トランザクションを使う場合は上位のレイヤでのリカバリが必要な気もしますが…

*2:INSERT/UPDATEと構造体から値を取ってくるタイミングが同じであれば、です

*3:クラスメソッドだとrb_mysql_client_escapeですね。何でだろう?