もしも明日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クラスターを作成します。
なぜPITRを選んだのか?
Amazon RDSのMySQLでクラスターを特定の時点に復元する方法として、いくつかの選択肢がありますが、なぜPITRが最適だと考えたのかを説明します。
選択肢1: バックトラック
クラスターの状態を特定の時点に戻すことができる機能です。
まず、この機能はAurora MySQLに限定されており、クラスター作成時に設定を有効化しなければ使用できません。
また、バックトラックは特定のテーブル・スキーマの状態だけでなく、DB全体の状態を巻き戻してしまうため、影響範囲が大きく選択肢から外しました。
選択肢2: スナップショット
RDSクラスターのスナップショットを使用して、新たなクラスターを作成します。
PITRと比較して、目立つデメリットはありませんが、クラスター作成に時間がかかり、操作手順も少し多いため、今回においてはPITRよりスナップショットを使用するメリットが無かったので選択肢から外しました。
選択肢3: その他外部クライアント
細かいカスタマイズが可能である点は優秀ですが、今回の場合AWS内で完結する復元機能が提供されている状況で、外部ツールを採用する必要はなく余分なリスクや手間が発生すると考え、選択肢から外しました。
以上の内容から作業手順のシンプルさ、コスト効率、AWSの環境内で完結するセキュリティの高さが優れていると判断して、PITRを復元方法として選択しました。
PITRでの過去時点のRDSクラスターを作成する手順
マネジメントコンソール上での作業手順と、CLIによる手順の2種類を紹介します
マネジメントコンソール上での作業手順
復元したいRDSクラスターを選択し、「アクション」メニューから「特定時点への復元」を選択します
復元する時刻を指定し、復元プロセスを開始します
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クラスターの再起動は必要ありません。
④ 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ロールの設定に問題がある可能性があります。
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”を記入するようにしてください
あとがき
ここまでお読みいただきありがとうございます。
今回は、データベースの論理障害が発生した際に、過去時点のデータをインポートし復旧する手順について説明しました。
障害は必ず発生するものですし、いかにそれに対し備えるかが重要なポイントだと考えています。この記事を見てくださった方にとってなにかの参考になれば光栄です。