技術本部 サービスリライアビリティグループ(SRG)の長谷川 @rarirureluis です。
#SRG(Service Reliability Group)は、主に弊社メディアサービスのインフラ周りを横断的にサポートしており、既存サービスの改善や新規立ち上げ、OSS貢献などを行っているグループです。

また Amazon Aurora MySQL(以下:Aurora MySQL)の話です。何でこんなに Aurora MySQL に関する記事ばっか書いてるのか僕も分かりません。

前回の Aurora MySQL のアップグレード方法のベストプラクティスはこちらです。
RDS Graviton2 に少ないリスクで切り替える方法を考えてみる【アップグレード編】 | CyberAgent Developers Blog

今回はバックアップについてです。

そのクラスター、間違ったクエリ流したときに直前まで戻せますか?

例えばテーブルを間違えて DROP してしまったとき、DROP する直前までの状態に戻すことはできますか?
もしくは直前まで戻す方法はドキュメント化されていますか?

これは AWS などのパブリッククラウドに限った話ではないのですが、フルバックアップだけを取っていてもある特定のクエリまで戻すことはできません。
そこで必要になるのがバイナリログです。

最新のフルバックアップと、そのバックアップ以降に取られたバイナリログさえあれば、特定のクエリまで復元することができます。

 

バックトラックと、ポイントインタイムリカバリ

Aurora MySQL には復元に関する機能に バックトラックポイントインタイムリカバリ の2つ機能があります。

これら2つの機能は特定のクエリの時点で復元するのではなく、日時指定で復元する機能です。
そのため間違ったクエリの直前まで戻すといったことはできません。

バックトラック

バックトラックは指定した時間までクラスターをそのまま「巻き戻す」機能です。

バックトラックは最大72時間前まで巻き戻すことができ、5.6.10a もしくは 2.06 以降のバージョンで利用可能です。
バックトラックを有効にしてクラスターを作成する必要があり、既存のクラスターを有効化することはできません。

また、バックトラックを利用するのに $0.014 / 変更レコード 100万件が発生します。
これが見積もれない場合、Web 上でクラスターを作成(変更)する際に今までのワークロードからある程度の料金を表示してくれます。

t3.small で sysbench を流し 24時間のターゲットバックトラックウィンドウを設定した場合

Aurora DB クラスターのバックトラック – Amazon Aurora

ポイントインタイムリカバリ

ポイントインタイムリカバリはバックアップ保持期間内の特定の時点のクラスターを別のクラスターとして作成する機能です。
バックトラックとの差は、既存のクラスターか、新たに作るかの差です。

特定の時点への DB クラスターの復元 – Amazon Aurora

 

バイナリログの必要性

バイナリログは特定のクエリの直前まで復元する場合は必須になります。

Aurora MySQL はデフォルトでバイナリログは保存されません。

もし有効化していない場合は、間違ったクエリを発行した日時以前の最新のスナップショット、もしくはバックトラック、ポイントインタイムリカバリから復元するしか方法がありません。(間違ったクエリの直前まで戻すことができない)

バイナリログを有効化している場合、デフォルトでは 24時間の保存期限があります。

バイナリログの有効化はこちらをご覧ください。
MySQL を実行する Amazon Aurora インスタンスのバイナリログを有効にする

binlog_format は MySQL 5.7 からデフォルトで ROW になったため、ROW が良いと思います。一番扱いやすいです。

 

バイナリログを有効化すると書き込みのパフォーマンスが落ちるらしい

公式ドキュメントにも記載されています。

注: Aurora MySQL 互換 DB クラスターでバイナリログを有効にすると、パフォーマンスに以下の影響が生じます。

  • 追加の書き込みオーバーヘッドが発生する (必要な場合にのみ有効にしてください)
  • バイナリログのリカバリプロセスにより、再起動時のエンジンの起動時間が長くなる

Amazon Aurora MySQL 互換 DB クラスターのバイナリログの保持期間を長くする

 

ベンチマーク

db.r6g.large(2.09.2) に対して sysbench で oltp_write_only を流してみます。コマンドは下記の通り 5分間を 5回実行した平均の TPS を計測します。

$ for i in {1..5}; do /usr/local/bin/sysbench --db-driver=mysql --mysql-host= --mysql-user= --mysql-password='' --mysql-db=benchmark oltp_write_only --threads=64 --time=300 run

バイナリログの ON_OFF による書き込みパフォーマンスの差 2.09.2

一般的にバイナリログが有効になるとパフォーマンスは劣化しますが、それでも OFF に対して 96.5%という結果になりました。

5/25 にリリースされたエンジンバージョン 2.10.0 では比較してみるとバイナリログが有効と無効で差がない結果が得られました。

バイナリログの ON_OFF による書き込みパフォーマンスの差

2.10.0 以前を使っていて 3.5% の性能劣化を無視できない場合は 2.0.0 へのアップグレードをオススメします。
アップグレードの方法を比較した記事をアップしているのでよろしければご覧ください。
RDS Graviton2 に少ないリスクで切り替える方法を考えてみる【アップグレード編】 | CyberAgent Developers Blog

 

バックアップと、バイナリログの保持期間

クラスターの設定を見てみると「バックアップの保持期間」という項目があります。
この保持期間でポイントインタイムリカバリを実行できる期間が決まります。(バックトラックはまた別の設定にあります。> ターゲットバックトラックウィンドウ)

例えば保持期間がデフォルトの 1日間で、間違ったクエリを流したことに気づいたのが 3日後だった場合、バックアップ(ポイントインタイムリカバリ)で戻せる最も古い日時でも既に間違ったクエリが適用されている状態になってしまいます。

保持期間どうするかは構難しいところですが僕が見ているところでは 7日間が多いです。

バイナリログの保持期間はバイナリログを有効にするとデフォルトで 24時間となります。

この保持期間も間違ったクエリを流したことに気づいたのが 3日後だった場合:

  • 現在運用中のクラスターにあるバイナリログは 1日分しかない
  • ポイントインタイムリカバリまたは、バックトラックから 3日前の状態に戻す
  • 3日前から現在(- 1日)までのバイナリログが存在せず

という状況になり、戻せるのが 3日前となり間違ったクエリをスキップして最新の状態まで戻すことができません。

じゃあどうすればいいかと言われればバックアップの保持期間も、バイナリログの保持期間も 7日間としておけばとりあえずは安心ではないでしょうか。

 

バイナリログの保持期間

確認

mysql> call mysql.rds_show_configuration;
+------------------------+-------+------------------------------------------------------------------------------------------------------+
| name | value | description |
+------------------------+-------+------------------------------------------------------------------------------------------------------+
| binlog retention hours | NULL | binlog retention hours specifies the duration in hours before binary logs are automatically deleted. |
+------------------------+-------+------------------------------------------------------------------------------------------------------+

設定

mysql> call mysql.rds_set_configuration('binlog retention hours', 168);
Query OK, 0 rows affected (0.05 sec)

 

 

実際にやってみる

DB が d1, d2 の2つがあり、d1.t1 には下記のレコードが入ってる状態で d2 は DB のみが作成されている状況です。

Aurora MySQL のバックアップは本当にそれでいいのだろうか?

障害が起きました

としましょう。

Aさんがサービスクリティカルなテーブル t1 を間違えて DROP してしまいました。

mysql> DROP TABLE d1.t1;

順に追って解決していきます。

 

1. まずは DB への書き込みを停止する

間違ったクエリの直前に戻すため、できるだけ更新されないように(ユーザー影響を小さくするため)緊急メンテナンスを行い、DB への更新が走らないようにします。

強制的にアプリからの書き込みを停止するには権限を剥奪するのもアリですがあまりオススメしません。
理由は 3. で説明します。

mysql> REVOKE INSERT,UPDATE ON d1.* FROM app@'%'

 

2. バイナリログを救出する

バイナリログをローカルに保存するには下記のスクリプトを参考にしてみてください。

 

3. クラスターを復元する

最新のスナップショット、またはバックトラック、もしくはポイントインタイムリカバリで間違ったクエリ以前の時間で最新のを使用してクラスターを復元します。

DB への書き込みを禁止にした際、権限の剥奪の方法で行った場合はバックトラックが終わると権限剥奪前の状態に戻ってしまうためアプリやバッチを止めていない場合更新されてしまいます。そのためどこから DB が参照されよく分からない状態の場合はバックトラックではなく、ポイントインタイムリカバリで復元するのが良いかと思います。(バックトラックはそのクラスターをもとに戻す、ポイントインタイムリカバリは新しくクラスターが作成されるためです)

バックトラックについてはこちらが参考になります。
Aurora MySQL のバックトラック機能を使ってDROP DATABASEをなかったことにしてみた | DevelopersIO
Aurora MySQL 5.7 でポイントインタイムリカバリをやってみた | DevelopersIO

 

4. 復元したクラスターの現在のポジションを確認する

Aurora MySQL のバックアップは本当にそれでいいのだろうか?

バックトラックから復元した DB には d2 はない状態です。

 

mysql> show binary logs;
+----------------------------+-----------+
| Log_name | File_size |
+----------------------------+-----------+
| mysql-bin-changelog.000002 | 154 |
| mysql-bin-changelog.000003 | 154 |
+----------------------------+-----------+
2 rows in set (0.01 sec)

show binary logs で復元されたクラスターの現在のバイナリログのポジションを確認しておきます。

 

5. 救出したバイナリログから間違ったクエリのポジションを調べる

2. バイナリログを救出する で救出したバイナリログを調べます。

$ mysqlbinlog --base64-output=DECODE-ROWS -vv mysql-bin-changelog.000004

Aurora MySQL のバックアップは本当にそれでいいのだろうか?

4. 復元したクラスターの現在のポジションを確認するmysql-bin-changelog.000003 154 まで適用されていることが確認できています。
抽出したバイナリログを見てみると 372 で間違ったクエリを流していることが分かります。

 

6. 救出したバイナリログから復元を行う

下記のコマンドを実行し、4. 復元したクラスターの現在のポジションを確認する のポジションから、5. 救出したバイナリログから間違ったクエリのポジションを調べる のポジション間のバイナリログを作成します。

$ mysqlbinlog --start-position=154 --stop-position=372 --skip-gtids -vv --base64-output=DECODE-ROWS mysql-bin-changelog.000004 > recovery.sql

 

復元したバイナリログの中身を確認し、間違ったクエリが反映されていないことを確認します。

Aurora MySQL のバックアップは本当にそれでいいのだろうか?

RDS では一部権限の問題で上記の作成したバイナリログは実行できません。
今回は下記の箇所を削除する必要がありました。

Aurora MySQL のバックアップは本当にそれでいいのだろうか?

下記のコマンドで実際に適用します。

$ mysql -h hoge.cluster-random.ap-northeast-1.rds.amazonaws.com -uroot -p < recovery.sql

 

適用後、確認してみると消してしまった d1.t1 テーブルと、d2 データベースがあるのが確認できます。

Aurora MySQL のバックアップは本当にそれでいいのだろうか?

まとめ

今回も Aurora MySQL に関する記事でした。

バイナリログから復元する一連の流れを予習しておくと必要になったときに焦らずに対応することができるのでみなさんもぜひ!

(コマンドが <code></code> だったり画像だったりして見にくかったかと思いますが、セキュリティに引っかかっちゃうのでこんな感じになってしまいました。)