はじめに
子育てメディア「トモニテ」でバックエンドやフロントエンドの設計・開発を担当している桝村です。
この記事は「every Tech Blog Advent Calendar 2023」 の 15 日目の記事です。
私たちは 2023 年 6 月にシステムメンテナンスを実施し、トモニテのアプリやWebサイト等で利用している本番環境のデータベース Amazon Aurora の MySQL バージョンを 5.7 から 8.0 へアップグレードしました。
本記事では、メンテナンスを伴う MySQL 8.0 へのアップグレードについて、実施内容やそれによって得られた知見について紹介します。
背景
前提として、Amazon Aurora は 1 〜 3 の メジャーバージョンが存在し、それぞれのバージョンに対してサポート終了日が設定されています。
Auroraバージョン | MySQLバージョン | Aurora 標準サポート終了日 |
---|---|---|
1 | 5.6 | 2023 年 2 月 28 日 |
2 | 5.7 | 2024 年 10 月 31 日 |
3 | 8.0 | 未定 |
トモニテでは、2019 年にサービスを開始して以来、Aurora バージョン 2 (MySQL 5.7 互換) を利用してきました。
しかし、Aurora バージョン 2 (MySQL 5.7 互換) のサポート終了日 (2024 年 10 月 31 日) が近づいていることから、Aurora バージョン 3 (MySQL 8.0 互換) へのアップグレードを実施することにしました。
また、Aurora MySQL は コミュニティ版 MySQL をベースに開発されているため、コミュニティ版 MySQL 8.0 の機能を利用できることもアップグレードの実施理由の一つになりました。
調査
データベースの現状把握
アップグレードにおける最初のステップとして、データベースの現状を正確に把握することが重要です。
そこで、データベースのエンジンバージョンや、アップグレード可能なエンジンバージョンを確認しました。
# データベースのエンジンバージョンを出力 # AWS CLI の aws rds describe-db-clusters コマンドを利用 $ aws rds describe-db-clusters --db-cluster-id tomonite-prod-rds-cluster \ --query '*[].EngineVersion' \ --output text \ 5.7.12 # エンジンバージョンから、アップグレード可能なエンジンバージョンのリストを出力 # AWS CLI の aws rds describe-db-engine-versions コマンドを利用 $ aws rds describe-db-engine-versions --engine aurora-mysql \ --engine-version 5.7.12 \ --query 'DBEngineVersions[].ValidUpgradeTarget[].EngineVersion' \ [] # データベースのインスタンスへ接続の上、 Aurora のバージョンを出力 # Aurora MySQL関数 AURORA_VERSION() を利用 mysql> select AURORA_VERSION(); +------------------+ | AURORA_VERSION() | +------------------+ | 2.02.5 | +------------------+ 1 row in set (0.00 sec)
アップグレード可能なエンジンバージョンが存在しないことがわかり、Aurora のバージョンを確認したところ、Aurora バージョン 2.02.5 で、これは新規作成には使用できなく、非推奨となっているバージョンであることがわかりました。
この前提を踏まえて、次のステップとしてアップグレードの手法の検討を行いました。
アップグレードの手法の検討
アップグレードの手法の検討に際して、重要な考慮事項として以下を挙げました。
- 切り戻しができ、かつ容易であること
- 移行作業が簡潔であること
- データベースの停止時間が短いこと
データベースは Web や App, RSS 等のサービスへの影響範囲が大きい点で、不具合発生の可能性を考慮すると、切り戻しができ、かつ容易であることが特に重要でした。
結果としては、スナップショット復元
を利用したアップグレード手法を採用しました。
理由としては、切り戻しの必要がある場合、サーバーのデータベースへの向き先を古いデータベースへ切り替えるだけで済むため、切り戻しが容易であることです。
他の候補として インプレースアップグレード
、ブルー/グリーンデプロイ
があり、移行作業の簡潔さやデータベースの停止時間の短さという点では、スナップショット復元よりも優れている可能性があると考えていました。
しかしながら、前述の通り Aurora バージョン 2.02.5 はアップグレード可能なエンジンバージョンが存在しなく、また新規作成にも使用できない点で、利用できないと思われることから、採用を見送りました。
定期的に運用・管理することが大切だと感じる事例であり、今後の教訓にしたいと思います。
MySQL 8.0 の変更点の影響確認
ここでは、MySQL 8.0 における変更の影響を確認しました。
照合順序
照合順序とは、データベースでも文字列を比較する際のルールや順序を定義するものです。
MySQL 8.0 より、utf8mb4
のデフォルトの collation が utf8mb4_general_ci
から utf8mb4_0900_ai_ci
になるという変更があります。 つまり、明示的に collation を設定していない場合は utf8mb4_0900_ai_ci
になってしまうという問題ですが、トモニテでは、明示的に utf8mb4_general_ci
を指定していたため、影響はありませんでした。
各文字セットにはデフォルト照合があります。 たとえば、utf8mb4 および latin1 のデフォルトの照合は、それぞれ utf8mb4_0900_ai_ci および latin1_swedish_ci です。
MySQL :: MySQL 8.0 リファレンスマニュアル :: 10.2 MySQL での文字セットと照合順序
予約語の rank
MySQL 8.0 より rank
という単語が予約語に追加されました。トモニテでは、既存のテーブルに rank
というカラムが存在しており、マイグレーション時にエラーが発生しましたが、バッククォートでエスケープすることで回避しました。
MySQL :: MySQL 8.0 リファレンスマニュアル :: 9.3 キーワードと予約語
暗黙的ソート
MySQL 8.0 より、GROUP BY
句での暗黙的ソートがされなくなったので、ORDER BY
句を使う必要が発生しました。ただし、トモニテでは、暗黙のソートに依存している箇所はなかったため、影響はありませんでした。
以前は (MySQL 5.7 以下)、GROUP BY は特定の条件下で暗黙的にソートされていました。 MySQL 8.0 では発生しなくなったため、暗黙的ソートを抑制するために最後に ORDER BY NULL を指定する必要はなくなりました (前述のとおり)。 ただし、クエリー結果は以前の MySQL バージョンとは異なる場合があります。 特定のソート順序を生成するには、ORDER BY 句を指定します。
MySQL :: MySQL 8.0 リファレンスマニュアル :: 8.2.1.16 ORDER BY の最適化
計画・検証
メンテナンスの手順
まずデータベースのアップグレードを含むメンテナンスの手順を策定するにあたって、現行のシステムなど前提条件を整理すると、以下の通りになりました。
- アップグレードの手法は、スナップショット復元を利用
- 新旧のデータベースにおいて、できる限り整合性の担保が必要
- データベースへのリクエストは必ずサーバーを経由
AWS EventBridge
経由で定期実行されるバッチ処理や死活監視ツールDatadog
のアラート通知が存在
関係箇所を構成図にまとめると、以下のようになります。
これらの前提条件を踏まえた結果、メンテナンスの手順を概ね以下で策定しました。
# 1. 事前作業 (ex. バッチ処理実行・死活監視ツールのアラート通知を一時停止) # 2. サーバーが一律で503を返却するように ALB を設定 # 3. 古いクラスター (Aurora バージョン 2) からスナップショットで復元 # 4. スナップショットからクラスター・インスタンス (Aurora バージョン 3) を生成 # 5. サーバーのDBの向き先を新しいクラスター (Aurora バージョン 3) のエンドポイントへ変更 # 6. [切り戻しの必要がでた場合] 古いクラスター (Aurora バージョン 2) のエンドポイントへ変更 # 7. サーバーがリクエストを受け付けるように ALB を設定 # 8. アプリ・Webサイト等の動作確認 # 9. [切り戻しの必要が出た場合] 古いクラスター (Aurora バージョン 2) のエンドポイントへ変更 # 10. 事後作業 (ex. バッチ処理実行・死活監視ツールのアラート通知を再開)
手順の 2 ~ 6 が、サーバーがリクエストを受け付けていない、つまり各サービスが停止している時間になります。また、手順 3 ~ 5 が、データベースのアップグレード (スナップショット復元
) に関する手順になります。
開発環境での検証
本番環境にて、可能な限りアップグレードを計画通りに完了させるためにも、開発環境での検証は非常に重要です。
特に以下の点について、念頭におきながら開発環境での検証を行いました。
- 定義した手順でのアップグレードの実現性
- 各手順の所要時間の概算見積もり
- 作業効率化など改善ポイントの洗い出し
結果としては、大きな遅延や問題なくアップグレードを完了させることができました。
また、作業に含まれていた AWS CLI コマンドのパラメータの指定ミスや、Web サイトがメンテナンス画面の表示できない等が発覚し、本番環境への実施に向けて、開発環境での検証の重要性を再認識することができました。
本番環境での実施
ここまで長い道のりでしたが、ついに本番環境でのアップグレードを実施しました。
結果としては、予定のメンテナンスの時間内かつサービスへの大きな影響なく、アップグレードを完了させることができました。1回のメンテナンスで完了させることができたことは、非常に良かったと思います。
ただし、アップグレードに際して、一部意図しない事象が発生したので、簡単ですが紹介します。
データベースインスタンスのオペレーティングシステムアップデート
手順 4 のスナップショットからクラスター・インスタンス (Aurora バージョン 3) を生成した直後、データベースインスタンスのオペレーティングシステムアップデート
が自動的に実施されました。
原因としては、RDS エンジンをアップグレードしたことに伴い、新しい RDS エンジンに対応する OS アップデートが必須だったためかと思われます。
DB インスタンスのメンテナンス - Amazon Relational Database Service
このオペレーティングシステムアップデート自体は、15 分程度かかりましたが、幸いにも、他の手順がスムーズに進んでいたため、全体としてはメンテナンスの時間内にアップグレードを完了させることができました。
クエリによるCPU負荷の上昇
データベースのアップグレードが完了した翌日、クエリによる CPU 負荷が上昇していることが Datadog
によるアラート通知で発覚しました。
RDS データベースのパフォーマンスを分析しチューニングできる RDS Performance Insights
を有効化し、どのクエリが CPU 負荷の上昇に関与しているかを調査しました。
その結果、社内向けサービスで利用している API の SELECT COUNT(*)
クエリが CPU 負荷の上昇に関与していることがわかりました。
もちろん、本番環境における元々利用していたテーブルのレコード数が多く、ワークロードが大きいことも原因です。
SELECT COUNT(*) FROM `user_tokens`;
調査したところ、MySQL バージョン 8.0 の特定のバージョンにおいて、SELECT COUNT(*)
のパフォーマンスに関係するバグが報告されていることを確認しました。
幸いにも、このクエリは無くても業務上大きな影響はないことが判明したため、クエリを利用しない設計へ変更することで、CPU 負荷の上昇を抑えることができました。
反省点としては、MySQL バージョン 8.0 における仕様変更やバグについて調査したり、開発環境と本番環境で異なるデータ量への依存を考慮しておくべきだったと思います。
全体を通して振り返り
メンテナンスを実施し、本番環境のデータベースを Aurora バージョン 2 から 3 へアップグレードすることができました。
最後に、メンテナンス全体を通して、やってよかったことを振り返ります。
AWS リソースの設定変更を Terraform および AWS CLI でコード化
手順 2 の ALB の設定変更をはじめ、可能な限り AWS リソースの設定変更を AWS コンソール上での操作でなく、Terraform
を使ったコード化を行いました。
例えば、以下のように ALB の Listener Rule を追加する PR を作成しておき、メンテナンス中に terraform apply
で適用します。
resource "aws_alb_listener_rule" "tomonite_server_rule_maintenance" { listener_arn = aws_alb_listener.tomonite_ecs_lb_https.arn priority = 1 action { type = "fixed-response" fixed_response { status_code = "503" content_type = "application/json" message_body = jsonencode( { message = "メンテナンス中です" } ) } } condition { host_header { values = ["server.tomonite.com"] } } }
同様に、手順 3, 4 でのデータベースのスナップショット復元やクラスター・インスタンスの生成などのAWS リソースの設定変更も、AWS CLI を利用しました。
これにより、AWSコンソールでの操作ミスの未然防止やチーム内でのレビューが容易になり、メンテナンスの安全性を高めることができました。また、メンテナンス中はチーム内でレビュー済みのコマンドを実行するのみになり、作業の効率化にもつながりました。
切り戻し時の作業のマニュアル化
手順 6 の切り戻しの必要があった場合の作業をマニュアル化しました。
本番環境でのメンテナンス中は緊張感が高まるので、意図しないトラブルが発生した場合、チームメンバーが普段通りの作業・判断ができない可能性があります。そのため、切り戻し時の作業をマニュアル化することで、メンテナンス中に切り戻しを実施する場合でも、作業の手順を確認しながら実施できるようにしました。
今回は切り戻しの必要はありませんでしたが、データベースインスタンスのオペレーティングシステムアップデートの件で、メンテナンス中にて時間内に完了できるかどうか焦りや不安を感じたので、切り戻し時の作業のマニュアル化は非常に有効だと感じました。
終わりに
今回は、トモニテの本番環境のデータベース Amazon Aurora の MySQL バージョンを 5.7 (Aurora バージョン 2) から 8.0 (Aurora バージョン 3) へアップグレードしたことについて、実施内容やそれによって得られた知見について紹介しました。
これから MySQL 8.0 化を検討されている方、データベースのアップデートに伴うメンテナンスを実施される方々の参考になれば幸いです。