Subchapter 63.4
references/migration.mdMarkdown10 KBView on GitHub
End-to-end migration from MongoDB to DocumentDB using AWS DMS for data, the index tool for indexes, and manual steps for users/roles. Produces artifacts/{app-name}/migration-plan.md.
Scripts
Wa Reviewapp_name (lowercase with hyphens)full-load or full-load-and-cdcCheck for the compatibility report first:
ls artifacts/<app-name>/compatibility-report.md 2>/dev/null || \
echo "WARNING: Run the compatibility sub-skill first to identify blockers."If missing, warn the user and ask whether to proceed.
Run against MongoDB source via mongosh:
// Counts per collection
db.getCollectionNames().forEach(c => print(c, db[c].countDocuments()))
// Data and index sizes
db.stats()
// Active indexes
db.getCollectionNames().forEach(c => db[c].getIndexes().forEach(i => printjson(i)))
// Index usage
db.getCollectionNames().forEach(c => db[c].aggregate([{ $indexStats: {} }]).forEach(printjson))Record totals for the migration plan.
# Export
python3 amazon-documentdb-tools/index-tool/migrationtools/documentdb_index_tool.py \
--dump-indexes --dir ./migration-index-export \
--uri "mongodb://<user>:<pass>@<mongo-host>:27017"
# Check compatibility
python3 amazon-documentdb-tools/index-tool/migrationtools/documentdb_index_tool.py \
--show-issues --dir ./migration-index-export
# Dry run restore
python3 amazon-documentdb-tools/index-tool/migrationtools/documentdb_index_tool.py \
--restore-indexes --skip-incompatible --dry-run \
--dir ./migration-index-export \
--uri "mongodb://admin:<pw>@<docdb-endpoint>:27017/?tls=true&tlsCAFile=global-bundle.pem&replicaSet=rs0&retryWrites=false"
# Actual restore
python3 amazon-documentdb-tools/index-tool/migrationtools/documentdb_index_tool.py \
--restore-indexes --skip-incompatible \
--dir ./migration-index-export \
--uri "mongodb://admin:<pw>@<docdb-endpoint>:27017/?tls=true&tlsCAFile=global-bundle.pem&replicaSet=rs0&retryWrites=false"Add --shorten-index-name for long names and --support-2dsphere for 2dsphere indexes. Record the count restored and any skipped.
DocumentDB built-in roles: read, readWrite, dbAdmin, dbAdminAnyDatabase, readAnyDatabase, readWriteAnyDatabase. Custom roles are not supported — map each MongoDB custom role to the nearest built-in.
db.createUser({
user: "<username>", pwd: "<password>",
roles: [{ role: "readWrite", db: "<database>" }]
})4a. Security group. DocumentDB SG must allow inbound TCP 27017 from the DMS replication instance’s SG. Self-referencing rule if in the same SG:
DOCDB_SG=$(aws docdb describe-db-clusters --db-cluster-identifier <docdb-id> \
--query 'DBClusters[0].VpcSecurityGroups[0].VpcSecurityGroupId' --output text --region <region>)
aws ec2 authorize-security-group-ingress \
--group-id $DOCDB_SG --protocol tcp --port 27017 \
--source-group $DOCDB_SG --region <region> 2>/dev/null || true4b. DMS replication subnet group (must be in the same VPC as DocumentDB):
VPC_ID=$(aws docdb describe-db-instances --db-instance-identifier <docdb-inst> \
--query 'DBInstances[0].DBSubnetGroup.VpcId' --output text --region <region>)
SUBNET_IDS=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" \
--query 'Subnets[*].SubnetId' --output text --region <region>)
aws dms create-replication-subnet-group \
--replication-subnet-group-identifier <app>-migration-sg \
--replication-subnet-group-description "MongoDB to DocumentDB" \
--subnet-ids $SUBNET_IDS --region <region>4c. DMS replication instance:
aws dms create-replication-instance \
--replication-instance-identifier <app>-dms \
--replication-instance-class dms.r5.large --allocated-storage 50 \
--vpc-security-group-ids $DOCDB_SG \
--replication-subnet-group-identifier <app>-migration-sg \
--no-publicly-accessible --multi-az --region <region>Poll aws dms describe-replication-instances until ReplicationInstanceStatus=available.
4d. Import the RDS CA bundle into DMS (once per region):
DMS_CERT_ARN=$(aws dms describe-certificates --region <region> \
--query 'Certificates[?CertificateIdentifier==`global-bundle`].CertificateArn' --output text)
if [ -z "$DMS_CERT_ARN" ] || [ "$DMS_CERT_ARN" = "None" ]; then
curl -s https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem -o /tmp/global-bundle.pem
DMS_CERT_ARN=$(aws dms import-certificate \
--certificate-identifier global-bundle \
--certificate-pem file:///tmp/global-bundle.pem --region <region> \
--query 'Certificate.CertificateArn' --output text)
fi4e. Source endpoint (MongoDB):
aws dms create-endpoint --endpoint-identifier <app>-source \
--endpoint-type source --engine-name mongodb \
--server-name <mongo-host> --port 27017 \
--username <u> --password '<pw>' --database-name admin --region <region>For production, keep credentials off the command line: replace --username/--password with --mongo-db-settings referencing a Secrets Manager secret — 'ServerName=<host>,Port=27017,DatabaseName=admin,SecretsManagerAccessRoleArn=<role-arn>,SecretsManagerSecretId=<secret-arn>'. The role needs secretsmanager:GetSecretValue plus iam:PassRole.
4f. Target endpoint (DocumentDB). MUST use --ssl-mode verify-full with --certificate-arn — without TLS DMS hits socket timeouts:
aws dms create-endpoint --endpoint-identifier <app>-target \
--endpoint-type target --engine-name docdb \
--server-name <docdb-endpoint> --port 27017 \
--username admin --password '<pw>' \
--ssl-mode verify-full --certificate-arn $DMS_CERT_ARN \
--database-name "" --region <region>As with the source endpoint (4e), for production keep credentials off the command line by passing --doc-db-settings with SecretsManagerSecretId + SecretsManagerAccessRoleArn instead of --username/--password.
4g. Test both endpoints (DMS refuses to start a task without passing tests):
DMS_INSTANCE_ARN=$(aws dms describe-replication-instances \
--filters Name=replication-instance-id,Values=<app>-dms \
--query 'ReplicationInstances[0].ReplicationInstanceArn' --output text --region <region>)
SRC_ARN=$(aws dms describe-endpoints --filters Name=endpoint-id,Values=<app>-source \
--query 'Endpoints[0].EndpointArn' --output text --region <region>)
TGT_ARN=$(aws dms describe-endpoints --filters Name=endpoint-id,Values=<app>-target \
--query 'Endpoints[0].EndpointArn' --output text --region <region>)
aws dms test-connection --replication-instance-arn $DMS_INSTANCE_ARN \
--endpoint-arn $SRC_ARN --region <region>
aws dms test-connection --replication-instance-arn $DMS_INSTANCE_ARN \
--endpoint-arn $TGT_ARN --region <region>
# Poll both until successful
aws dms describe-connections \
--filters Name=endpoint-arn,Values=$SRC_ARN,$TGT_ARN --region <region>4h. Migration task — set FailOnNoTablesCaptured: false or the task fails fatally on an empty source:
TASK_ARN=$(aws dms create-replication-task \
--replication-task-identifier <app>-migration \
--source-endpoint-arn $SRC_ARN --target-endpoint-arn $TGT_ARN \
--replication-instance-arn $DMS_INSTANCE_ARN \
--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"}]}' \
--replication-task-settings '{"TargetMetadata":{"SupportLobs":true,"FullLobMode":false,"LimitedSizeLobMode":true,"LobMaxSize":32},"FullLoadSettings":{"TargetTablePrepMode":"DO_NOTHING"},"ErrorBehavior":{"FailOnNoTablesCaptured":false},"Logging":{"EnableLogging":true}}' \
--query 'ReplicationTask.ReplicationTaskArn' --output text --region <region>)Wait for Status=ready, then start the task using the captured ARN:
aws dms start-replication-task \
--replication-task-arn $TASK_ARN \
--start-replication-task-type start-replication --region <region>Default 32 KB LOB limit truncates larger documents — check the user’s largest docs and adjust if needed.
# Progress + CDC latency
aws dms describe-replication-tasks --filters Name=replication-task-arn,Values=$TASK_ARN \
--query 'ReplicationTasks[0].{Status:Status,Progress:ReplicationTaskStats.FullLoadProgressPercent,CDCLatency:ReplicationTaskStats.CDCLatencySource}'
# Table-level errors
aws dms describe-table-statistics --replication-task-arn $TASK_ARN \
--query 'TableStatistics[?TableState==`Table error`]'Compare counts and sample documents on MongoDB and DocumentDB. Acceptable variance < 0.1% during active CDC.
artifacts/{app-name}/migration-plan.md — collections migrated with counts, indexes restored/skipped, users created, DMS task ARN + status, validation results, planned cutover time.
Before switching traffic, complete every item:
explain()tls, tlsCAFile, replicaSet, readPreference, retryWrites=false)successful)Replication ongoingCutover steps (in order):
CDCLatencySource)CPUUtilization, DatabaseConnections)Rollback within 24–48 hours: point app back at MongoDB, fix the issue in staging, re-plan cutover.