もしも明日DB障害が発生したら、あなたはどのように復旧しますか？ ~RDSとS3を連携し、PITRで損失したデータを復元するプラクティスについて~

## 概要

データベースの論理障害が発生した際に、過去時点のデータをインポートし復旧する手順について説明します。

具体的には、ポイント・イン・タイム・リカバリ（PITR）で、任意の時刻のRDSクラスタを作成し、データをS3へエクスポート、その後S3から再度インポートすることで任意のテーブル・スキーマを過去の状態に復元します。

## 手順の概要

① ポイント・イン・タイム・リカバリ（PITR）で障害発生前のRDSクラスターを作成

② RDS が S3 にアクセスするための IAM ロールを作成

③ DBクラスターパラメータグループの設定

④ **`SELECT INTO OUTFILE S3`**/**`LOAD DATA FROM S3`** でデータを復旧する

## 手順の説明
### ① PITRを使用してデータを復元する

ポイント・イン・タイム・リカバリ（PITR）でRDSクラスターを過去の時点に復元するプロセスを説明します。

**PITRとは？**
PITRでは、リソースの状態を指定した過去の時刻に巻き戻すことができます。

今回はこの機能を採用し、RDSリソースを障害発生以前の状態に巻き戻し、RDSクラスターを作成します。

> 一部のリソースでは、 スナップショットバックアップに加えて、継続的なバックアップと point-in-time リカバリ (PITR) AWS Backup をサポートしています。**継続的バックアップ** では、精度の 1 秒 (最大 35 日間) 以内に、 が AWS Backupサポートするリソースを選択した特定の時間に巻き戻すことで復元できます。継続的なバックアップは、最初にリソースのフルバックアップを作成し、次にリソースのトランザクションログを定期的にバックアップすることによって機能します。PITR の復元は、フルバックアップにアクセスし、トランザクションログを復旧 AWS Backup するよう指示した時点まで再生することで機能します。
> 

参照: [継続的なバックアップと point-in-time 復元 (PITR)](https://docs.aws.amazon.com/ja_jp/aws-backup/latest/devguide/point-in-time-recovery.html)

### **なぜPITRを選んだのか？**

Amazon RDSのMySQLでクラスターを特定の時点に復元する方法として、いくつかの選択肢がありますが、なぜPITRが最適だと考えたのかを説明します。

**選択肢1: バックトラック**

クラスターの状態を特定の時点に戻すことができる機能です。

まず、この機能はAurora MySQLに限定されており、クラスター作成時に設定を有効化しなければ使用できません。

また、バックトラックは特定のテーブル・スキーマの状態だけでなく、DB全体の状態を巻き戻してしまうため、影響範囲が大きく選択肢から外しました。

**選択肢2: スナップショット**

RDSクラスターのスナップショットを使用して、新たなクラスターを作成します。

PITRと比較して、目立つデメリットはありませんが、クラスター作成に時間がかかり、操作手順も少し多いため、今回においてはPITRよりスナップショットを使用するメリットが無かったので選択肢から外しました。

**選択肢3: その他外部クライアント**

細かいカスタマイズが可能である点は優秀ですが、今回の場合AWS内で完結する復元機能が提供されている状況で、外部ツールを採用する必要はなく余分なリスクや手間が発生すると考え、選択肢から外しました。


以上の内容から作業手順のシンプルさ、コスト効率、AWSの環境内で完結するセキュリティの高さが優れていると判断して、PITRを復元方法として選択しました。

**PITRでの過去時点のRDSクラスターを作成する手順**

マネジメントコンソール上での作業手順と、CLIによる手順の２種類を紹介します

- マネジメントコンソール上での作業手順

    1. 復元したいRDSクラスターを選択し、「アクション」メニューから「特定時点への復元」を選択します
    2. 復元する時刻を指定し、復元プロセスを開始します

    ![スクリーンショット 2023-09-05 14.51.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437097/9a1a147c-b306-b823-b815-d30ee4d91a24.png)


- CLI上での作業手順

  1. 最初に以下のコマンドで、復元可能な最も古い時間を確認します

        ```
        aws rds describe-db-clusters --db-cluster-identifier target-cluster-name --query 'DBClusters[0].EarliestRestorableTime'
        ```    
        
  2. 確認した時間から特定の時点への復元を行うためのCLIコマンドを実行します。以下はその例です
  
        ```
        aws rds restore-db-cluster-to-point-in-time --source-db-cluster-identifier source-cluster-name --target-db-cluster-identifier target-cluster-name --restore-to-time "2023-01-01T15:00:00Z"
        ```

### ② RDS が S3 にアクセスするための IAM ロールを作成

Amazon AuroraインスタンスがAmazon S3バケットとオブジェクトを効果的に操作できるように、適切なIAMロールを設定します。

**必要なIAMポリシー要件**

AuroraインスタンスがS3でデータをロードおよびアンロードする際に必要な権限は以下の通りです

- **LOAD DATA FROM S3**: データをS3からRDSにロードする際には、バケットのリスト表示（ListBucket）とオブジェクトの取得（GetObject）権限が必要です。
- **SELECT INTO OUTFILE S3**: RDSからS3へデータをアンロードする際には、バケットのリスト表示（ListBucket）、マルチパートアップロードの中止（AbortMultipartUpload）、マルチパートアップロードのリスト表示（ListMultipartUploads）、パーツのアップロード（UploadParts）、オブジェクトの追加（PutObject）、バケット位置情報の取得（GetBucketLocation）権限が必要です。


これらの権限を個別に設定する代わりに、**AmazonS3FullAccess**ポリシーをロールにアタッチして全権限を付与することも可能ですが、セキュリティのベストプラクティスとしては、必要な操作のみを許可する設定がオススメです。

IAMロールが作成出来たら、データのロード・アンロードしたいRDSのクラスターの`追加するIAMロールの選択`から作成したIAMロールを選択してロールの追加を実行。ステータスがアクティブになったら無事完了です。

### ③ DBクラスターパラメータグループの設定

Amazon RDSクラスターで使用されるパラメータグループにIAMロールのARNを設定する方法を説明します。

この設定により、クラスターがAmazon S3と連携してデータのエクスポートやインポートを行う際に、指定されたIAMロールを使用できるようになります。

**IAMロールのARN設定**

- 使用中のパラメータグループを選択し、`aws_default_s3_role`パラメータを探します
- `aws_default_s3_role`の値に、先に作成したIAMロールのARNを入力します

`aws_default_s3_role`のパラメータは動的であるため、RDSクラスターの再起動は必要ありません。

> 動的パラメータを変更すると、デフォルトでは、パラメータの変更は直ちに有効になり、再起動は不要です。AWS Management Console を使用して DB クラスターのパラメータ値を変更するときには、常に動的パラメータ向けの `ApplyMethod` として `immediate` を使用します。パラメータの変更を、関連する DB クラスターが再起動されるまで延期するには、AWS CLI または RDS API を使用します。パラメータを変更する場合は、`ApplyMethod` を `pending-reboot` に設定します。
> 

参照: [静的および動的 DB クラスターパラメータ](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/parameter-groups-overview.html#parameter-groups-overview.maz)

### ④ **`SELECT INTO OUTFILE S3`**/**`LOAD DATA FROM S3`** でデータを復旧する

Amazon RDSからAmazon S3へデータをエクスポートし、その後S3からRDSに再度データをロードすることで、任意のテーブル・スキーマを過去の状態に復元します。

**1. RDSからS3へデータのエクスポート**

**`SELECT INTO OUTFILE S3`** コマンドを用いて、特定のクエリ結果を直接S3にエクスポートできます。以下は構文の具体例です。

```
SELECT * INTO OUTFILE S3 's3://bucket_name/path/file_name.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\n' FROM table_name
```

※BY以降に指定している内容について、フィールドはコンマで区切られ、各フィールドはダブルクォートで囲まれ、各レコードは新しい行で終了することを意味します。

もし、where句を使用したい場合は以下の通り指定できます。

```
SELECT * INTO OUTFILE S3 's3-ap-northeast-1://bucket_name/file_name.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\n' FROM table_name WHERE created_at > '2020-12-31';
```

上記の実行が完了したら指定したS3バケットを確認しに行き、CSVファイルが作成されていたら成功です。

**2. S3からRDSへデータのインサート**

`LOAD DATA FROM S3` コマンドを使用して、S3に保存されたファイルからデータをRDSのテーブル・スキーマに直接インポートします。以下は構文の具体例です。

```
LOAD DATA FROM S3 's3://bucket_name/path/file_name' INTO TABLE table_name FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\n';
```

`LOAD DATA FROM S3`に成功すると`aurora_s3_load_history` コマンドで取り込み結果が出力されます。正しいファイルが取り込まれたか確認しましょう。

```
 SELECT * FROM mysql.aurora_s3_load_history WHERE load_prefix = 's3://bucket_name/path/file_name
 
+--------------------------------------------------------------------------------------+----------------------------+----------------+--------------+---------------------+
| load_prefix                                                                          | file_name                  | version_number | bytes_loaded | load_timestamp      |
+--------------------------------------------------------------------------------------+----------------------------+----------------+--------------+---------------------+
| s3://bucket_name/paht/file_name                                                      | file_name                  |                |          942 | 2024-01-01 13:0:00  |
+--------------------------------------------------------------------------------------+----------------------------+----------------+--------------+---------------------+
```

**権限周りのエラー**

もしエクスポートまたはインポートの際に以下のようなエラーが発生した場合、IAMロールの設定に問題がある可能性があります。

```sql
ERROR 1871 (HY000): S3 API returned error: Missing Credentials: Cannot instantiate S3 Client
```

このエラーは、RDSインスタンスに適切なIAMロールがアタッチされていないか、IAMロールに必要なS3アクセス権限が付与されていないことを示しています。

IAMロールに **`AmazonS3FullAccess`** または必要な最小限の権限を確認し、再度ロールをアタッチする必要があります。

### Q&A

**Q. インポートの途中でエラーになった場合はリソースに変更が反映されるのか？**

A. トランザクション上でインポートが実行されるため、エラーが発生した場合はロールバックされ、リソースはインポート前の状態に戻ります。

---

**Q. 特殊文字がレコードに含まれる場合は問題ないか？**

A.varcharカラムに使用する分には問題ありません。

しかし、特殊文字によっては入力と結果が異なる場合があるため、考慮して検討してください。

---


**Q. データ削除以外でデータを誤って更新しまった場合にどうすべきか(ex. テーブルの主キーが被っている場合など)**

A. 主キーが重複している場合は、以下のようなエラーが返却されます。
```
ERROR 1062 (23000): Duplicate entry '123u9' for key 'table_name.PRIMARY’
```

この場合、新しいkeyを設定するか、更新前のテーブル状態をPITR（Point-In-Time Recovery）を使用して復元し、必要なデータを再インサートしてください。

---


**Q. nullと空文字はどう区別されるのか**

A. nullは`\N`で判別されます。

nullを入れたい場合は`\N` を記入、空文字や0を入れたい場合は`””` や`”0”`を記入するようにしてください



## あとがき
ここまでお読みいただきありがとうございます。

今回は、データベースの論理障害が発生した際に、過去時点のデータをインポートし復旧する手順について説明しました。

障害は必ず発生するものですし、いかにそれに対し備えるかが重要なポイントだと考えています。この記事を見てくださった方にとってなにかの参考になれば光栄です。