Amazon RDSのMySQL5.6を5.7へバージョンアップする

現在（2021年6月）、RDS で MySQL 5.6系を稼働させていると、AWSコンソールで以下のような警告が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/7816104f-c982-7827-11f3-66173439bc6a.png)

Amazon RDS では MySQL 5.6 のサポートは 2021年8月3日に終了します。
これはOracle社によるメンテナンスリリースやバグ修正、パッチが提供されなくなるためのようです。
（MySQL5.6 と互換性のある Amazon Aurora MySQL 1系 は特に終了のアナウンスはされてないので、おそらくAmazon側で独自にサポートしていくことになるのかもしれません）

## Oracle社によるMySQL5.6のEOLは2021年02月01日
MySQLの開発元であるOracle社では、「Oracle Lifetime Support for MySQL」というライフタイムサポートポリシーがあり、これに従ってアップデートやセキュリティパッチが提供されます。
バグ修正やセキュリティパッチの提供が行われなくなるのは9年以降の MySQL Sustaining Support の期間になります。

MySQL 5.6 の正式(GA)リリースは MySQL 5.6.10 で 2013年02月05日なので、2021年02月05日以降となります。
（正確には2021年02月01日から Sustaining Support になりました）

[MySQL :: MySQL テクニカル・サポート](https://www.mysql.com/jp/support/)
[MySQL :: MySQL 5.6 Release Notes :: Changes in MySQL 5.6.10 (2013-02-05, General Availability)](https://dev.mysql.com/doc/relnotes/mysql/5.6/en/news-5-6-10.html)
[MySQL :: MySQL Product Support EOL Announcements](https://www.mysql.com/support/eol-notice.html)

## RDSでMySQL5.6を2021年8月3日以降も稼働させるとどうなる？
2021年08月03日からメンテナンスウィンドウ中にMySQL5.7へアップグレードされます。
さらに、2021年09月01日以降はメンテナンスウィンドウにかかわらず強制的にMySQL5.7へアップグレードされるようです。

[AWS Developer Forums: Amazon Relational Database Service (RDS) for MySQL 5.6 End-of-Life date is August 3, 2021](https://forums.aws.amazon.com/ann.jspa?annID=8498)

## MySQL 5.7 へのアップグレードについて
まず、MySQLはメジャーバージョンアップ1つ分のアップグレードしかサポートされていません。MySQLの現在の最新バージョンは8.0（5.7の次）ですが、5.6から一気に8.0へアップグレードすることはできません。

### SQLの互換性について
オンプレなどでMySQLを運用している場合、MySQL 5.7 からSQLモードのデフォルトが変わったため、5.6で動いていたSQLがそのままでは動かないことがあるため、SQLモードの設定を5.6に合わせるか、SQLの方を修正するか対応する必要があります。
RDSのMySQLでは、パラメーターグループのSQLモードのデフォルトの設定が5.6と5.7ともに空に設定されているため、基本的には5.6で動いていたSQLは5.7でも動くと考えられます。（とはいえ検証環境などで入念に試験はするべきだと思います）

### パフォーマンスについて
これについては検索するとさまざまなサイトで色々なベンチマークやマシンスペックで行われていて、MySQL 5.7 は 5.6 と比較してN倍速いとか遅いなどと言われていますが、実際には自分のシステムのワークロードでちゃんとパフォーマンスが出るかどうかが大事なので、パフォーマンステストは行っておきましょう。
（バージョンアップすると特定のクエリだけ遅くなるというのはよくある話です）

### MySQLのアップグレードにかかる時間
アップグレードにかかる時間はメンテナンス時間に関わってくるため、事前に確認しておきます。アップグレードにかかる時間が許容できるかどうかによってアップグレード手順が変わってきます（後述）

AWS のドキュメントによると下記のような記載もありますが、

> MySQL のメジャーバージョンアップグレードは、通常約 10 分で完了します。

下記のような記載もあるため想定しているよりも時間がかかってしまうことがありえます。

> DB インスタンスクラスのサイズのため、またはインスタンスが「 Amazon RDS のベストプラクティス」の運用ガイドラインに従っていないため、この時間が長くなることがあります。

[MySQL DB エンジンのアップグレード - Amazon Relational Database Service](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.MySQL.html)
[Amazon RDS のベストプラクティス - Amazon Relational Database Service](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/CHAP_BestPractices.html)

試しにダミーデータで2TBのデータを作成して、`db.r5.2xlarge` のインスタンスをMySQL5.7へアップグレードすると30分くらいかかりました（データ構造やデータ量、インスタンスのスペックなどで異なると思います）

### 日時カラムの内部構造の変換にかかる時間
AWSのドキュメントにもある MySQL 5.7 へアップグレードする際の遅くなる場合として、MySQL 5.6.4から `datetime`、`tiem` 、`timestamp`の内部構造が変更されて、5.6.4より前のバージョンで作成されたテーブルでは変換処理としてテーブルの再作成が行われるため、データ量によっては非常に時間がかかってしまいます。

ただ、こちらは MySQL 5.5 から MySQL 5.6 へアップグレードするときに、よく問題としてあげられていたもので、構築時にMySQL 5.6であればおそらく問題ないと思います。
（MySQL5.6.4は正式(GA)リリース前で、MySQL 5.6.10 から正式リリースのため）

念の為、`CHECK TABLE ... FROM UPGRADE` クエリで確認してもいいかもしれません。

```例
MySQL> CHECK TABLE table_A, table_B FOR UPGRADE;
+----------+-------+----------+----------+
| Table    | Op    | Msg_type | Msg_text |
+----------+-------+----------+----------+
| table_A  | check | status   | OK       |
| table_B  | check | status   | OK       |
+----------+-------+----------+----------+
2 rows in set (0.00 sec)
```

`Msg_typ = status`、`Msg_text = OK` であれば変換処理は行われません。

[MySQL DB エンジンのアップグレード - Amazon Relational Database Service](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.MySQL.html#USER_UpgradeDBInstance.MySQL.DateTime57)

## MySQL アップグレード手順について
MySQLをアップグレードする手順は大きく分けて以下の2つになるかと思います。

* in-place アップグレード
* フェイルオーバーを使ったアップグレード

in-place アップグレードは比較的単純で、アップグレードプロセスの時間が短い、またはメンテナンス時間が長く取れる場合にマッチします。
フェイルオーバーを使ったアップグレードはアップグレード時に使用するインスタンス数が増えることによるコスト増や手順が in-place と比較するとやや複雑です。ただ、メンテナンス時間はフェイルオーバーにかかる時間だけになるため、アップグレードプロセスの時間がかかりすぎる場合や、メンテナンス時間があまり取れないシステムの場合にマッチしそうです。

## in-place アップグレードの手順
正式なアップグレードの手順についてはAWSのドキュメントを参考にしてください。ここでは大まかな流れを図を交えながら記載します。
ここではプライマリ1台、リードレプリカ2台の構成のシステムとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/1bbfcdfe-732b-56ab-5a69-aa83b7cdb2b8.png)

**1. レプリカ2を外す**
まず、アプリケーションからレプリカ2に対してアクセスが行かないように設定します。
システムによりますが、アプリケーションの設定を変更したり、Route53でCNAMEを設定している場合は外すなど行ってください。


**2. レプリカ2をアップグレードする**
アプリケーションから外れたレプリカ2をMySQL5.7へアップグーレードします。
アプリケーションからはプライマリーとレプリカ1を見ているので稼働したままアップグレードが可能です。

**3. レプリカ2のアクセスを戻す**
レプリカ2がMySQL 5.7へのアップグレードが完了して、レプリケーションの遅延が解消されたら、アプリケーションからレプリカ2へのアクセスを戻します。

**4. レプリカ1をアップグレードする**
レプリカ2の時と同様に、まずアプリケーションからレプリカ1へのアクセスが行かないように設定変更して、レプリカ1をMySQL5.7へアップグレードします。

ここまでで、レプリカはMySQL5.7へアップグレードが完了しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/49645914-98a3-4501-e7bf-3aa3b41836cd.png)

**5. アプリケーションをメンテナンスモードにする**
プライマリにアクセスできない（書き込みが行えない）のでアプリケーションをメンテナンスモードにして、プライマリをMySQL5.7へアップグレードします。
アップグレードにかかる時間がおおよそメンテナンスの時間となります。

**6. アプリケーションのメンテナンスを解除する**
プライマリのアップグレードが完了したら、アプリケーションからアクセスするように設定を変更してメンテナンスモードを解除します。


実際には確認作業等々あるので「プライマリのアップグレード時間=メンテナンス時間」とはならないのですが、おおよその流れはこんな感じになります。

また、リードレプリカのアップグレード中はリードレプリカの台数が減るため、リソースに余裕があるアクセスが少ない時間帯に行うか、リードレプリカの台数を増やすなどの対応が必要です。

## フェイルオーバーによるアップグレード手順
先ほどと同様にプライマリ1台、レプリカ2台の構成とします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/e6471f9a-43f3-811d-981c-55c7db24b320.png)

**1. リードレプリカを追加する**
レプリカ3になる新しいリードレプリカを追加します。
この段階ではアプリケーションからはアクセスさせません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/c4090d30-60a0-4652-1163-2c7b0f240c9b.png)

**2. レプリカ3 をMySQL5.7にアップグレードする**
新しく追加したレプリカ3をアップグレードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/8620db11-31a9-a734-1031-474140330816.png)

**3. レプリカ3にさらに新しいリードレプリカを追加する**
追加したレプリカ3をプライマリーとしたリードレプリカ4とリードレプリカ5を追加します。
プライマリーのレプリカ3は既にMySQL5.7なので、追加したリードレプリカもMySQL5.7になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/ab7611fc-aac0-e7d3-20d7-db6d3d48af1a.png)

**4. アプリケーションをメンテナンスモードに切り替える**
アプリケーションをメンテナンスモードにして、プライマリへの書き込みをしないようにします。
新しく追加したレプリカ3のレプリケーションが追いつくのを待ちます（更新データに漏れがないように）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/deb16f32-87d1-eb77-50c0-84b8932d323e.png)

**5. レプリカ3をフェイルオーバーして新しいプライマリに昇格させる**
リードレプリカ3をフェイルオーバーして新しいプライマリに昇格させます。この際にかかる時間は数分程度なのでアップグレードにかかる時間よりも短くなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46847/3ad02dce-d11d-eecb-c23d-f9fcd0361eb1.png)


**6. アプリケーションの接続先を新しいレプリケーションに変更する**
フェイルオーバーした新しいMySQL5.7のレプリケーショングループにアプリケーションの接続先を変更してメンテナンスモードを解除します。

**7. 不要となった古いMySQL5.6のレプリケーショングループを削除する**

一時的にMySQLインスタンスの台数が2倍になることと、手順がin-placeに比べてやや複雑なこともありますが、メンテナンス時間はフェイルオーバーにかかる時間となるため、in-placeと比べてメンテナンス時間を短くすることができます。


## まとめ
Webアプリケーションの場合、DBのパフォーマンスはレスポンスタイムなどに直結しやすいため、パフォーマンステストは入念に行っておいた方がいいです。特に本番のワークロードに近い状態でないと再現しないパフォーマンスが低下しないクエリーがあったります。
システム的に許されるならリードレプリカ1台だけMySQL5.7にアップグレードしたものを追加してパフォーマンスを見たりする手段をとってもいいかもしれません。