📎

export-table-to-point-in-timeでDynamoDBのバックアップを行う(&ついでにリストアの手順も解説)

2021/08/02に公開

はじめに

複数のDynamoDBテーブルの全Itemを コスパよく 取得したかったので、色々な方法がある([1])中からexport-table-to-point-in-time[2]を利用して実施することにしました。
初めてなので実際にやってみました。

結論

  • export-table-to-point-in-timeでDynamoDBテーブルの全データをJSON形式で出力できる
    • S3バケットへ出力できる
    • ただし、テーブルのポイントインタイムリカバリが有効になっている必要あり
  • list-exportsでバックアップの状況を確認できる
    • export-table-to-point-in-timeの完了までは時間がかかるので、list-exportsと組み合わせて「終わるまでポーリング」する使い方が便利
  • バックアップデータをDynamoDBのAWS SDKで putItem できる
    • ただし、バックアップデータは分割して保存されていることがあるので、事前にマージが必要

前提

  • CLIはV2系を利用します
  • jqを利用します
$ aws --version
aws-cli/2.1.1 Python/3.7.4 Darwin/19.6.0 exe/x86_64
$ jq --version
jq-1.6

やってみたこと

データ準備

テーブルを2つ作成しました。

マネジメントコンソール上から以下のようにテーブル名とキー設定だけ実施し、他はデフォルトのまま作成しています。

  • テーブル名:table1
    • パーティションキー:id
    • ソートキー:なし
  • テーブル名:table2
    • パーティションキー:id
    • ソートキー:なし

作成したテーブルにItemを入れておきます。
適当にidが1〜6のItemを作成しました。


その上で、後述のバックアップ機能を利用するため、ポイントインタイムリカバリの設定を有効化しています。
マネジメントコンソールからの設定手順は次のとおりです。

バックアップ先(S3バケット)作成

バックアップデータの格納先バケットを作成します。
ここでは hirobel-ddb-bkp というバケットを作成しました。

バックアップ取得

export-table-to-point-in-time[2:1]を利用してバックアップを取得をリクエストします。
レスポンスの ExportDescription.ExportArn がデータの格納先です。
リストア時に必要な情報になるので、ファイルへ出力しておくことにしました。
ちなみにレスポンス全体はこんな感じです。

{
    "ExportDescription": {
        "ExportArn": "arn:aws:dynamodb:REGION:ACCOUNT:table/tablename/export/01234567890abc-1234abc",
        "ExportStatus": "IN_PROGRESS",
        "StartTime": ...,
        "TableArn": ...,
        "TableId": ...,
        "ExportTime": ...,
        "ClientToken": ...,
        "S3Bucket": ...,
        "S3Prefix": ...,
        "S3SseAlgorithm": ...,
        "ExportFormat": ...,
    }
}

list-exports[3]を利用してバックアップの状況を確認できます。
export-table-to-point-in-timeによるバックアップでは、実際にデータが出力完了するまでにしばらく時間がかかるため、これを利用して状況をポーリングするような形にしました。

作ってみたコードは以下になります。

バックアップコード

backup.bash
#!/bin/bash

tables=("table1" "table2")

if [ -z "$EXPORT_S3_BUCKET" ]; then
  echo "EXPORT_S3_BUCKETが指定されていません。終了します。"
  exit 0
fi

for i in "${tables[@]}"
do
  aws dynamodb export-table-to-point-in-time \
      --table-arn arn:aws:dynamodb:ap-northeast-1:$(aws sts get-caller-identity | jq -r .Account):table/$i \
      --s3-bucket $EXPORT_S3_BUCKET \
      --export-format DYNAMODB_JSON | jq -r .ExportDescription.ExportArn | cut -d "/" -f 4 > $i-export-path      
done

for i in "${tables[@]}"
do
  while :
  do
    path=`cat $i-export-path`
    targetExportArn=arn:aws:dynamodb:ap-northeast-1:$(aws sts get-caller-identity | jq -r .Account):table/$i/export/$path
    status=`aws dynamodb list-exports | jq --arg targetExportArn $targetExportArn '.ExportSummaries[] | select(.ExportArn == $targetExportArn)' | jq -r .ExportStatus`
    if [ "$status" = "COMPLETED" ]; then
      echo "${i}のバックアップデータ出力は完了しました"
      break
    fi
    echo "${i}のバックアップデータ出力は未完了です。リトライします..."
    sleep 60
  done
done

これを実行してみるとこんな感じになります。

$ EXPORT_S3_BUCKET=katoaki-ddb-backup-test bash backup.bash
table1のバックアップデータ出力は未完了です。リトライします...
table1のバックアップデータ出力は未完了です。リトライします...
table1のバックアップデータ出力は未完了です。リトライします...
table1のバックアップデータ出力は未完了です。リトライします...
table1のバックアップデータ出力は未完了です。リトライします...
table1のバックアップデータ出力は完了しました
table2のバックアップデータ出力は完了しました

データを変更

後続のリストアの効果を検証するために、table1からid=1のデータを削除します。

リストア

バックアップしたデータはDDB-JSON形式でS3バケット上に保存されています。
読み込むには、オブジェクトをダウンロードしてテーブル毎に一つのファイルにマージし、マージしたファイルを読み込んでputItem[4]すればデータを元に戻すことができます。

pubItemを行う処理はこんな感じです。

restore.js
const AWS = require("aws-sdk");
AWS.config.update({ region: "ap-northeast-1" });
const dynamodb = new AWS.DynamoDB(); // バックアップのjsonがDDB-JSON形式なのでdocumentClientを使わない
fs = require("fs");

const tableName = process.env.TABLE_NAME; //e.g. table1
const readline = require("readline");
const filePath = process.argv[2];

const stream = fs.createReadStream(filePath, {
  encoding: "utf8",
});

const rl = readline.createInterface({
  input: stream,
});

let Items = [];

const main = async () => {
  for await (const line of rl) {
    Items.push(JSON.parse(line).Item);
  }

  await Promise.all(
    Items.map(async (item) => {
      const params = {
        TableName: tableName,
        Item: item,
      };
      try {
        await dynamodb.putItem(params).promise();
      } catch (e) {
        console.log(e);
        console.log("テーブルの更新処理でエラーが発生しました。終了します。");
      }
    })
  );
};

main();

pubItemを行う処理のインプットとして、読み込むファイルと読み込んだデータを格納するテーブルが必要になります。
そのため、こんな感じで実行してみました。

BACKUP_OBJECT_PATH=$(cat <テーブル名>-export-path)
mkdir -p backup
aws s3 cp s3://${EXPORT_S3_BUCKET}/AWSDynamoDB/${BACKUP_OBJECT_PATH}/data/ backup --recursive
gzip -d backup/*
cat backup/* > backup/merged.json

node restore.js <テーブル名> "backup/merged.json" 

リストアが行われるのでtable1は実行後にこんな感じになります

参考

脚注
  1. Amazon S3 に DynamoDB テーブルデータをエクスポートする - Amazon DynamoDB https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DataExport.html ↩︎

  2. export-table-to-point-in-time — AWS CLI 2.2.20 Command Reference https://awscli.amazonaws.com/v2/documentation/api/latest/reference/dynamodb/export-table-to-point-in-time.html ↩︎ ↩︎

  3. list-exports — AWS CLI 2.2.25 Command Reference https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudformation/list-exports.html ↩︎

  4. Class: AWS.DynamoDB — AWS SDK for JavaScript https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#putItem-property ↩︎

Discussion