この記事は以下の記事の続きです。
前提等を読まないとわからない点がありますので、先にこちらの記事を読むことをお勧めします。
なお、先の記事は移行作業の段階別に記しましたが、ここから先の段階で発生したのはほぼパフォーマンスの問題ですので、段階別ではなく
- 単一スレッドでのパフォーマンス問題
- 複数スレッドでのパフォーマンス問題
に分けて記して行きます。
また、Auroraの仕組み、特にストレージ処理については、以下のスライド・記事がわかりやすいので先に見ておくことをお勧めします。
- Amazon Aurora - Auroraの止まらない進化とその中身(db tech showcase Tokyo 2017・AWS星野さん/7~10ページ目)
- Amazon Aurora のアーキテクチャまとめ - おくみん公式ブログ(おくみんさん)
3. 単一スレッドでのパフォーマンス問題
この点については事前に把握していたので正確には「躓いたポイント」ではないのですが、Auroraのパフォーマンスについて理解するのに重要な点だと思いますので、あえて記しておきます。
移行対象の夜間バッチの中に、以下の記事に書いたような状態の処理が含まれていました。
要は、
- バッファプール外のデータを読み込んで上書きしていた
- その中に、大きなBLOBカラムを含むものがあった
ということですが、このバッチの処理時間が
- オンプレ時⇒30分程度
- Aurora⇒1時間30分程度
と、約3倍に延びてしまいました。
AWS公式の情報にも**「Auroraは並列処理に最適化されている」**旨の記述がありますが(裏を返せば直列処理は得意ではない)、
- オンプレ⇒ホストの**「すぐそば」にあるストレージの、1台のコントローラのライトバックキャッシュに書き込んだら「ACK」**
- Aurora⇒WriterがあるAZとは別のAZにあるストレージノードのローカルSSDにも書き込まないと「ACK」にならない
という差がある以上、仕方のないことだと思います。
前者のネットワーク遅延がマイクロ秒(またはそれ以下)のオーダーであるのに対し、後者はミリ秒のオーダーになります。
移行前が**「非常に遅い」と評判?の「MySQL 5.5でsync_binlog=1」**だったので、MySQL 5.6ベースのAuroraならもう少しマシな結果になるかな、とは思いましたが…。
Auroraでは、処理を並列化することで速度改善するテクニックを使うことができますので、このバッチ処理も本来であれば**「処理するデータの範囲を複数に分割して並列処理する」ことで処理時間の短縮を図るのが筋でしたが、時間がなかったので「無駄に読み込んでいたデータをできるだけ削減することでI/Oを減らして処理時間を短縮する」**という手法で暫定対処しました。
4. 複数スレッドでのパフォーマンス問題
こちらは、Webアプリケーションからのアクセスにおけるパフォーマンス問題です。
1つは「並列処理のパフォーマンス」問題、もう1つは「AZ間のネットワーク遅延」問題です。
4-1. 並列処理のパフォーマンス
先に記したとおり、Auroraは並列処理に最適化されているそうですので、それを信用するのであればこの問題は(少なくとも「遅いことで有名な(しつこい)MySQL 5.5でsync_binlog=1」からの移行であることを考えれば)発生しないはずです。
実際のところ、こちらはほぼ問題にはなりませんでした。
書き込みに関しては、I/Oの削減やストレージ層でのリソース競合を減らす改善の効果があって、**「単一の処理は速くないが、並列数が増えたときに遅くなりにくい」**という結果になりました。
※本筋の話とは違いますが、オンラインDDLには非常に強いです。
但し、「INDEXをうまく使えないWHERE句・結合を使ったUPDATE/DELETE」は、より上位の層でロックが掛かりますので並列性は向上しません。
読み取りに関しては、**「特に並列性は向上しない=ほとんどMySQLと変わらない」**という結果に。
元から「遅かったSQL」は、移行後も変わらず「遅いSQL」のままです。並列処理の足を引っ張り続けています…。
これには理由があって、MySQL時代から**「処理に必要なデータの大部分はバッファプールに載っている」前提でアプリケーションが動作**しており、このような状態ではストレージ層以下の並列性向上の仕組みは効果が出ないからです。
効果がありそう、といえば、「ベースがMySQL 5.5から5.6に上がったこと」と(MySQL 5.6以降はロック粒度などの関係で「使用は非推奨」とされている)クエリキャッシュが(機能改善によって)「使える」状態になっている、ということぐらいでしょうか。
結論として…期待通りに読み取りをスケールさせるには、(レプリカ遅延が短く安定している)Readレプリカを有効活用する必要があると思います(私見)。
4-2. AZ間のネットワーク遅延
実際にはこちらが問題になりました。
オンプレからの移行では特に注意が必要なポイントです。
逆に、Multi-AZ構成のRDS for MySQLからの移行では特に問題にならないと思います。
pt-query-digestで集計してみると、AuroraのWriterとは別のAZにあるWebサーバからAuroraのWriterの間のネットワーク遅延が往復2ミリ秒強ありました。
JavaのアプリケーションからMySQL Connector/J経由でSQLを送る際、**「SET autocommit=【0または1】」などが意外と多く送信されます。
また、DBCP2などのコネクションプーリングでValidation Queryを使っていると、「SELECT 1」**のようなSQLを大量に送信することにもなります。
このような単純なSQLは、AuroraのWriterと同じAZにあるWebサーバから送れば(ネットワークの往復時間込みで)数十~数百マイクロ秒で実行できるのに対し、別AZにあるWebサーバから送ると、それだけで元のSQLの数倍またはそれ以上の時間を要することになります。
さらに、アプリケーション側で「ぐるぐる系」の処理を書いて、単純なクエリを大量にループで投げるようなことをしていると、(同じ原因により)非常に遅くなります(この手のものは「サブクエリが非常に遅かった頃のMySQL」時代から使われているコードによく潜んでいたりします)。
この問題は、「ぐるぐるコード」を書き直すとともに、Webサーバと同じAZにあるReadレプリカを活用することによって、解決することになりました。
また、更新処理の直接的な改善にはつながりませんが、Readレプリカに処理を逃がすことによってWriterの負荷を下げる、という効果はあります。
※その他、更新系では、addBatch()の処理でオーバーヘッドを減らすために、MySQL Connector/J(JDBC)のパラメータで**「rewriteBatchedStatements=true」を忘れずに設定しておく**ことも有効です。
もっとも、これらの問題は、
「移行の際、本当にMulti-AZ構成が必要だったのか?」
という点に行き着く、とも言えます。
確かに、**「Multi-AZでなければSLA対象外」**というネックはあるのですが、本当に大事なのは、
「実際にどの程度の頻度でAZ単位の障害が発生し、発生したらどの程度の損害が生じるのか?」
だと思います。
Auroraを使えばデータそのものは高い冗長性・耐久性のある仕組みによって「保護」されますし、**「AZ全滅時に15分程度の停止時間が許容できる」**のであれば、Single-AZ構成(別AZ側でEC2等をスタンバイ)を採用しても良いわけです。
※ALBを使っていて時々暖機が必要な場合は、ターゲットのWebサーバが両AZでアクティブ(Healthy)になっていないとAWSに暖機処理をしてもらえない、という問題はあります。が、自分でリクエストを投げて「暖機」してしまえば問題ありません。
というわけで、今後何らかのシステムを移行/新規構築する際には、この点をきちんと検討した上で構成を決定しようと思います。
5. まとめ
色々ありましたが、結局、主に躓いたのは、
- IPアドレスベースではなくDNS(FQDN)ベースでのインスタンスへのアクセスと、それを利用したフェイルオーバーの仕組み
- AZ間のネットワーク遅延
という、「Aurora固有というよりはAWS全体の特性」にフィットさせる部分だったと思います。
また、オンプレからの移行におけるAurora採用のメリットは、速度面よりも、
- 管理の手軽さ(特にデータ領域の拡大が実質的にメンテナンスフリー!)
- データ保護性能・高耐久性
- 遅延が少なく手軽に使えるReadレプリカ
にある、と感じています。
2018/01/27追記:
続きを書きました。
【おまけ】
Amazon Aurora関連投稿記事へのリンクを集めました。