技術本部 サービスリライアビリティグループ(SRG)の長谷川 @rarirureluis です。
#SRG(Service Reliability Group)は、主に弊社メディアサービスのインフラ周りを横断的にサポートしており、既存サービスの改善や新規立ち上げ、OSS貢献などを行っているグループです。
AWS Graviton2…?
AWS Graviton2(以下 Graviton2) をご存知でしょうか?ご存知でない方はこちらをご覧ください。
Amazon EC2 を Arm に切り替えたら幸せなことしかありませんでした
そんな、Graviton2 が 3/12 に Amazon Aurora(以下;Aurora)で利用できるようになりました。
新しい Graviton2 インスタンスを使用して Amazon Aurora で最大 35% 優れた料金/パフォーマンスを実現
確かに Graviton2 の Aurora が速い
このスコアは Aurora MySQL 2.0.92 の r5.large と r6g.large に対して、threads=64 で sysbench OLTP(Read & Write)を流した結果です。
TLS接続の有無で2つがありますが気にしないでいただいて大丈夫です。
r5.large より、Graviton2 な r6g.large の方がスコア上は良いようです。
しかもコストは r6g.large のほうが安いです。(r5.large: $0.35/h, r6g.large:$0.313/h 東京 オンデマンド 2021/05/10 現在)
Graviton2 な Aurora はバージョンに注意
Graviton2 に対応した Aurora バージョンは MySQL では 2.09.1(MySQL 5.7)、
PostgreSQL は 3.4(PostgreSQL 11.9)以降となります。
既存の MySQL のバージョンは?
以後、 Aurora MySQL についてのお話になります。
まずはいま利用している Aurora MySQL のバージョンを知る必要があります。
なぜなら前述した通り、Graviton2 な MySQL は 2.09.1(MySQL 5.7)以降が必要になります。
MySQL 5.6 -> MySQL 5.7 へアップグレード
もしいまお使いの MySQL が 5.6 の場合、5.7 へアップデートするには 5つの方法があります。
- インプレースアップグレード
- レプリケーション→スイッチオーバー
- スナップショット→リストア
- AWS DMSを利用し、2. の内容を簡略化したもの
- RDS Proxy を利用しエンドポイントを切り替える
- その他
アップグレードを検証するために、Aurora 1.22.2 r5.2xlarge のインスタンスが1つ、1.64TB ストレージを持つクラスターを作成しました。
(レコード数の把握は面倒なのでここでは割愛します)
1. インプレースアップグレード
Web GUI もしくは API を使ってかんたんに 5.6 -> 5.7 へのアップグレードができます。
ただし、アップグレード中はクラスターエンドポイントに接続できないためダウンタイムが発生します。
そしてアップグレード元のクラスターが 1.22.3 またはそれ以上でない場合、インプレースアップグレードプロセスは最初に 1.22.3 へのアップグレードが行われるためインプレースアップグレードにかかる時間が増えます。
実際にクラスターを複製してインプレースアップグレードにかかる時間を計測することで、ビジネス要件にあうかどうかを判断できます。
今回のクラスターでインプレースアップグレードを試したところ、10分程度で終了しました。
試しにインスタンスサイズを下げて台数を増やして、インプレースアップグレードにかかる時間に影響するか調べてみました。
r5.2xlarge x1 -> r5.large x3 の構成となります。
結果は 13分程度となり微増となりました。そこまで大きな影響はないようです。
ただし本番移行後に元のバージョンに戻したい場合、クラスターのエンジンバージョンを現在のものより下には変更できないため注意が必要です。
そのため前もってステージング環境でテストするするのをオススメします。
もし切り戻しを視野に入れる場合は、アップグレード後の DB でバイナリログを有効にしておくのを忘れないようにしてください。
大まかな切り戻し手順は下記の通りです。
- アップグレード時に作成された前バージョンのスナップショットからクラスターを復元
- アップグレードした DB に書き込みが行われないようにする
- アップグレードした DB にあるバイナリログを使って、復元した DB に適用
- アプリケーションの向き先を復元した DB に戻す
RDS ではバイナリログに含まれている SET @@session.pseudo_thread_id=999999999/*!*/;
が権限の問題で実行できないため、適用する際にはこの行を削除する必要があります。
また、Percona が提供している binlog2sql を利用するとバイナリログからクエリーのみを抽出してくれるため、上記の問題を気にせずに適用するのがかんたんになります。
$ ./binlog2sql/binlog2sql.py -h -u -p --start-file mysql-bin-changelog.000002 --stop-file mysql-bin-changelog.000120 | cut -f1 -d"#" | grep -v 'USE b' | mysql -h -u -p
バイナリログの適用ではなく、レプリケーションによる切り戻しも可能です。
アップグレードした DB をソースとして、前バージョンの DB をスナップショットから復元しレプリケーションを貼るのも方法の1つです。
インプレースアップグレードに関しては下記の記事が参考になります。
Upgrading the major version of an Aurora MySQL DB cluster from 1.x to 2.x – Amazon Aurora
Amazon Aurora MySQLがインプレイスアップグレード(5.6-→5.7)できるようになりました! | DevelopersIO
2. レプリケーション → スイッチオーバー
blue-green deployment の方法で最後にアプリケーションの向き先を変える方法です。
大まかに説明すると
- 現在動いている Aurora を Blue とし、Blue からクローンし、Green 環境を作ります
- Green(older) をインプレースアップグレードをする
- Blue とインプレースアップグレードした Green(newer) でレプリケーションを貼る
- 最後にアプリケーションの向き先をインプレースアップグレードしたクラスターに変えます
この方法に関しても 1. インプレースアップグレード 同様にダウンタイムが発生します。
(アプリケーションの DB の向き先を変えるタイミングで、アプリケーションが Blue と Green に同時に書き込まれないようにするため)
手順が多く、懸念事項も多いですがインプレースアップグレードよりもダウンタイムははるかに小さくなるはずです。
公式ドキュメントでは下記に公開されています。
Performing major version upgrades for Amazon Aurora MySQL with minimum downtime | AWS Database Blog
3. スナップショット → リストア
読んで字のごとくですが、インプレースアップグレードもスナップショットが作成されるのでこの方法を採用する理由はあまりないかもしれません。
しいて言えば、Aurora ではない RDS for MySQL をお使いの方はスナップショットを作成し、そこから Aurora MySQL へ変更しつつクラスタが作れたりします。
4. DMS
AWS DMS(以下:DMS)を利用し、2. のレプリケーションの部分を DMS に任せる方法です。
画像引用:AWS DMS を使用して Amazon Aurora for PostgreSQL のメジャーバージョンへのアップグレードを最小ダウンタイムで実現する
この画像では PostgreSQL の説明になりますが、MySQL でも同じようにすることができます。
この方法でも 2. と同様に、アプリケーションが見る DB の向き先を変更するタイミングで、ソースの MySQL への書き込みを禁止するためダウンタイムが発生しますが、インプレースアップグレードによる方法よりはダウンタイムは小さくなるはずです。
5. RDS Proxy
この記事を書いてるときにふと Amazon RDS Proxy(以下:RDS Proxy)の存在を思い出したのでこれを使ってうまいことできないか考えました。
Amazon RDS Proxy による接続の管理 – Amazon Relational Database Service
方法としては 2. のレプリケーションを行い、アプリケーションは RDS Proxy 越しに接続をし切り替えのタイミングで、RDS Proxy のターゲットグループを更新する方法です。
切り替えるタイミングで、新旧同時に書き込みが行われないことを確認するために EC2 を建てて下記のワンライナーを実行してみます。
$ while true; do date "+%H:%M:%S" && mysql --defaults-extra-file=/root/.my.cnf -e 'select @@hostname' | awk NR==2; done
ip-172-23-2-198
08:13:57
ip-172-23-2-59
08:13:58
ip-172-23-2-59
08:13:58
ip-172-23-2-198
08:13:58
ip-172-23-2-198
08:13:58
ip-172-23-2-59
08:13:58
ip-172-23-2-59
08:13:58
ip-172-23-2-59
08:13:58
ip-172-23-2-59
08:13:58
ip-172-23-2-198
08:13:58
ip-172-23-2-198
08:13:58
ip-172-23-2-59
08:13:58
ip-172-23-2-59
08:13:58
ERROR 2027 (HY000) at line 1: Malformed packet
08:13:58
ip-172-23-2-59
08:13:58
ip-172-23-2-59
08:13:58
ip-172-23-2-59
08:13:58
ip-172-23-2-59
08:13:58
ip-172-23-2-59
08:13:59
ip-172-23-2-59
08:13:59
172.23.2.198
が旧、172.23.2.59
がアップグレードされた新しい DB です。
結果を見てもらうと分かる通り、ふらついている部分があるものの切り替わるまで 2秒程度に見えます。
が、「お、切り替わったな」と思ったら ERROR 2013 (HY000): Lost connection to MySQL server at ‘reading initial communication packet’, system error: 2 エラーが続き、3分後に接続できるようになりました。
6. その他
今まで紹介した方法以外にも、いくつかアップグレード方法があります。
例えば
- ProxySQL を利用する
- DNS を使って、CNAME レコードを書き換えて行う方法
- リードは、MySQL 5.7 の読み込み専用エンドポイント、ライトは Route53 のプライベートホストゾーンにて、mysql-rw.infra.local CNAME MySQL 5.6 のソースエンドポイント にしておく
- Route53 にて TTL 1 にすることで 5. RDS Proxy にて紹介されたようにレコードが浮つくことなく一発で切り替わります。
- DeNA さんの記事で詳しく説明されていますのでご参考ください。
があります。
ProxySQL を利用する
ProxySQL はハイパフォーマンスな SQL プロキシで、本家 MySQL 以外にも Percona Server や、MariaDB、Galera Cluster にも対応しています。
ProxySQL – A High Performance Open Source MySQL Proxy
手順としては下記の通りです。
- 既存のクラスタ(Blue)からクローンし、インプレースアップグレードする(Green)
- Green と Blue でレプリケーションを貼る
- ProxySQL をセットアップし、モニタリングを停止しておく
- アプリは ProxySQL を通して使うように改修する
- アプリが使うユーザー名をリネーム(もしくは権限を剥奪)し、書き込みを停止させる
- RDS MySQL では SET read_only = 1 や、SET GLOBAL offline_mode= 1 も権限によって実行することができないため、この方法を使います
REVOKE INSERT,UPDATE ON d1.* FROM app@'%';
- このタイミングで書き込みができなくなります。
- レプリカに上記のクエリが伝搬されてしまっているので、レプリカ(次期ソース、ここでは MySQL 5.7)にてユーザーを復活させる
- SET sql_log_bin = 0 が権限により(ry
GRANT INSERT,UPDATE ON d1.* TO app@'%';
- レプリの Exec_Master_Log_Pos と、ソースの Position に差がないことを確認
- ProxySQL の mysql_servers の MySQL 5.6 hostgroup_id = 0 (Blue) を削除し、MySQL 5.7 hostgroup_id = 0 の weight = 1 にする
この方法であれば、アプリを停止する必要はなくなるので参照はエラーを出さずに切り替えることができます。
2. ~ 5. の手順をスクリプト化しておけば、ダウンタイムをもっと小さくできます。
(この方法が必要になった場面がないためスクリプトを公開できないのですが、もし作成したら公開します)
まとめ
テストをしてみてダウンタイムが許容できるのであればインプレースアップグレードが最も手軽でお金もかかりません。
ただし前述したように本番で移行した後に問題があって切り戻しがしたい場合は、ちょっと大変かもしれません。
インプレースアップグレードによるダウンタイムが許容できないけど、できるだけ簡単にしたい場合は 2. レプリケーション→スイッチオーバー
ちょっと複雑になってもいいから参照だけでもエラーにはなりたくない場合は 6. その他
また、DMS の料金はインスタンス、ストレージ、ネットワークがかかります。詳しい料金についてはこちらをご覧ください。
料金 – AWS Database Migration Service |AWS
3.で説明した「スナップショットからリストアによるアップグレード」は、この方法をあえて選ぶ理由が見つかりませんでした。
(もし、この方法のメリット等がありましたら Twitter 等で教えていただけると幸いです)
サービスをメンテナンス状態に入れることが可能な状況であれば「1. インプレースアップグレード」が個人的にはおすすめです。
オンプレミス環境の MySQL は、レプリカをアップグレードしたあとに MHA や、Orchestrator でフェイルオーバーと、alias IP の付け替え等を同時に行う方法を取ると数秒以内に完了することができ、ほぼダウンタイム無しで実現できます。
(一瞬エラーレートは上がるかもしれませんが、コネクション周りの設定をきちんとしていたらそこまでの影響はないかと思います。)
RDS でも同じようなことができたらなあと思っています。
今回は Graviton2 な RDS を導入する前のアップグレード編でした。
次回に続きます(未定!)