Recovering DLT data after stack deletion
This section describes how to restore test scenarios and run history into a new DLT stack after the original stack has been deleted. It applies when the customer’s S3 buckets and DynamoDB tables were retained (the default behavior) and the customer wants to reconnect them to a fresh deployment.
Background
When a DLT stack is deleted, the following resources are retained in the customer’s AWS account due to data protection policies:
| Resource | Contains | Naming |
|---|---|---|
|
Scenarios S3 bucket |
Test scripts (JMeter/K6/Locust), per-task result XML files, JMeter framework assets |
Auto-generated (for example, |
|
Logs S3 bucket |
S3 access logs for all DLT buckets |
Auto-generated |
|
Console S3 bucket |
Web UI static assets |
Auto-generated |
|
Scenarios DynamoDB table |
Test definitions, configurations, scheduling rules |
Auto-generated (for example, |
|
History DynamoDB table |
Test run results, durations, success rates |
Auto-generated (for example, |
|
CloudWatch log groups |
Lambda and ECS execution logs (10 years retention) |
Named by function |
All resource names are auto-generated by CloudFormation. They are not predictable and cannot be specified as parameters when deploying a new stack.
Important constraints
-
The DLT CloudFormation template does not accept parameters for existing DynamoDB tables or S3 buckets. A new deployment always creates new resources.
-
Directly modifying CloudFormation-managed resources outside the stack causes stack drift. All data migration must happen at the data level, not the infrastructure level.
-
The solution UUID is randomly generated on each fresh deployment. The new stack will have a different UUID than the original.
-
DynamoDB tables have Point-in-Time Recovery (PITR) enabled by default, providing continuous backups for the last 35 days.
Recovery process
Step 1: Identify retained resources
Before deploying a new stack, locate the retained resources from the deleted stack.
# 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')]"
If you noted the CloudFormation stack outputs before deletion, use those exact names. The relevant outputs were:
-
ScenariosBucket— the S3 bucket name -
ScenariosTable— the DynamoDB scenarios table name
For resources not exposed as stack outputs (such as the history table), use list-stack-resources before deletion to get the physical resource IDs. This command lists every resource in the stack regardless of whether it has a corresponding output.
Step 2: Deploy a new DLT stack
Deploy a fresh DLT stack using the same CloudFormation template version (or newer). Use the same parameters as the original deployment (VPC settings, admin email, etc.).
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
Wait for the stack to reach CREATE_COMPLETE status.
Step 3: Identify the new stack’s resources
Retrieve the resource names from the new stack’s outputs.
aws cloudformation describe-stacks \ --stack-name distributed-load-testing-new \ --query "Stacks[0].Outputs"
Note the values for:
-
ScenariosBucket(new bucket name) -
ScenariosTable(new table name)
For any resource not exposed as a stack output (such as the history table), use list-stack-resources to find it by its logical ID:
aws cloudformation list-stack-resources \ --stack-name distributed-load-testing-new \ --query "StackResourceSummaries[?contains(LogicalResourceId, 'HistoryTable')].{LogicalId:LogicalResourceId,PhysicalId:PhysicalResourceId}"
This command lists every resource the stack manages. You can filter by logical ID substring to find any resource.
Step 4: Migrate DynamoDB data
Export data from the retained tables and import it into the new tables. Use DynamoDB data export or scan-and-write operations.
Option A: Using DynamoDB Export/Import (recommended for large datasets)
For tables with thousands of items, use a DynamoDB export to S3 followed by an import:
# 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
Note
The import-table API creates a new table. Since the new stack already created the target table, use Option B instead, or delete the new empty table first and recreate it via import (this will cause stack drift, see the warning below).
Option B: Scan and BatchWrite (recommended)
This approach reads directly from the retained tables and writes into the new stack’s tables without causing stack drift. It requires Python 3 and the boto3 library.
# 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>")
For large tables (tens of thousands of items), consider using parallel scan segments to speed up the read phase.
Option C: Restore from PITR (if within 35-day window)
If the stack was deleted within the last 35 days and PITR was enabled (it is by default), you can restore the table to a point in time. However, PITR restores create a new table with a different name, so you would still need to copy data into the stack-managed table using Option 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
Step 5: Migrate S3 data
Copy test scripts and result files from the retained bucket to the new stack’s bucket.
# 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>
This copies:
-
Test scripts (JMeter
.jmxfiles, K6 scripts, Locust files, ZIP archives) -
Test result XML files organized by test ID and region
-
JMeter framework assets and plugins
Step 6: Verify the migration
-
Log in to the DLT web console using the new stack’s URL (found in the
WebConsolestack output). -
Confirm that all test scenarios appear in the scenarios list.
-
Open individual scenarios and verify that test run history is visible.
-
Confirm that test scripts are downloadable from the scenario detail page.
-
Run a small test to verify end-to-end functionality with the new stack.
Step 7: Clean up retained resources
After verifying that all data has been migrated successfully, delete the old retained resources to avoid ongoing storage costs.
# 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>
Only perform this step after confirming the new stack is fully operational with all historical data intact.
Avoiding stack drift
Stack drift occurs when the actual state of a resource differs from what CloudFormation expects. To avoid drift during this recovery process:
-
Do NOT rename or modify the new stack’s DynamoDB tables or S3 buckets directly.
-
Do NOT use
import-tableto replace the new stack’s tables (this would require deleting the CloudFormation-managed table first). -
Do NOT modify IAM policies, bucket policies, or table settings outside of CloudFormation.
-
DO use data-level operations only:
PutItem,BatchWriteItem,s3 sync,s3 cp. -
DO write data into the tables and buckets that CloudFormation created. Adding items to a DynamoDB table or objects to an S3 bucket does not constitute drift.
Writing data into CloudFormation-managed tables and buckets is safe because CloudFormation tracks the resource configuration (table schema, billing mode, encryption settings), not the data contents.
What cannot be recovered
The following are lost when a stack is deleted and cannot be restored through this process:
| Item | Reason |
|---|---|
|
Cognito user accounts |
User pool is deleted with the stack. Users must re-register. |
|
Active EventBridge schedules |
Scheduled test rules are deleted. Recreate schedules manually in the DLT UI. |
|
CloudWatch dashboards |
Per-test dashboards ( |
|
Solution UUID |
A new random UUID is generated. This affects operational metrics correlation only. |
|
Regional stack associations |
Regional stacks must be redeployed and re-associated with the new hub stack. |
Preventive measures
To simplify future recovery scenarios:
-
Record stack outputs before any deletion. Save the
ScenariosBucket,ScenariosTable, and history table names. -
Enable DynamoDB PITR (enabled by default in DLT). Verify it remains active.
-
Enable S3 versioning (enabled by default on the scenarios bucket). This protects against accidental object deletion.
-
Consider enabling DynamoDB deletion protection on the scenarios and history tables as an additional safeguard:
aws dynamodb update-table \ --table-name <scenarios-table> \ --deletion-protection-enabled -
Use AWS Backup to create scheduled backups of DynamoDB tables for long-term retention beyond the 35-day PITR window.