RailsとAmazon Aurora利用時のフェイルオーバー問題を解決
tl;dr
- RailsのコネクションプールとAmazon Auroraのフェイルオーバーの仕組みは相性が悪く、フェイルオーバー時に致命的な問題が発生する
- 解決方法の1つは、コネクションプールを使わないこと
- ただし、都度接続だと接続コストがかかる
- New Relicなどを使ってる場合は、自分の実装以外で使ってるコネクションまで都度接続になってしまう
- 別スレッドでDB操作を行っている場合、処理中であってもそのスレッドのコネクションまで切断されてしまう(Railsのコネクション破棄がプロセス単位のため)
- コネクションプールを活かしたままこの問題を解決できたので、その方法をご紹介します。ちなみにRails 4.2の話。
RailsとAmazon Aurora利用時のフェイルオーバー問題とは
詳しくはこちら
問題が発生する状況をまとめると以下の通りです。
Amazon Auroraのフェイルオーバーの動作
- ReaderはWriterに昇格し、WriterはReaderに降格する
- Writer/Readerの昇格/降格が発生しても、インスタンスのIPアドレスは変わらない
- WriterにつながるCluster Endpointと、Reader用のReader EndpointのDNSレコードのCNAMEが変わるだけ
Railsのコネクションプールの動作
- フェイルオーバーすると、接続していたコネクション利用時に以下のエラーが発生
ActiveRecord::StatementInvalid (Mysql2::Error: MySQL server has gone away: xxx):
ActiveRecord::StatementInvalid (Mysql2::Error: Lost connection to MySQL server during query xxx):
- 次にコネクションを利用しようとすると以下のエラーが発生
Mysql2::Error (Can't connect to MySQL server on 'aurora-test.cluster-xxxx.us-west-2.rds.amazonaws.com' (61)):
- このエラーが発生したタイミングで、接続先(CNAMEかIPアドレスかは未確認)を内部でキャッシュしてしまう
- Writerにつないでたコネクションは、フェイルオーバー後はWriterのつもりでReaderに降格したインスタンスにつながってしまう
- 更新処理を行うたびに、Readerに対する更新処理となり、以下のエラーが発生
ActiveRecord::StatementInvalid (Mysql2::Error: The MySQL server is running with the --read-only option so it cannot execute this statement:
解決方法
解決方法その1. コネクションプールを使わない
上記のQiitaの記事にある方法がこれ。具体的には以下のgemを導入して都度接続にする方法。
これなら確かにReaderにつながることはなかったです。
解決方法その2. 接続できなくなった場合だけコネクションプールを破棄
今回考えたやり方がこちら。都度接続を避けたかった理由は冒頭でも書いたとおり、
- 接続コストが気になる
- New Relicを使ってるのでそっちまで都度接続されてしまう
- 別スレッドでDB操作を行っている処理があり、突然コネクションが切断されてしまって使い物にならなかった
を回避したかったたからです。
導入手順
Railsの標準の ConnectionManagement
を差し替えるだけです。
lib/active_record/connection_adapters/reconnect_on_error_management.rb
ファイルを作成し、application.rb
で ConnectionManagement
を差し替えます(Amazon Auroraのための限定的な対応になるので、gemとか作ってないです。すみません(;´Д`))
これだけ。
対応内容の説明
Railsのデフォルトの ConnectionManagement
は、call
で例外が発生すると clear_active_connections!
を呼び出してコネクションをコネクションプールに戻します。
これだと接続先情報がそのまま残ってしまうため、何度試行してもReaderに接続にいってしまいます。
上記で紹介する ReconnectOnErrorManagement
では、エラーが発生した場合のみ、clear_all_connections!
を呼び出してコネクションをプールに戻さずに破棄しています。
この方法であれば、普段はコネクションプールを使い続けることができます。
ただし、ここでつかまえられる例外はrubyのsyntaxエラーやSQLのエラーなどなんでも飛んできますので、Amazon Auroraの接続エラーに限定してコネクションを破棄するため、エラーメッセージから対象を限定しています。
ちなみに都度接続を行う activerecord-refresh_connection
は、例外だけじゃなく通常の処理が終了した場合も clear_all_connections!
を呼び出して常にコネクションを破棄していました。
注意点
WriterとReaderの接続先情報が完全に置き換わるまでに少し時間がかかるようで(Amazon AuroraのCNAMEの切り替え?)、数秒〜20秒ぐらいはReaderにつながってしまう時間があります。 これは都度接続だったとしても同じ状況かと。 その間は更新処理はエラーになるのでご注意ください。
あとMySQLライブラリのエラーメッセージから接続回りのエラーを判断してますので、エラーメッセージが変わったりするとコネクションを破棄してくれなくなったりするかも。 mysql2 | RubyGems.org | your community gem host の0.3.21と0.4.3では問題なかったです。
利用実績
まだ本番では利用していませんが、数百回試した限りでは意図した通りの動作をしています。近々導入してしばらく様子を見るつもり。
やってることはシンプルなので大丈夫だと思いますが、何かあっても自己責任の上でご利用頂ければ m( )m
ご意見ご指摘ありましたらぜひお願いします。
2019/03/04 更新
いまは普通に運用で利用してますので、問題ないかと思います。
Amazon Web Services実践入門 (WEB+DB PRESS plus)
- 作者: 舘岡守,今井智明,永淵恭子,間瀬哲也,三浦悟,柳瀬任章
- 出版社/メーカー: 技術評論社
- 発売日: 2015/11/10
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
- 出版社/メーカー: トーヨー
- 発売日: 2002/09/10
- メディア: オフィス用品
- クリック: 1回
- この商品を含むブログを見る
水曜どうでしょう 第12弾 香港大観光旅行/門別沖釣りバカ対決/北極圏突入 〜アラスカ半島620マイル〜 [DVD]
- 出版社/メーカー: HTB 北海道テレビ
- 発売日: 2009/10/28
- メディア: DVD
- この商品を含むブログを見る