なからなLife

geekに憧れと敬意を抱きながら、SE、ITコンサル、商品企画、事業企画、管理会計、総務・情シス、再び受託でDB屋さんと流浪する人のブログです。

RDSのフェイルオーバーとJDBCコネクションプーリングでハマった件

フェイルオーバー発生させたら、15分固まった。

Amazon RDS MySQL 5.6.23+Tomcat+JavaServlet+Connector/Jでコネクションプールを利用した環境で、RDSのフェイルオーバー試験をするべくManagement Consoleから「Reboot with Failover」を発生させたら、フェイルオーバーは数分で完了して他の新規接続も受け付けられているのに、前から接続していた分が固まってしまいました。


フェイルオーバーが始まった直後に投げたリクエストのあと、うんともすんとも言わない。


JDBCのconnectTimeoutは3秒にしてあって、SecurityGroupの設定ミスなどでつながらない場合は5秒でタイムアウトすることは確認済みなのに、このケースではお返事が帰ってこない。。。


ぐぐっても、新規接続時のタイムアウト(connectTimeout)や、ステートメントレベルのタイムアウトとか、プールしておくコネクション数の増減コントロールの話は見つかるのだけど、今回の事象に関する話はなかなか見つからず。
また、同じ事象が出た、って書いてあるけど、結論や解決方法まで言及されてない
新宿鮫:第4回AWSもくもく勉強会にいってきました。
とか
Amazon RDSのフェールオーバーを試してみたが | OpenGroove
とか。




対策としては、「socketTimeout」の設定が必要だった、という話。

新規接続時のタイムアウトはconnectTimeoutで良いのだけど、確立済コネクションに異常が発生した場合は、socketTimeoutの設定が必要、とのこと。


「socketTimeout」のデフォルトは「0」で、無制限、ってことらしいですが、こちらで確認した環境の挙動では、他の何かの設定にひきずられているのか、15分でタイムアウトしていました。

・connectTimeout

Timeout for socket connect (in milliseconds), with 0 being no timeout. Only works on JDK-1.4 or newer. Defaults to '0'.

Default: 0

Since version: 3.0.1


・socketTimeout

Timeout on network socket operations (0, the default means no timeout).

Default: 0

Since version: 3.0.1

MySQL :: MySQL Connector/J 5.1 Developer Guide :: 5.1 Driver/Datasource Class Names, URL Syntax and Configuration Properties for Connector/J


わかりにくいよ。。。


そもそも、なんでこれに依存するような異常が起こるの?

日本語では、この記事が、一番しっかり書いてあるようです。

・現象発生のメカニズム
1)通信タイムアウトにより処理が停止
Javaでの通信処理では、java.net.Socketを使用しますが、このAPIではデフォルトで通信タイムアウトを行わない仕様となっています。

この場合の「通信タイムアウト」とは、Socketに関連付けられたInputStreamのread()呼び出しがブロックされる時間です。

http://java.sun.com/javase/ja/6/docs/ja/api/java/net/socket.html;

データベースのフェイルオーバ時には、データベースサーバが終了するため、一旦、アプリケーションサーバからのデータベース接続が切断されます。
その際、データベースサーバから、TCPのFINパケットが送信されるため、そのパケットを受けることで、アプリケーションサーバでのread呼び出しでIOExceptionが発生し、切断を検知することができます。

しかし、FINパケットを送信しない、あるいは、通信系路上でFINパケットが消えてしまう場合があり、その場合には、read呼び出しがタイムアウトするまで待ち受けることになります。
そこでタイムアウトが未指定であると、無限に待ち続けることになります。
Javaトラブルシューティング メールマガジン バックナンバー一覧


英語だと、この辺でしょうか。
Git Hubで公開されているMySql backend for the fxa-auth-db-serverというツールに関してのIssueの中で、「フェイルオーバーさせると再接続されない問題が出てるんだけど?」という話題があがっており、RDSでこの事象が起こっていることについて言及があります。
reboot with failover」の時、FINパケットが見つからなかった、って言ってます。

If I simply reboot the RDS instance with no failover, then after a short interruption, normal operation comes back with no intervention. With traces of DNS and port 3306 traffic in place, I can see that FIN packets arrive from the RDS instance, followed by a period of further pushes getting a RST packet response. Then a series of attempts to SYN with the RDS instance at the same IP as before (which are RST), and finally the SYN handshake is accepted and normal connections are re-established. For DNS, at some point around the disconnect, lookups to resolve dbwrite are made, returning the same IP as before.

That's all normal.

If I do a reboot with failover, I do not see FIN packets. No DNS requests are made for dbwrite. In fact, I see nothing odd, except for the fact that I should be seeing something happen. Packets continue to be pushed and ack-ed, as if the original master db has not shut down.
Pool does not reconnect on db failover · Issue #108 · mozilla/fxa-auth-db-mysql · GitHub

公式ドキュメントからは、見つけ出すことができませんでした。


よくわかってないこと

開発中のプログラムでこれらの現象が起きて、そこで確認もしているので、もっとちゃんと検証用の環境とソースを用意しなきゃいけないです。
いつ確認できるかわからないけど。

「socketTimeoutのデフォルトは無制限」なのに、毎回15分で処理が帰ってきた。

OS(AmazonLinux)か、作りこんだアプリのレベルでは、意図的には設定していないので、何かこれに影響するデフォルト設定があったのかな。

「socketTimeout」を設定後に再現させたら、「socketTimeout」+「connectionTimeout」の時間で処理が帰ってきた。

「確立済コネクションに異常があったら、新規コネクションを生成しようとする」という処理が、アプリレベルで組み込まれているのか、Tomcatのコネクションプーリングの挙動としてそうなっているのか。

RDSのFailoverは、本当にFINを出さずにいきなりフェイルオーバー処理を始めるの?

パケット調べるの辛い。


まあ、RDSのフェイルオーバーがFINを返してから処理するように挙動が変わったとしても、FINなしで落ちるケースを考慮してsocketTimeoutを設定することは必要なんだろうけどね。

追記

AWSの中の人に聞いてみた件。

というわけで、何分で検知するかはクライアント側の設定に依存するものとして、RDS・Auroraに対してコネクションプールを使うとフェイルオーバーを検知できないケースがあるは「仕様」です。