久しぶりの投稿で全く筆が乗らないので、真面目な技術話をザックリとした感じで参ります。
今回は Aurora の I/O-Optimized を扱うなり採用を検討する上での、基本的な知識と考え方を整理していきます。ある程度の大きめなトラフィックを扱うエンジニアにとっては、絶対に知っておいた方が良い機能でもあります。
目次
はじめに
基本的な内容は公式リリース記事のとおりです。一般記事としては、この辺も参考にするとよさげです。
- Amazon Aurora I/O-OptimizedではI/O オペレーションの料金に目が行きがちだけど実はパフォーマンスも向上するかもしれない話 – Coincheck Tech Blog
- AmebaのAurora MySQLをI/O Optimizedに切り替えたらコストが半額になりました – CyberAgent SRG #ca_srg
- Aurora I/O-OptimizedでRDSのコストを削減した話 – Gunosy Tech Blog
基本から整理していきますが、重要事項は公式にあるこの記述に集約されています。
Aurora I/O 最適化を使用すると、ワークロードの要求が非常に高い場合でも、パフォーマンスは向上し、スループットは増加し、レイテンシーは減少します。Aurora I/O 最適化では、読み取りと書き込み I/O オペレーションの料金は発生しません。
実態はすごく強い機能なのですが、あまり具体的な数値や事例がないことや、世間的には費用メリットの方に目が向きがちになっている感じがあって、そのすごさの理解度が低いのではないか、といういことで書いている次第であります。
費用メリット
費用条件
まず採用するメリットの前半となるコスト削減について、これは公式の通りなのですが少し具体的に考えていきます。Aurora I/O 最適化は、I/O 料金が Aurora データベースの総消費量の 25% を超える場合、I/O 集約型アプリケーションのコストを最大 40% 削減します。
料金 – Amazon Aurora | AWS より、標準ストレージ版と I/O最適化版 では、以下のような違いがあります。
- インスタンス費用が 30% 増しになる
- ストレージGB 費用が 125% 増し (2.25倍) になる
- I/O 費用がゼロになる
元がリザーブドインスタンスの場合、30% 増しになると不足分が出ることになり最適化には付け足しが必要になりますが、その辺まで含めると面倒なので省略してオンデマンドとして扱っていきます。
損益分岐点
構成・利用状況によって I/O-Optimized による損益条件は変わりますが、わかりやすさのため具体的例をだして整理していきます。ここで重要なのは、I/O-Optimized 有効化によってコストが大幅に減る場合は嬉しいですが必須ではなく、トントンもしくは場合によっては微損でもよい、というのは後で説明する性能の話とか関わってきます。
では計算用の条件として中規模にするとどうなるかというと──
月額条件(東京30日間) | 標準ストレージ | I/O-Optimized |
r7g.4xlarge * 2AZ | $3833 | $4983 |
データ容量 1000 GB | $120 | $270 |
I/O 費用 | $3000 | $0 |
合計 | $6953 | $5253 |
このように更新の激しいデータベースの場合、I/O 費用が結構高くなるので -$1700/月 くらいのコスト削減になることは珍しくありません。当然ですが、この3つの条件はサービスや Auroraクラスタによって異なるので、こういう風に整理して計算してみましょうという例に過ぎません。
インスタンスとデータ容量の費用が上昇する分を、I/O 費用で相殺以下にできるかがポイントなので、まずは Cost Explorer でグループ化の条件を「使用タイプ」にして Aurora:StorageIOUsage の費用を確認するところから始めましょう。
I/O の使用量
負荷試験時などサービスのリリース前においては、正確に I/O リクエスト数を予想することは難しいです。そのため、リリースやアップデート前後において、標準からI/O最適化へ、I/O最適化から標準に変更することを視野に入れておくとよいです。変更回数に条件はあるものの、オンラインで変更可能なので、1日分のコスト結果や各メトリクスを確認してから最適な方を選択することが可能です。
既存のデータベースクラスターを Aurora I/O-Optimized に切り替えることができるのは、30 日ごとに 1 回です。Aurora Standard にはいつでも戻すことができます。
更新量が少ないことが確定しているクラスタは標準ストレージで良いですが、トラフィックの急増や予期せぬ処理量が読めない状況に対応する場合は、初手 I/O-Optimized にして開始しつつ数日経過してから標準に変更するかどうかを見極めると、少々の費用増加と引き換えに安全を買えるでしょう。
ストレージの性能限界
I/O-Optimized の性能の話に入る前におさらいとして、データベース── ここでは MySQL としてのストレージの要所について整理します。HDD の時代に苦労したような データセンター部屋おじさん 達は、百も承知な知識と苦い経験があるでしょうが、ここ最近の若手だと認識・理解するための境遇に遭遇する機会が激減しているかもしれませんね。
ストレージの性能上限とは
まずストレージには Read/Write の IOPS 性能上限が存在し、SATA HDD 1本で 100、SAS HDD 1本で 200 とかだったのが、今は多くの環境で 数千~数万以上 が上限値になっています。この IOPS 上限に到達するとどうなるかというと、ストレージへのインプットやストレージからのアウトプットが詰まるので、後続の処理が遅延します。遅延が酷くなると、おそらくどこかでタイムアウトが発生してユーザーへのレスポンスとしてはエラーになるでしょう。
別の要因としては、ストレージのいろんな箇所に書き込むだけなら IOPS 性能が足りていれば問題なくとも、1つのファイルに追記していく場合は話が全く別になります。並列した複数の処理が1つのファイルに正確に追記するにはロック含めた順番待ちが必要になるので、ストレージ性能だけでは解決できないことも多いです。
他にはストレージそのものへの Bandwidth や Latency などが要因としてありますが、大きくは IOPS 上限と追記型 の2種類が障害に強く関係すると思ってよいです。
Read IOPS
ストレージへの Write は INSERT / UPDATE などの QPS が大きければ高くなるのは想像しやすいでしょう。では Read はどういう時かというと、データがメモリに無くてストレージからデータを取得する必要がある場合となります。DBデータが全てメモリに収まる容量であれば、ずっとオンメモリなので Read IOPS はゼロとなり理想形となります。しかし実際にはいつかは メモリ容量 < データ+インデックス容量 になるので、徐々に Read が発生してきます。
Read が発生するということは、メモリ上で処理が完結しない → ストレージから持ってくる 分だけ、処理に遅延が発生することになります。ただ、しつこいですが HDD 時代と違って今は Latency がとても小さくなっているので、少々の Read が発生しても処理全体の時間に対する遅延時間が小さいため、とても致命傷になりづらくなりました。
しかし Aurora においては Read/Write IOPS は従量課金であり、Aurora の高性能ストレージといえど Read が発生する処理のほうが遅くなるのは変わらない、ということは覚えておく必要があります。
REDO ログ
Aurora だけ使っていると、もはや REDO ログの存在も知らない場合も多そうだし、知らなくてもよしなにデータ保全をしてくれるのが良いところなのですが、わりと基礎寄りの重要知識なので触れておくとよいです。REDO ログは Aurora のクラスタストレージに保存されるので、そのストレージ性能に影響されるものです。
実は Aurora は(贅沢な物言いだけど)そこまで更新性能に強いわけではなく、リソース構成やクエリ性質の兼ね合いによっては、CPU使用率 100% 到達前に REDO ログの更新限界によって実質的な障害に陥ることがあります。
これは処理内容によって異なるので無責任な参考値になりますが、QPS が高くなりがちな INSERT / UPDATE が 8000 ~ 15000 QPS を超えてくるあたりから、REDO ログもとい COMMIT の Latency が急激に悪化していきます。これはメトリクスの CommitLatency よりは Performance Insights の wait/io/redo_log_flush を見るほうが正確です。
また、インスタンスクラスのサイズによって裏側の性能は結構変わります。上記の参考値は r7g.8x ~ 16xlarge あたりの話なので、より小さいサイズの場合はより性能悪化の QPS が小さくなる可能性があります。
binlog
主にレプリケーション用のバイナリログについても、今だと大きな環境移行でもしない限りは扱わなくなってきたかもしれません。これも REDO ログ同様、クラスタストレージに保存されるのでストレージ性能が関係するのと、サイズが大きくなりがちなのでデフォルトの1日分でもストレージ容量課金に大きく影響してくるものです。
最近だと Blue/Green のために使うことがあるかもですが、更新系の QPS が高いと Performance Insights の wait/synch/cond/sql/MYSQL_BIN_LOG が上昇しやすくなり、REDO ログと合わせるとかなりのボトルネック要因になる場合があります。
性能メリット
効果
ここまで要因を整理すれば、I/O-Optimized の性能メリットは丸裸になっていると思いますが整理するとこんな感じです。- Read IOPS が発生しても Latency が高くなりづらい(ついでに費用もゼロ)
- 更新量が多くても REDO ログがボトルネックになりづらい
- binlog を有効にした時の Latency が小さくなる
大量の更新クエリが投げられている環境で、標準から I/O-Optimized に変更すると、例えば wait/io/redo_log_flush の値が 1/2 ~ 1/5 くらいに減少したり、CommitLatency が 15ms → 3ms に早くなったりします。
あくまでストレージ性能要因なので、InsertLatency や UpdateLatency は変わりませんが、高トラフィックに対する COMMIT 時の急激な性能悪化、という現象を抑えてくれるのは心強いところです。
また、サービスの運用がめでたく長期化して、データ+インデックス容量がメモリサイズ以上になって Read IOPS が多く発生してきた時に、昔ならメモリ容量を増やす対応も普通でしたが、Aurora I/O-Optimized ならインスタンスクラスをそのままに、ある程度の良好なレイテンシと I/O 費用ゼロにできるので、地味に見えて超高い効果だったりします。
採用すべき状況
Performance Insights の値はかなり有用で、大雑把にはその vCPU数の 100 ~ 200% あたりの使用量以上になってくると、そこかしこのクエリの Latency が上昇してくると思われます。その詳細を見て、wait/io/redo_log_flush が多くを〆ていた場合に I/O-Optimized を有効化するだけで問題点をかなり先送りにできる可能性が高いです。メトリクスでは ReadIOPS が増加してきたときに、I/O-Optimized を有効化するだけで SelectLatency を緩和できる可能性が高いです。ReadIOPS が多く発生しだすような環境は、おそらく WriteIOPS も常々高い環境なので、その TotalIOPS を費用に換算すると、費用ゼロにしつつ性能向上の恩恵を受けるべきな条件になっているかもしれません。
CPU < I/O
運用中の Aurora インスタンスにおいて、例えば最大CPU使用率 30% で、wait/io/redo_log_flush が最大 50% 程度の場合、それ以上トラフィックが増えた時に先に REDO ログ要因で遅延が発生すると考えられます。ボトルネックは手前から順に何が先にきて次に何がきて、と考えるわけですが、REDOログ要因が一番手にある場合にインスタンスクラスを1つ上げて解決するかというと、多少マシにはなるけど根本的な解決にはならないと思われます。それは先に述べた通り、Aurora 特有の REDO ログ設計とはいえ、インスタンスのサイズが倍になったからといって、その部分の書き込み速度の上限が倍になるわけではないからです。
こういった条件下の場合は、コスパ良くいくならインスタンスクラスを1つ下げて I/O-Optimized を有効化すると、CPU使用率が 60%以下で wait/io/redo_log_flush が 20% 前後になると推測できます。ただし、コスパ最適化と引き換えにメモリ容量が半減すると ReadIOPS が発生しやすくなることに注意が必要です。もしCPU使用率やメモリ容量が不安な状況ならば I/O-Optimized を有効化するだけでもよく、それでも費用的にはトントン前後で、性能向上だけを受けられるかもしれません。
Read系 と Write系
それなりの規模になるサービスにおいては、もしデータベースのクラスタを役割で垂直分割できる場合は、書き込み量が多いユーザー系と、読み込みメインなデータで分けることを基本にすると、より最適化がしやすくなります。単純に Read 系は CPU使用率先行型かつデータ容量の蓄積も少ないため標準ストレージでよく、Write 系は I/O-Optimized によってボトルネックが REDOログになることを緩和しつつ、Read IOPS Latency の緩和、I/O 費用ゼロ化、
という風に分けて、それぞれでインスタンスクラスのサイズや Reader の数を調整することで、もろもろの最適化が捗ることになるでしょう。
おわりに
管理画面で言えば、たった1つの設定な I/O-Optimized ではありますが、それがどういう効果をもたらしてくれ、それはどういう理由であり、どのような知識があれば理解できるのか、という感じで基礎知識としてまとめてみました。結構多くの環境に刺さる可能性の高い、リスクが小さく強い一手なので、多少は理解度が低くても「最後のお願い」的にぶっ込んでみる価値はあります。
もしくは重たくなってきたときに AWS サポートに相談して、「I/O-Optimized の有効化を推奨します」と助言されて、じゃあそれを鵜呑みにして有効化するってのも、サポート費用を払っている身としてはアリでしょう。
ただ、理解度が低い状態で治った良かった良かったでチャンチャンするのは、やっぱエンジニアとしてはそうじゃないよねってのを提唱し続けていきたいお年頃なわけで、正しく理解をする学習の一助とでもなれれば幸いでありんす:-)