この記事は every Tech Blog Advent Calendar 2024(夏) 24 日目の記事です。
はじめに
こんにちは。DELISH KITCHEN 開発部 RHRA グループ所属の池です。
RHRA グループでは主に小売向けプロダクトの開発を行なっています。 本記事では、RDS の EBS BurstBalance が枯渇してパフォーマンスが著しく低下した事例について、その調査過程で得られた知見を共有したいと思います。
背景
事象が発生した環境では、以下の構成の RDS インスタンスを使用していました。
- MySQL 5.7.44
- インスタンスタイプ: db.t3.medium
- ストレージタイプ: 汎用 SSD (gp2)
- ストレージサイズ: 30GiB
この環境において、BurstBalance が急激に消費され始め、最終的に枯渇してししまい、データベースのパフォーマンスが著しく低下しました。 次の図は BurstBalance が枯渇した際のメトリクスです。
BurstBalance とは
まず BurstBalance について理解を深めておく必要があります。
BurstBalance の仕様
BurstBalance は、Amazon EBS の汎用 SSD (gp2) ボリュームに適用される性能指標です。これは、ベースラインパフォーマンスを超えて一時的に高い IOPS を発揮できるクレジットの残量を表します。 gp2 ボリュームの場合、ボリュームサイズに応じて以下の特性を持ちます。
- ベースラインパフォーマンス: 3 IOPS/GB、ただし最小 100 IOPS
- バーストパフォーマンス: 最大 3,000 IOPS
- 最大クレジット量: 5.4 million I/O クレジット
- IOPS がベースラインを超える場合にバーストが利用され、最大 3,000 IOPS まで発揮される
BurstBalance が完全に枯渇した場合、IOPS はベースラインの値に制限されます。例えば、30GiB のボリュームの場合、ベースラインは 100 IOPS(3 IOPS/GB × 30GB = 90 IOPS ですが、最小値の 100 IOPS が適用されます)となり、BurstBalance 枯渇時はこの値に制限されます。
これらの仕様により、gp2 ボリュームは短期的な高 I/O 要求に対応できますが、継続的に高い I/O 要求がある場合、バーストバランスが徐々に減少し、最終的にベースラインパフォーマンスに制限されることになります。
消費量の計算式
BurstBalance の消費量は以下の式で概算できます
消費量 = (使用IOPS - ベースラインIOPS) × 秒数
例えば、30GiB のボリュームの場合
ベースラインIOPS = 最小量の100IOPS 1分間300 IOPSを維持した場合の消費量 = (300 - 100) × 60 = 12,000 IOクレジット
より詳細な gp2 ボリュームの仕様と BurstBalance については、AWS の公式ドキュメントを参照してください。
調査方法
問題が発生した際、以下の調査を行いました
- CloudWatch メトリクスの確認
- BurstBalance, ReadIOPS, WriteIOPS 等のメトリクスを確認
- RDS のエラーログの確認
- スロークエリログの確認
- Performance Insights の利用
- 問題のあるクエリの特定と実行計画の分析
調査結果
調査の結果、以下のことが判明しました。
- BurstBalance は主に WriteIOPS の増加によって発生
- I/O を大きく消費するクエリが存在
次の図は BurstBalance と WriteIOPS の相関を表すグラフになります。 青色が BurstBalance、オレンジ色が WriteIOPS を示しています。WriteIOPS が増加すると BurstBalane が減少することがわかります。
ここで仮に 1 時間平均の WriteIOPS を 600IOPS/second として、そのペースで消費し続けたと仮定した場合に、枯渇するまでの時間を概算で計算すると以下のように計算できます。
1時間あたりの消費量: (600 - 100) × 3600 = 1,800,000 IOPS/hour BurstBalanceを消費し切る時間: 5,400,000 / 1,800,000 = 3時間
また、問題のクエリは複雑な SELECT 文で、実行時に I/O が急上昇していました。 このクエリに対して実行計画を確認したところ、extra に Using temporary, Using filesort という記述があり、内部的なソートの実行と一時テーブルへの書き込みが発生していることがわかりました。
これらの結果から、複雑な SELECT 文の実行により、大量の一時テーブルへの書き込みが発生し、それが BurstBalance の急激な消費につながったと考えられます。
ただ、あるタイミングで突然このクエリが一時テーブルへの書き込みを大量に行うようになった要因については調査中になります。 可能性の一つとして、日々のデータの蓄積によりこのクエリで利用する一時テーブルの容量がオンメモリ内で処理できるメモリ制限を超えてしまい、ディスクに書き込まれるようになった可能性を推察しています。 この部分については引き続き調査中なので、結果がわかったら報告できればと思います。
対策
本事象について、以下の対策が考えられます。
- 問題のクエリのチューニング
- インデックスの見直し
- クエリの書き換え(サブクエリの削除、JOIN の最適化など)
- ストレージのアップグレード
- gp2 から gp3 への移行を検討(より予測可能なパフォーマンス特性)
- ストレージサイズの増加(ベースライン IOPS の向上)
- 一時テーブルのサイズ制限の調整
- tmp_table_size と max_heap_table_size パラメータの調整
- インスタンスタイプの見直し
- より高性能なインスタンスタイプへの移行
- 定期的なクエリパフォーマンスの監視と改善
- システム監視の見直し
- CloudWatchAlert 等による BurstBalance の監視
- Pingdom 等による外形監視
まとめ
RDS における BurstBalance の枯渇は、予期せぬパフォーマンス低下を引き起こす可能性がある問題です。今回の事例から、以下の学びを得ることができました
- 複雑なクエリが I/O に与える影響の大きさ
- データベース設計とクエリ最適化の継続的な改善の必要性
- 定期的なパフォーマンスモニタリングの重要性
今回の知見が少しでも皆様の参考になれば幸いです。