前回に続いてまたAuroraです、Aurora。高い可用性なのはわかっていますが、じゃあ具体的にどのくらいやねん、となると良い情報がなかったので調べることにしました。
Auroraの公式情報は、一定以上の知識を前提とした内容になっていて、良いバランスで心地よい感じなのですが、まだ読んでて楽しいコンテンツが欠けている印象です。一般情報も、出ました、使ってみました、とかしか無くて寂しいので、ブルーオーシャンを泳いでいくとしましょう。
AuroraのEndpoint
公式読めばいい所なので軽く復習ですが、現在のAuroraのEndpointは3種類あります。これらをどう扱うかは、そのシステムの仕組みによって変わります。Cluster Endpointだけで運用することもあれば、参照分散のためにReader Endpointを併用することもあります。Instance Endpointは、DNS TTL遅延問題を回避したい時にCluster Endpointを捨てて、クライアントにInstance Endpoint群を設定して自力フェイルオーバーする、といったように使うことがあります。
今回は、このEndpoint達が、フェイルオーバーなどが発生した時にどのような状態になっているのか、もっと言えばどのくらいのダウンタイムを想定しておけばいいのか、を理解していくための検証となっています。
検証方法について
クライアントのDNSキャッシュ
その前に、わりと重要なポイントである、検証環境であるクライアントのDNSキャッシュについて説明しておきます。クライアントは NetworkManager から起動された dnsmasq を使用しており、(NetworkManagerが) /etc/resolv.conf で nameserver 127.0.0.1 を指定し、取得し直しの際には外部DNSサーバーにリクエストを送ります。で、肝心のキャッシュについては、TTLの秒数以内なら内部キャッシュを利用し、過ぎたら取得し直す、という正しい挙動を先に確認した上でAuroraを検証しています。TTLが1秒だとしても、0.9秒ならキャッシュを利用します。
インスタンス
t2.medium で Multi-AZ として 2台作成し、そのあとに 2台の Readレプリカを作成して、Writerと3台のReadレプリカで構成しました。検証クエリ
だいぶデータは適当ですが、Writerでユーザーとテーブルを作成して、
1 2 3 |
grant all on gedow.* to 'gedow'@'10.%' IDENTIFIED BY 'pass'; create table test (`num` int); insert into test values (1), (2), (3), (4), (5), (6); |
6つのシェルで host だけ変更して、参照&更新クエリをループしました。良い距離ではなかったので、6 qps 程度ですが、ダウンの開始と終わりを知るには十分です。ホストは Cluster Endpoint, Reader Endpoint, そして全部の Instance Endpoint です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
host=gedow-01.cluster-asdfghjkl.ap-northeast-1.rds.amazonaws.com host=gedow-01.cluster-ro-asdfghjkl.ap-northeast-1.rds.amazonaws.com host=gedow-01-01.asdfghjkl.ap-northeast-1.rds.amazonaws.com host=gedow-01-01-ap-northeast-1c.asdfghjkl.ap-northeast-1.rds.amazonaws.com host=gedow-01-11.asdfghjkl.ap-northeast-1.rds.amazonaws.com host=gedow-01-12.asdfghjkl.ap-northeast-1.rds.amazonaws.com user=gedow pass=pass db=gedow log_dir=/tmp/gedowlog/ while : do date=`date "+%Y%m%d_%H%M"` log=${log_dir}${date}_${host}.log num=$(( $RANDOM % 6 + 1 )) query="SHOW GLOBAL VARIABLES like 'aurora_server_id';" query+="SELECT CURTIME(4);" query+="UPDATE test set num = num WHERE num = ${num}" echo "${query}" | mysql -N -h${host} -u${user} -p${pass} ${db} >> ${log} 2>&1 done |
こんなログが残ります。variables は hostname だと ip-11-22-33-44 って形式になるので、aurora_server_id で表示名を出して、どこに接続してるか明確にしています。時間は適当に少数4桁まで。更新は成功する場合はエラーがない感じ。
1 2 3 |
aurora_server_id gedow-01-01-ap-northeast-1c 03:14:04.0107 ERROR 1290 (HY000) at line 1: The MySQL server is running with the --read-only option so it cannot execute this statement |
Endpointとして、またはインスタンスが接続不可になった時はこんなログ。
1 2 3 4 |
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet', system error: 0 ERROR 2003 (HY000): Can't connect to MySQL server on 'gedow-01.cluster-asdfghjkl.ap-northeast-1.rds.amazonaws.com' (111) ERROR 2003 (HY000): Can't connect to MySQL server on 'gedow-01.cluster-asdfghjkl.ap-northeast-1.rds.amazonaws.com' (111) ... |
フェイルオーバー時のダウンタイム
まずは管理画面についている手動フェイルオーバーを実行した際の、各Endpointのダウンタイムについて計測しました。5~6回の実行結果から大雑把にまとめたものであることをご了承くださいませ。接続先 | ダウンタイム |
---|---|
Cluster Endpoint | 5.5s – 13.3s |
Reader Endpoint | 0.0s – 5.9s |
旧Writer=>Reader降格 | 7.1s – 13.3s |
Reader=>新Writer昇格 | 5.0s – 13.6s |
Reader継続 | 2.3s – 3.7s |
Cluster Endpoint考察
当然ですが、Multi-AZ構成であることを前提としての内容になります。基本的にはフェイルオーバー発生時に 5~13秒 程度の接続不可能の期間が発生し、その後に新Writerに対して正常に参照更新される、という流れになります。実際の流れをまとめるとこんな感じ。
これは、かなり上手くいった時のパターンです。理由は、Cluster EndpointのIPアドレス変更と、クライアントのTTL切れが、フェイルオーバー処理とほぼ同時に完了しているからです。
実際には、Cluster Endpointのアドレス切り替えがあまりスムーズではないようで、多くは以下のようにReadOnlyエラーが発生します。
酷い場合は、フェイルオーバー処理が完了したと思われる、新Writerの復旧後にも、IPアドレスがフラつくことがあります。
少々不満な挙動をすることもある、という結果ではありますし、実際に障害を正確迅速に検知してくれるかは黒箱ですが、全体を見れば公式掲示の1分未満よりも早く、それこそ30秒以内に完了しそうであると推測できます。
Reader Endpoint考察
Reader Endpointに参照クエリを送り続けていると、フェイルオーバー発生時にはクエリが全くエラーにならない時と、~6秒程度の接続エラーが返るときがあります。この理由は少々複雑です。まず、フェイルオーバー発生時はWriterに昇格しない、Reader継続のインスタンスも数秒間、接続が不可能になります。ただし、複数台が同時に接続不可にならないよう、順番にそれが行われるようになっているみたいです(断定はしない:-)
そしてReader Endpointが返すアドレスは、基本は数秒間隔毎にランダムで返すのですが、切り替わるタイミングでは、このフェイルオーバー処理時には接続不可中のReaderを返さないようにしていると見受けられます(断定は:-)。
そこに、クライアントのDNS TTL事情が加わってきます。とはいえ、Reader EndpointのTTLは1秒なので、影響割合としてはそう大きいわけではありません。
順番に接続不可になる一方で、DNSのレスポンスが適切に有効インスタンスFQDNを返すかというと、そうしようとはしているっぽいけども、DNS結果は一定秒……約10秒弱毎に切り替わるので、間に合わず接続不可インスタンスのFQDNを返し続けたままになった場合にエラーが起こる。そしてクライアントのTTLも少々影響を与えてエラーになる・長くなることもある、といった流れです。
うまく行った時の流れは、
イマイチの場合はこう、
パターンを全て考えてたらキリがない感じです。なので、Cluster Endpoint同様、フェイルオーバー中はエラーが起きる前提で利用することになります。
Instance Endpoint考察
Writerの昇降格に関わるインスタンスは、5~13秒ほど接続不可の期間が発生します。Reader継続である昇降格に無関係のインスタンスも、2~4秒ずつ順に接続不可になります。新Writerの接続不可中に全ての処理が終わるかというと、そういうこともあれば、新Writer復旧後もReader継続インスタンスが順に接続不可になることもあります。
つまりフェイルオーバー開始から完了まで、インスタンス全体としては、Writer関連はガッと落ちるし、Reader継続もパラパラと落ちる、ということになります。Reader Endpointくらいは無敵でいてほしかったところですが、この事実とDNSの都合によって、それは難しいことがわかります。
Readerスケールアップ時のダウンタイム
ついでに、Readerのインスタンスタイプを変更した時の Reader Endpoint も調べてみました。同じく4~5回程度の大雑把な結果ですが、こんな感じです。接続先 | ダウンタイム |
---|---|
Reader Endpoint | 15s – 100s |
スケールアップReader | 200s – 340s |
据え置きReader | 0s |
一応ヘルスチェックしてくれて・・・る感触もありますが、あまり綿密に連携しているとは言い難い結果です。Reader Endpointの分散先は一切手を加えられないので、現時点では残念な結果と言わざるを得ません。
Reader EndpointのFQDN選択
正常な時の Reader Endpoint は、Reader となっているインスタンスのFQDNをランダムで返してくれます。ただし、約10秒毎に切り替わるので、1秒間隔でチェックしてみると、9~10回連続で同じFQDNが返り、次の10回はまた同じか、別のFQDNということになります。ずーっとログを採ってみると、1分間同じFQDNの時もあれば、綺麗に10秒毎に変わる時もあれば、まれに1~3秒で入れ替わる時もあることがわかります。最終的に回数を数えてみると、概ね平均な回数になることもわかりました。
これはクライアント単体で取得したのですが、複数クライアントの場合にも10秒間隔で同じFQDNを返されると、あまり綺麗な負荷分散にならなさそうなので、せめてSourceIPAddressなどで結果を変えていて欲しいところ。まぁNAT経由だと知らん、とかになりそうですが。
クライアントの接続方法
Cluster Endpoint と Reader Endpoint の結果を見てわかるとおり、ダウンタイムは確実に存在するので、クライアントからの接続方法は、持続ではなく都度接続が基本になります。接続を維持してしまうと、数秒ごとに再接続する仕組みにしたり、TTLを見て・・・とか考えたくもない作り込みになってしまいます。あとは当然ですが、エラー処理キッチリねってくらいですか。
どの程度の可用性を目指すか
もともと、Auroraは従来のMySQLサーバーと比べて、故障率が高い部品であるディスクやRAIDカードの問題がなくなっているため、障害発生率はかなり低くなっている、はずです。それを前提に置いた上で考えれば、1回の障害時間が少々長い(といっても数十秒)としても、数年単位で見ればかなり高い可用性を維持できると推測できます。DNSに頼る分、超迅速というわけではないですが、逆に言うとその分がシンプル構成であり、さらにWriter/Readerの厳格な切り替え猶予の時間を設けているとも捉えられます。
より高みを目指すというのであれば、こういうのを使ってみるという選択もあります。
システムによっては、これはこれで需要があるのはわかります。
が、私個人の考え方としては、クライアント側にフェイルオーバーの仕組みを持たせる構成は、あまり好きではありません。アプリケーションとしては設定管理や仕込みが複雑になってしまいますし、品質保証を筆頭にメンテナンスコストが高くなるからです。また、Java以外は存在しなさそうなので、同様の仕組みを他言語で実装となると、コストが急激に上がるだけでなく、結果的に事故率・事故度合いが高くなることも容易に想像できます。
構築・維持コスト、安定度、予想稼働率、シンプルさ、などを総合的に判断した時に、AuroraのDNSを軸にした仕組みをそのまま採用するというのは、非常に賢い選択であると考えます。
ただし、それをより正しく理解して利用することは必須なので、今回は各Endpointやクライアントの挙動の確認を行ってみた、というわけです。
結果としては、概ね満足。でもReader Endpointはもっと精密度を上げてダウンタイムを減らしたり、仕様を公開したり、分散先インスタンス一覧を見れるようにして安心感を与えてもらえたり、とかはできそうなので、今後に期待といったところです。
・・・ふぅ、内容が地味過ぎてヤヴァイ!!