active_record_mysql_xverifyというgemファイルを書いた。
これは何?
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でやっていることは
というのが大きな流れのようだった。
エラーハンドリングの判断にエラーの種別を見たり、別スレッドでコネクションの状態をチェックしていたり*3、といろいろやっているけれど、基本的にはactiverecord-mysql-reconnectと変わらないような気がする。
トランザクションが切れた状態でのSQLの再実行って、トラブルありそうだけどあんまり問題になってないのかな?
ActiveRecord::QueryCache
コネクションプールからのcheckoutとcheckinのタイミングが重要なので、ActiveRecordのコードをちまちま読んでいたんだけれど、Rails5でQueryCacheはRackのミドルウェアじゃなくなってた*4。
checkout
は最初にコネクションにアクセスしたタイミングでされるけど*5、checkin
はQueryCacheに依存している気がする*6。
ミドルウェアじゃなくなったのも、その辺の「コネクションプールの管理に必須だから」みたいな判断もあるのかな?あるいは単にAPIの変更の影響だろうか?
QueryCacheのレイヤが完全に削除されるとcheckin
が機能しなくなりそうなんだけど、そのへんどうなんだろう?よくわからない。
*1:https://github.com/rails/rails/blob/v5.2.1/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L520
*2:https://github.com/rails/rails/blob/v5.2.1/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L434
*3:ドライバがスレッドを立ち上げるって、なんかすごいと思った
*4:https://github.com/rails/rails/blob/v5.2.1/activerecord/lib/active_record/query_cache.rb
*5:https://github.com/rails/rails/blob/v5.2.1/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L380
*6:https://github.com/rails/rails/blob/v5.2.1/activerecord/lib/active_record/query_cache.rb#L33