Use AWS DMS to Synchronize or Migrate RDS Databases
Migrate or continuously synchronize a source RDS database to a SleakOps-managed RDS using AWS Database Migration Service (DMS), including cross-account networking and per-engine prerequisites.
Prerequisites
- AWS CLI configured with profiles for both source and destination accounts
- Source and target RDS instances running and accessible
- Sufficient resources: the DMS Replication Instance should match or exceed the source database's specs
- The source database must have CDC (Change Data Capture) enabled — see the engine-specific steps below
Let's Start
Step 1 — Create the DMS Replication Instance
Run this in the source account. Set allocated_storage, replication_instance, availability_zone, source_db_name, and subnet_ids (must match the source DB subnets):
allocated_storage="25"
replication_instance="dms.r5.large"
availability_zone="us-east-1a"
source_db_name="sourcedatabase"
subnet_ids="subnet-0123456789abcdef0,subnet-0fedcba9876543210"
replication_instance_id="replication-${source_db_name}"
subnet_group_name="dms-subnet-group-${source_db_name}"
aws dms create-replication-subnet-group \
--replication-subnet-group-identifier "$subnet_group_name" \
--replication-subnet-group-description "Subnet group for DMS replication of $source_db_name" \
--subnet-ids ${subnet_ids//,/ }
latest_engine_version=$(aws dms describe-orderable-replication-instances \
--query 'OrderableReplicationInstances[*].[EngineVersion]' --output text | sort -V | tail -n 1)
aws dms create-replication-instance \
--replication-instance-identifier "$replication_instance_id" \
--replication-instance-class "$replication_instance" \
--allocated-storage "$allocated_storage" \
--availability-zone "$availability_zone" \
--replication-subnet-group-identifier "$subnet_group_name" \
--engine-version "$latest_engine_version" \
--no-publicly-accessible \
--tags Key=SourceDB,Value="$source_db_name"
Step 2 — Create source and target endpoints
Source endpoint:
source_db_name="sourcedatabase"
source_engine="postgres"
source_username="mydbuser"
source_password="mypassword"
source_server_name="mydb.c1234567890.us-east-1.rds.amazonaws.com"
source_database_name="mydatabase"
source_port="5432"
aws dms create-endpoint \
--endpoint-identifier "source-endpoint-${source_db_name}" \
--endpoint-type "source" \
--engine-name "$source_engine" \
--username "$source_username" \
--password "$source_password" \
--server-name "$source_server_name" \
--database-name "$source_database_name" \
--port "$source_port"
Target endpoint:
target_db_name="targetdatabase"
target_engine="postgres"
target_username="targetdbuser"
target_password="targetpassword"
target_server_name="targetdb.c1234567890.us-east-1.rds.amazonaws.com"
target_database_name="targetdb"
target_port="5432"
aws dms create-endpoint \
--endpoint-identifier "target-endpoint-${target_db_name}" \
--endpoint-type "target" \
--engine-name "$target_engine" \
--username "$target_username" \
--password "$target_password" \
--server-name "$target_server_name" \
--database-name "$target_database_name" \
--port "$target_port"
Step 3 — Configure cross-account networking
Both accounts need VPC Peering and Security Group rules so the Replication Instance can reach both databases.
Source account (full script):
REGION="us-east-1"
SOURCE_PROFILE="source-profile"
SOURCE_VPC_ID="<source-vpc-id>"
TARGET_VPC_ID="<target-vpc-id>"
TARGET_ACCOUNT_ID="<target-account-id>"
SOURCE_RDS_SECURITY_GROUP="<source-rds-security-group-id>"
TARGET_RDS_CIDR_BLOCK="<target-vpc-cidr-block>"
peering_connection_id=$(aws ec2 create-vpc-peering-connection \
--vpc-id "$SOURCE_VPC_ID" \
--peer-vpc-id "$TARGET_VPC_ID" \
--peer-owner-id "$TARGET_ACCOUNT_ID" \
--region "$REGION" --profile "$SOURCE_PROFILE" \
--query "VpcPeeringConnection.VpcPeeringConnectionId" --output text)
SOURCE_ROUTE_TABLE_ID=$(aws ec2 describe-route-tables \
--filters "Name=vpc-id,Values=$SOURCE_VPC_ID" \
--profile "$SOURCE_PROFILE" \
--query "RouteTables[0].RouteTableId" --output text)
aws ec2 create-route \
--route-table-id "$SOURCE_ROUTE_TABLE_ID" \
--destination-cidr-block "$TARGET_RDS_CIDR_BLOCK" \
--vpc-peering-connection-id "$peering_connection_id" \
--profile "$SOURCE_PROFILE"
aws ec2 authorize-security-group-ingress \
--group-id "$SOURCE_RDS_SECURITY_GROUP" \
--protocol tcp --port 5432 \
--cidr "$TARGET_RDS_CIDR_BLOCK" \
--profile "$SOURCE_PROFILE"
aws iam create-role \
--role-name "dms-access-role" \
--profile "$SOURCE_PROFILE" \
--assume-role-policy-document '{"Version":"2012-10-17","Statement":{"Effect":"Allow","Principal":{"Service":"dms.amazonaws.com"},"Action":"sts:AssumeRole"}}'
aws iam attach-role-policy \
--role-name "dms-access-role" --profile "$SOURCE_PROFILE" \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole
Target account: Accept the VPC peering, update route tables, and create a cross-account IAM role — mirror the source account steps with target account credentials.
Step 4 — Engine-specific prerequisites
Before running the migration task, the source database must have CDC enabled.
PostgreSQL — update pg_hba.conf and postgresql.conf:
# pg_hba.conf — allow DMS replication instance
host all all <dms-instance-ip>/32 md5
host replication dms <dms-instance-ip>/32 md5
# postgresql.conf
wal_level = logical
max_replication_slots = 2
max_wal_senders = 2
wal_sender_timeout = 0
Reload: SELECT pg_reload_conf();
Oracle — enable archive log and supplemental logging:
-- Enable Archive Log Mode (if not already enabled)
SHUTDOWN TRANSACTIONAL;
STARTUP MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE OPEN;
ALTER DATABASE FORCE LOGGING;
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA;
ALTER DATABASE ADD SUPPLEMENTAL LOG DATA (PRIMARY KEY, UNIQUE, FOREIGN KEY) COLUMNS;
Step 5 — Run the pre-migration assessment and start the task
Pre-migration assessment:
source_db_name="sourcedatabase"
target_db_name="targetdatabase"
aws dms start-replication-task-assessment-run \
--replication-task-assessment-run-name "premigration-${source_db_name}-to-${target_db_name}" \
--replication-instance-arn "$(aws dms describe-replication-instances \
--filters "Name=replication-instance-id,Values=replication-${source_db_name}" \
--query "ReplicationInstances[0].ReplicationInstanceArn" --output text)" \
--source-endpoint-arn "$(aws dms describe-endpoints \
--filters "Name=endpoint-id,Values=source-endpoint-${source_db_name}" \
--query "Endpoints[0].EndpointArn" --output text)" \
--target-endpoint-arn "$(aws dms describe-endpoints \
--filters "Name=endpoint-id,Values=target-endpoint-${target_db_name}" \
--query "Endpoints[0].EndpointArn" --output text)" \
--migration-type "full-load-and-cdc" \
--table-mappings '{"rules":[{"rule-type":"selection","rule-id":"1","rule-name":"include-all","object-locator":{"schema-name":"%","table-name":"%"},"rule-action":"include"}]}'
Start the migration task (after the assessment passes):
aws dms create-replication-task \
--replication-task-identifier "migration-task-${source_db_name}-to-${target_db_name}" \
--source-endpoint-arn "$(aws dms describe-endpoints \
--filters "Name=endpoint-id,Values=source-endpoint-${source_db_name}" \
--query "Endpoints[0].EndpointArn" --output text)" \
--target-endpoint-arn "$(aws dms describe-endpoints \
--filters "Name=endpoint-id,Values=target-endpoint-${target_db_name}" \
--query "Endpoints[0].EndpointArn" --output text)" \
--replication-instance-arn "$(aws dms describe-replication-instances \
--filters "Name=replication-instance-id,Values=replication-${source_db_name}" \
--query "ReplicationInstances[0].ReplicationInstanceArn" --output text)" \
--migration-type "full-load-and-cdc" \
--table-mappings '{"rules":[{"rule-type":"selection","rule-id":"1","rule-name":"include-all","object-locator":{"schema-name":"%","table-name":"%"},"rule-action":"include"}]}'