

# スタック削除後の DLT データの復旧
<a name="recover-data-after-stack-deletion"></a>

このセクションでは、元のスタックが削除された後に、テストシナリオと実行履歴を新しい DLT スタックに復元する方法について説明します。これは、お客様の S3 バケットと DynamoDB テーブルが保持されている場合 (デフォルトの動作)、お客様がそれらを新しいデプロイに再接続する場合に適用されます。

## 背景
<a name="recovery-background"></a>

DLT スタックが削除されると、データ保護ポリシーにより、次のリソースがお客様の AWS アカウントに保持されます。


| リソース | Contains | 命名 | 
| --- | --- | --- | 
| シナリオ S3 バケット | テストスクリプト (JMeter/K6/Locust)、タスクごとの結果 XML ファイル、JMeter フレームワークアセット | 自動生成 (例: `dltstack-scenariosbucket-abc123`) | 
| ログ S3 バケット | すべての DLT バケットの S3 アクセスログ | 自動生成 | 
| コンソール S3 バケット | ウェブ UI 静的アセット | 自動生成 | 
| シナリオ DynamoDB テーブル | テスト定義、設定、スケジューリングルール | 自動生成 (例: `DLTStack-ScenariosTable-xyz789`) | 
| 履歴 DynamoDB テーブル | テスト実行結果、所要時間、成功率 | 自動生成 (例: `DLTStack-HistoryTable-xyz789`) | 
| CloudWatch ロググループ | Lambda および ECS 実行ログ (保持期間: 10 年間) | 関数名ごとに命名 | 

すべてのリソース名は CloudFormation によって自動生成されます。これらは予測不可能であり、新しいスタックをデプロイするときにパラメータとして指定することはできません。

## 重要な制約
<a name="recovery-important-constraints"></a>

1. DLT CloudFormation テンプレートは、既存の DynamoDB テーブルまたは S3 バケットのパラメータを受け入れません。新しいデプロイでは、常に新しいリソースが作成されます。

1. スタックの外部で CloudFormation が管理するリソースを直接変更すると、スタックドリフトが発生します。すべてのデータ移行は、インフラストラクチャレベルではなくデータレベルで行う必要があります。

1. ソリューション UUID は、新しいデプロイごとにランダムに生成されます。新しいスタックには、元のスタックとは異なる UUID があります。

1. DynamoDB テーブルでは、ポイントインタイムリカバリ (PITR) がデフォルトで有効になっており、過去 35 日間の継続的なバックアップを提供します。

## 復旧プロセス
<a name="recovery-process"></a>

### ステップ 1: 保持されているリソースを特定する
<a name="recovery-step-1-identify-retained-resources"></a>

新しいスタックをデプロイする前に、削除されたスタックから保持されているリソースを見つけます。

```
# List retained DynamoDB tables (look for DLT naming patterns)
aws dynamodb list-tables --query "TableNames[?contains(@, 'ScenariosTable') || contains(@, 'HistoryTable')]"

# List retained S3 buckets (look for DLT naming patterns)
aws s3api list-buckets --query "Buckets[?contains(Name, 'dlttestrunnerstoragedlts') || contains(Name, 'dltconsoleresourcesdltda')]"
```

削除前に CloudFormation スタック出力を記録した場合は、それらの正確な名前を使用します。関連する出力は次のとおりです。
+  `ScenariosBucket` - S3 バケット名
+  `ScenariosTable` - DynamoDB シナリオテーブル名

スタック出力として公開されていないリソース (履歴テーブルなど) の場合は、削除前に `list-stack-resources` を使用して物理リソース ID を取得します。このコマンドは、対応する出力があるかどうかに関係なく、スタック内のすべてのリソースを一覧表示します。

### ステップ 2: 新しい DLT スタックをデプロイする
<a name="recovery-step-2-deploy-new-stack"></a>

同じ CloudFormation テンプレートバージョン (またはそれ以降) を使用して、新しい DLT スタックをデプロイします。元のデプロイと同じパラメータ (VPC 設定、管理者 E メールなど) を使用します。

```
aws cloudformation create-stack \
  --stack-name distributed-load-testing-new \
  --template-url https://s3.amazonaws.com/solutions-reference/distributed-load-testing-on-aws/latest/distributed-load-testing-on-aws.template \
  --parameters ParameterKey=AdminName,ParameterValue=<admin-name> \
               ParameterKey=AdminEmail,ParameterValue=<admin-email> \
  --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM CAPABILITY_AUTO_EXPAND
```

スタックが `CREATE_COMPLETE` ステータスになるまで待ちます。

### ステップ 3: 新しいスタックのリソースを特定する
<a name="recovery-step-3-identify-new-stack-resources"></a>

新しいスタックの出力からリソース名を取得します。

```
aws cloudformation describe-stacks \
  --stack-name distributed-load-testing-new \
  --query "Stacks[0].Outputs"
```

以下の値を書き留めます。
+  `ScenariosBucket` (新しいバケット名)
+  `ScenariosTable` (新しいテーブル名)

スタック出力として公開されていないリソース (履歴テーブルなど) については、`list-stack-resources` を使用して論理 ID で検索します。

```
aws cloudformation list-stack-resources \
  --stack-name distributed-load-testing-new \
  --query "StackResourceSummaries[?contains(LogicalResourceId, 'HistoryTable')].{LogicalId:LogicalResourceId,PhysicalId:PhysicalResourceId}"
```

このコマンドは、スタックが管理するすべてのリソースを一覧表示します。論理 ID の部分文字列でフィルタリングして、任意のリソースを検索できます。

### ステップ 4: DynamoDB データを移行する
<a name="recovery-step-4-migrate-dynamodb"></a>

保持されたテーブルからデータをエクスポートし、新しいテーブルにインポートします。DynamoDB データエクスポート、またはスキャンと書き込みオペレーションを使用します。

 **オプション A: DynamoDB エクスポート/インポートの使用 (大規模なデータセットに推奨)** 

数千の項目があるテーブルの場合は、S3 への DynamoDB エクスポートとインポートを使用します。

```
# Export from retained table to S3
aws dynamodb export-table-to-point-in-time \
  --table-arn arn:aws:dynamodb:<region>:<account>:table/<old-scenarios-table> \
  --s3-bucket <temporary-export-bucket> \
  --s3-prefix dlt-migration/scenarios/ \
  --export-format DYNAMODB_JSON

# Wait for export to complete, then import into new table
aws dynamodb import-table \
  --s3-bucket-source S3Bucket=<temporary-export-bucket>,S3KeyPrefix=dlt-migration/scenarios/ \
  --table-creation-parameters '{"TableName":"<new-scenarios-table>","KeySchema":[{"AttributeName":"testId","KeyType":"HASH"}],"AttributeDefinitions":[{"AttributeName":"testId","AttributeType":"S"}],"BillingMode":"PAY_PER_REQUEST"}' \
  --input-format DYNAMODB_JSON
```

**注記**  
`import-table` API は新しいテーブルを作成します。新しいスタックは既にターゲットテーブルを作成しているため、代わりにオプション B を使用するか、最初に新しい空のテーブルを削除してからインポートによって再作成します (これによりスタックドリフトが発生します。以下の警告を参照してください)。

 **オプション B: スキャンと BatchWrite (推奨)** 

このアプローチは、スタックドリフトを引き起こすことなく、保持されたテーブルから直接読み取り、新しいスタックのテーブルに書き込みます。Python 3 と `boto3` ライブラリが必要です。

```
# Install boto3 if not already available
pip install boto3
```

```
#!/usr/bin/env python3
"""Migrate DynamoDB items directly from retained tables to new stack tables."""

import boto3

dynamodb = boto3.resource("dynamodb", region_name="<region>")

def migrate_table(source_table_name, target_table_name):
    source = dynamodb.Table(source_table_name)
    target = dynamodb.Table(target_table_name)

    # Scan all items from the retained table (handles pagination automatically)
    items = []
    response = source.scan()
    items.extend(response["Items"])
    while "LastEvaluatedKey" in response:
        response = source.scan(ExclusiveStartKey=response["LastEvaluatedKey"])
        items.extend(response["Items"])

    print(f"Scanned {len(items)} items from {source_table_name}")

    # Write items to the new table using batch_writer (handles batching automatically)
    with target.batch_writer() as batch:
        for item in items:
            batch.put_item(Item=item)

    print(f"Wrote {len(items)} items to {target_table_name}")

# Migrate scenarios
migrate_table("<old-scenarios-table>", "<new-scenarios-table>")

# Migrate history
migrate_table("<old-history-table>", "<new-history-table>")
```

大規模なテーブル (数万項目) の場合は、並列スキャンセグメントを使用して読み取りフェーズを高速化することを検討してください。

 **オプション C: PITR からの復元 (35 日以内の場合)** 

スタックが過去 35 日以内に削除され、PITR が有効になっている場合 (デフォルトでは有効)、テーブルを特定の時点に復元できます。ただし、PITR 復元は別の名前の新しいテーブルを作成するため、オプション B を使用してスタック管理のテーブルにデータをコピーする必要があります。

```
# Restore to a new temporary table
aws dynamodb restore-table-to-point-in-time \
  --source-table-name <old-scenarios-table> \
  --target-table-name dlt-scenarios-restored \
  --restore-date-time <timestamp-before-deletion>

# Then scan from dlt-scenarios-restored and batch-write into the new stack's table
```

### ステップ 5: S3 データの移行
<a name="recovery-step-5-migrate-s3"></a>

テストスクリプトと結果ファイルを、保持されたバケットから新しいスタックのバケットにコピーします。

```
# Copy all objects from the old scenarios bucket to the new one
aws s3 sync \
  s3://<old-scenarios-bucket> \
  s3://<new-scenarios-bucket> \
  --source-region <region> \
  --region <region>
```

これにより、以下がコピーされます。
+ テストスクリプト (JMeter `.jmx` ファイル、K6 スクリプト、Locust ファイル、ZIP アーカイブ)
+ テスト ID とリージョン別に整理されたテスト結果 XML ファイル
+ JMeter フレームワークアセットとプラグイン

### ステップ 6: 移行を検証する
<a name="recovery-step-6-verify"></a>

1. 新しいスタックの URL (`WebConsole` スタック出力にあります) を使用して DLT ウェブコンソールにログインします。

1. すべてのテストシナリオがシナリオリストに表示されていることを確認します。

1. 個々のシナリオを開き、テスト実行履歴が表示されることを確認します。

1. テストスクリプトがシナリオの詳細ページからダウンロード可能であることを確認します。

1. 小規模なテストを実行して、新しいスタックでエンドツーエンドの機能を検証します。

### ステップ 7: 保持されているリソースをクリーンアップする
<a name="recovery-step-7-clean-up"></a>

すべてのデータが正常に移行されたことを確認したら、保持されている古いリソースを削除して、継続的なストレージコストを回避します。

```
# Delete old DynamoDB tables
aws dynamodb delete-table --table-name <old-scenarios-table>
aws dynamodb delete-table --table-name <old-history-table>

# Empty and delete old S3 buckets
aws s3 rm s3://<old-scenarios-bucket> --recursive
aws s3api delete-bucket --bucket <old-scenarios-bucket>

aws s3 rm s3://<old-logs-bucket> --recursive
aws s3api delete-bucket --bucket <old-logs-bucket>

aws s3 rm s3://<old-console-bucket> --recursive
aws s3api delete-bucket --bucket <old-console-bucket>
```

このステップは、新しいスタックが完全に動作し、すべての履歴データがそのままであることを確認した後にのみ実行します。

## スタックドリフトを回避する
<a name="recovery-avoiding-stack-drift"></a>

スタックドリフトは、リソースの実際の状態が CloudFormation の想定と異なる場合に発生します。この復旧プロセス中にドリフトを回避するには。
+ 新しいスタックの DynamoDB テーブルまたは S3 バケットの名前を変更したり、直接変更したりしないでください。
+ `import-table` を使用して新しいスタックのテーブルを置き換えないでください (最初に CloudFormation が管理するテーブルを削除する必要があります)。
+ CloudFormation 以外で IAM ポリシー、バケットポリシー、またはテーブル設定を変更しないでください。
+ データレベルのオペレーションのみを使用してください。`PutItem`、`BatchWriteItem`、`s3 sync`、`s3 cp`。
+ CloudFormation が作成したテーブルとバケットにデータを書き込みます。DynamoDB テーブルへの項目の追加、または S3 バケットへのオブジェクトの追加は、ドリフトには該当しません。

CloudFormation が管理するテーブルとバケットにデータを書き込むことは安全です。なぜなら、CloudFormation はデータコンテンツではなくリソース設定 (テーブルスキーマ、請求モード、暗号化設定) を追跡するからです。

## 復旧できないもの
<a name="recovery-what-cannot-be-recovered"></a>

スタックが削除されると、以下は失われ、このプロセスでは復元できません。


| Item | Reason | 
| --- | --- | 
| Cognito ユーザーアカウント | ユーザープールはスタックとともに削除されます。ユーザーは再登録する必要があります。 | 
| アクティブな EventBridge スケジュール | スケジュールされたテストルールは削除されます。DLT UI でスケジュールを手動で再作成します。 | 
| CloudWatch ダッシュボード | テストごとのダッシュボード (`EcsLoadTesting*`) は削除されます。新しい実行では、新しいダッシュボードが作成されます。 | 
| ソリューション UUID | 新しいランダムな UUID が生成されます。これは運用メトリクスの相関関係にのみ影響します。 | 
| リージョンスタックの関連付け | リージョンスタックを再デプロイし、新しいハブスタックに再関連付けする必要があります。 | 

## 予防策
<a name="recovery-preventive-measures"></a>

将来の復旧シナリオを簡素化するには。

1. 削除する前にスタック出力を記録します。`ScenariosBucket`、`ScenariosTable`、および 履歴テーブル名を保存します。

1. DynamoDB PITR を有効にします (DLT ではデフォルトで有効)。それがアクティブのままであることを確認します。

1. S3 バージョニングを有効にします (シナリオバケットでデフォルトで有効)。これにより、偶発的な削除から保護されます。

1. 追加の保護策として、シナリオと履歴テーブルで DynamoDB の削除保護を有効にすることを検討してください。

   ```
   aws dynamodb update-table \
     --table-name <scenarios-table> \
     --deletion-protection-enabled
   ```

1. AWS Backup を使用して、35 日間の PITR ウィンドウを超えて長期保存するための DynamoDB テーブルのスケジュールされたバックアップを作成します。