Restore
Go back to the previous release and the database snapshot taken before its migration.
Use this when a release migrated your database and you need both the previous release and the database as it was before the migration, for example because the migrated data is damaged. It costs data and downtime, so first check whether you need it:
- A failed update doesn't need a restore. CloudFormation rolls it back by itself; see Updates.
- A release that crossed no migration is undone by updating the stack to the previous release's template, without a restore.
Before you start
What you lose: everything written between the upgrade's restart and the moment you stop the service in step 1. Nothing written before the upgrade is lost: the snapshot was taken while the service was stopped.
How long it takes: in our tests, about 25 minutes of work for AWS (most of it restoring a Multi-AZ instance from the snapshot), plus your time between steps. The console is down throughout.
You need:
-
The snapshot. Every migration-crossing update takes one, named
tracemill-<short id>-<version>-<attempt>-<hash>. The short id is the first block of theDeploymentIdoutput. List them:aws rds describe-db-snapshots --snapshot-type manual \ --query "DBSnapshots[?starts_with(DBSnapshotIdentifier, 'tracemill-<short id>-')].[DBSnapshotIdentifier,SnapshotCreateTime]"Pick the one taken by the upgrade you're undoing. Its name carries that release's version and attempt.
-
The previous release's template URL:
https://tracemill-releases-prod-us-east-1.s3.us-east-1.amazonaws.com/releases/<previous version>/tracemill-byoc.yaml. -
AWS credentials allowed to update the stack, with
AWS_REGIONset to the stack's Region. -
The AWS CLI and
jq(step 4 uses it to build the parameter list).
Below, <stack> is the stack name, <id> the short id, and the database is
tracemill-<id>-db, the one the stack created. If you launched with an
existing database, use its identifier and its own settings instead.
Turn auto-apply off first if AutoApply is enabled. Otherwise its next
window reinstalls the ring's head, which may be the very release you're
undoing. Update the stack with Use existing template, AutoApply set to
disabled and every other parameter unchanged, and wait for
UPDATE_COMPLETE. Step 4 keeps it disabled. Turn it back on only once the
ring no longer offers the release you restored away from.
Don't start a restore while another stack update is in progress.
Don't change or rotate any of the stack's secrets during a restore. The restored database needs the same data-encryption key to read its encrypted fields.
Step 1: Stop the service
Scale the service to zero and wait until its tasks have actually stopped. The service's running count reaches zero before its tasks finish stopping, so wait on the tasks themselves.
T=$(aws ecs list-tasks --cluster <stack> --service-name <stack>-server --query taskArns --output text)
aws ecs update-service --cluster <stack> --service <stack>-server --desired-count 0
aws ecs wait tasks-stopped --cluster <stack> --tasks $TAbout a minute and a half in our tests. Writes stop here.
Step 2: Move the current database aside
Rename it, so the restored instance can take its identifier and endpoint:
aws rds modify-db-instance --db-instance-identifier tracemill-<id>-db \
--new-db-instance-identifier tracemill-<id>-db-old --apply-immediatelyThe original identifier is free about a minute and a half later: wait until
aws rds describe-db-instances --db-instance-identifier tracemill-<id>-db
fails with DBInstanceNotFound. Keep the old instance until you're satisfied
with the restore.
Step 3: Restore the snapshot under the original identifier
Restore with the old instance's settings. Read them first:
aws rds describe-db-instances --db-instance-identifier tracemill-<id>-db-old \
--query 'DBInstances[0].{Class:DBInstanceClass,MultiAZ:MultiAZ,SubnetGroup:DBSubnetGroup.DBSubnetGroupName,SecurityGroups:VpcSecurityGroups[].VpcSecurityGroupId,ParameterGroup:DBParameterGroups[0].DBParameterGroupName,StorageType:StorageType,AutoMinorVersionUpgrade:AutoMinorVersionUpgrade,BackupRetentionPeriod:BackupRetentionPeriod}'The command below matches what the stack creates (gp3 storage, automatic minor upgrades, log export, deletion protection). If you launched with an existing database, use your instance's own values instead.
Then restore:
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier tracemill-<id>-db \
--db-snapshot-identifier <snapshot> \
--db-instance-class <Class> \
--multi-az \
--db-subnet-group-name <SubnetGroup> \
--vpc-security-group-ids <SecurityGroups> \
--db-parameter-group-name <ParameterGroup> \
--storage-type gp3 \
--copy-tags-to-snapshot \
--deletion-protection \
--enable-cloudwatch-logs-exports postgresql \
--auto-minor-version-upgrade \
--no-publicly-accessible
aws rds wait db-instance-available --db-instance-identifier tracemill-<id>-dbUse --no-multi-az instead of --multi-az if MultiAZ was false.
This took about 16.5 minutes for a Multi-AZ instance in our tests. The instance
reports available a couple of minutes before its standby exists. The endpoint
is the same as before, so nothing else needs to change. The restored instance
keeps the snapshot's KMS key and master password. In our tests it also kept the
backup retention period; check that BackupRetentionPeriod matches the old
instance, and if it doesn't, set it back:
aws rds modify-db-instance --db-instance-identifier tracemill-<id>-db \
--backup-retention-period <BackupRetentionPeriod> --apply-immediatelyStep 4: Update the stack to the previous release
Update the stack to the previous release's template, with OperationAttempt
set to a new value starting with restore- and every other parameter at its
previous value. The restore- prefix tells the stack to trust the restored
database over its own records of what's installed.
aws cloudformation describe-stacks --stack-name <stack> \
--query 'Stacks[0].Parameters[].ParameterKey' --output json \
| jq '[.[] | if . == "OperationAttempt"
then {ParameterKey: ., ParameterValue: "restore-1"}
else {ParameterKey: ., UsePreviousValue: true} end]' > restore-params.json
aws cloudformation update-stack --stack-name <stack> \
--template-url <previous release template URL> \
--capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM \
--parameters file://restore-params.json
aws cloudformation wait stack-update-complete --stack-name <stack>Pass every parameter: an update that omits one resets it to the template default.
Keep rollback on. Never pass --disable-rollback: CloudFormation refuses it
for a release change, and the failed attempt leaves the stack's Lambda
functions on the wrong release's code.
The update stops the service, takes another snapshot (of the restored database), runs the previous release's transition, then starts the previous release. About five minutes in our tests, with the console back after four. While ECS replaces the deployment, it may briefly start a task of the release you're leaving; that task refuses to start on the older schema ("database schema N is below this binary's head M"). That's expected and doesn't fail the update.
Step 5: Verify
- The stack is
UPDATE_COMPLETE. https://<ConsoleHostname>/readyzreturns 200.- Settings → Updates shows the previous version and schema, with the
restore-attempt as the last operation.
Step 6: Clean up
Once you're satisfied, delete the old instance. It has deletion protection on, and you decide whether to keep a final snapshot of it:
aws rds modify-db-instance --db-instance-identifier tracemill-<id>-db-old \
--no-deletion-protection --apply-immediately
aws rds delete-db-instance --db-instance-identifier tracemill-<id>-db-old \
--final-db-snapshot-identifier tracemill-<id>-db-old-final # or --skip-final-snapshot
aws rds wait db-instance-deleted --db-instance-identifier tracemill-<id>-db-old
aws logs delete-log-group --log-group-name /aws/rds/instance/tracemill-<id>-db-old/postgresqlRDS creates that log group itself and keeps writing to it until the instance is gone, so delete it only after the wait. It isn't part of the stack, so nothing else deletes it.
If the restore update fails
Its transition fails
For example, the database wasn't restored first, so the previous release
refuses the newer schema. CloudFormation rolls the update back and ends at
UPDATE_ROLLBACK_COMPLETE with the service left stopped. You get one alert
("release rolled back; service not restored"). It took about seven minutes in
our tests.
Fix the cause and repeat step 4 with a new attempt, such as restore-2.
If instead you want the service back on the current release and database,
scale it back up:
aws ecs update-service --cluster <stack> --service <stack>-server --desired-count <DesiredCount>The restored release doesn't start
If the previous release's tasks never become healthy, CloudFormation rolls
back, but its rollback of the service redeploys the newer release onto the
restored schema, which refuses to start. The rollback itself then fails: the
stack ends at UPDATE_ROLLBACK_FAILED with the service stopped (about 13.5
minutes in our tests). Expect up to three alerts.
Finish the rollback without the service:
aws cloudformation continue-update-rollback --stack-name <stack> --resources-to-skip Service
aws cloudformation wait stack-rollback-complete --stack-name <stack>Then fix the cause and repeat step 4 with a new attempt.