active_record_mysql_xverify

active_record_mysql_xverifyというgemファイルを書いた。

github.com

これは何?

RailsでAuroraを使うときに、フェイルオーバー時に旧マスタにつなぎに行く問題をなんとかするやつ。 世間様を見渡すに「コネクションプール使うな。接続を切れ」というのが現状の解決策のようだけど、もうちょっとなんとかしたかったので作成。

使い方

config/environments/production.rbとかに

ActiveRecordMysqlXverify.verify = ->(conn) do
  conn.ping && conn.query('show variables like "innodb_read_only"').first.fetch(1) == 'OFF'
end

または

ActiveRecordMysqlXverify.verify = ActiveRecordMysqlXverify::Verifiers::AURORA_MASTER

と書いておくと、いい感じにしてくれます。

R/W splittingをしして、コネクション毎に有効にするかしないかを分けたい場合はActiveRecordMysqlXverify.handle_ifを使ってください。

ActiveRecordMysqlXverify.handle_if = ->(config) do
  config[:host] == 'my-cluster...'
end

あと、デフォルトではSQLの実行時にエラーが起きた場合、コネクションを検証するようになっているので、すべてのリクエストでコネクションを検証したい場合は

ActiveRecordMysqlXverify.only_on_error = false

としてください。

実装について

Railsはコネクションプールからのチェックアウト時にverify!を実行して*1、検証に失敗すると再接続する*2ようになっているので、verify!を上書きしてinnodb_read_onlyを見るようにしただけです。

すべてのリクエストでshow variablesを実行するのもアレなので、デフォルトではエラー時のみ。

MariaDB Connector/J

これを実装するにあたってMariaDB Connector/Jの実装を読んだり動作検証をしたりしたのだけれど、faster failoverでやっていることは

  1. SQLを実行
  2. エラーをハンドリング
  3. 再接続
  4. SQLを再実行

というのが大きな流れのようだった。

エラーハンドリングの判断にエラーの種別を見たり、別スレッドでコネクションの状態をチェックしていたり*3、といろいろやっているけれど、基本的にはactiverecord-mysql-reconnectと変わらないような気がする。

トランザクションが切れた状態でのSQLの再実行って、トラブルありそうだけどあんまり問題になってないのかな?

ActiveRecord::QueryCache

コネクションプールからのcheckoutとcheckinのタイミングが重要なので、ActiveRecordのコードをちまちま読んでいたんだけれど、Rails5でQueryCacheはRackのミドルウェアじゃなくなってた*4

checkoutは最初にコネクションにアクセスしたタイミングでされるけど*5checkinはQueryCacheに依存している気がする*6

ミドルウェアじゃなくなったのも、その辺の「コネクションプールの管理に必須だから」みたいな判断もあるのかな?あるいは単にAPIの変更の影響だろうか?

QueryCacheのレイヤが完全に削除されるとcheckinが機能しなくなりそうなんだけど、そのへんどうなんだろう?よくわからない。