šŸŽ‰ 75% of content is free forever — Unlock Premium from $10/mo →
CW
šŸ’¼ Servicesā„¹ļø Aboutāœ‰ļø ContactView Pricing Plansfrom $10

AWS Data Migration for Data Engineers

AWS Data EngineeringDatabase Migration & DMS⭐ Premium

Advertisement

AWS Data Migration for Data Engineers

Master AWS DMS and SCT for database migrations — full load, CDC replication, schema conversion, and post-migration validation strategies.

18 min readIntermediate

Why This Matters

Database migration is one of the most high-stakes tasks in data engineering. A failed migration can cause data loss, extended downtime, and regulatory penalties. Understanding AWS DMS (Database Migration Service) and SCT (Schema Conversion Tool) — including CDC replication, LOB handling, replication instance sizing, and post-migration validation — is essential for any data engineer working on cloud adoption projects. Interviewers will test your knowledge of migration patterns, failure modes, and production validation strategies.

Migration Architecture Overview

AWS DMS + SCT Migration ArchitectureSOURCEOracle / MySQLSQL Server / PostgreSQLTransaction LogsAWS SCTSchema ConversionAssessment ReportDDL GenerationAWS DMSSource EndpointReplication InstanceTarget EndpointFull Load + CDCTARGETAuroraRedshiftRDSS3Migration TypesFull LoadInitial bulk transfer | Downtime requiredCDC OnlyOngoing replication | Zero downtimeFull Load + CDC (Recommended)Baseline + ongoing sync | Minimal downtimePost-Migration ValidationRow Count CheckChecksum ValidationSchema CheckQuery ValidationPerformance TestData Type CheckDMS Built-in ValidationROW_LEVEL | THREAD_COUNT=5 | FAILURE_MAX=10000

Real-World Project Structure

A production database migration project follows a structured approach:

Architecture Diagram
database-migration-project/
ā”œā”€ā”€ assessment/
│   ā”œā”€ā”€ sct_assessment_report.pdf   # Schema conversion assessment
│   ā”œā”€ā”€ source_profiling.sql        # Source database analysis
│   └── migration_complexity.md     # Effort estimation
ā”œā”€ā”€ infrastructure/
│   ā”œā”€ā”€ terraform/
│   │   ā”œā”€ā”€ dms.tf                  # Replication instance + endpoints
│   │   ā”œā”€ā”€ vpc.tf                  # VPN/Direct Connect to on-prem
│   │   ā”œā”€ā”€ kms.tf                  # Encryption keys
│   │   └── iam.tf                  # DMS service role
│   └── cloudformation/
│       └── dms-tasks.yaml
ā”œā”€ā”€ migration/
│   ā”œā”€ā”€ table_mappings.json         # DMS task table mappings
│   ā”œā”€ā”€ transformation_rules.json   # Data transformations
│   ā”œā”€ā”€ full_load_config.json       # Full load settings
│   └── cdc_config.json             # CDC settings
ā”œā”€ā”€ validation/
│   ā”œā”€ā”€ row_count_comparison.sql
│   ā”œā”€ā”€ checksum_validation.py
│   ā”œā”€ā”€ sample_data_compare.sql
│   └── performance_benchmark.sql
ā”œā”€ā”€ scripts/
│   ā”œā”€ā”€ create_endpoints.sh         # Setup source/target endpoints
│   ā”œā”€ā”€ create_replication.sh       # Launch replication instance
│   ā”œā”€ā”€ monitor_cdc.py              # CDC lag monitoring
│   └── post_migration_test.py      # Automated validation
└── runbooks/
    ā”œā”€ā”€ migration_checklist.md
    ā”œā”€ā”€ rollback_procedure.md
    └── cutover_plan.md

DMS Endpoint Configuration

import boto3
import json
import logging

logger = logging.getLogger(__name__)

class DMSEndpointManager:
    """Manage AWS DMS endpoints for database migration."""

    def __init__(self, region='us-east-1'):
        self.client = boto3.client('dms', region_name=region)

    def create_source_endpoint(self, config):
        """Create a DMS source endpoint with error handling."""
        try:
            response = self.client.create_endpoint(
                EndpointIdentifier=config['identifier'],
                EndpointType='source',
                EngineName=config['engine'],
                ServerName=config['server'],
                Port=config['port'],
                DatabaseName=config['database'],
                Username=config['username'],
                Password=config['password'],
                SslMode=config.get('ssl_mode', 'require'),
                CertificateArn=config.get('cert_arn'),
                Tags=config.get('tags', [])
            )
            endpoint_arn = response['Endpoint']['EndpointArn']
            logger.info(f"Created source endpoint: {endpoint_arn}")
            return endpoint_arn
        except Exception as e:
            logger.error(f"Failed to create source endpoint: {e}")
            raise

    def create_target_endpoint(self, config):
        """Create a DMS target endpoint with error handling."""
        try:
            response = self.client.create_endpoint(
                EndpointIdentifier=config['identifier'],
                EndpointType='target',
                EngineName=config['engine'],
                ServerName=config['server'],
                Port=config['port'],
                DatabaseName=config['database'],
                Username=config['username'],
                Password=config['password'],
                SslMode=config.get('ssl_mode', 'require'),
                ExtraConnectionAttributes=config.get('extra_attributes', ''),
                Tags=config.get('tags', [])
            )
            endpoint_arn = response['Endpoint']['EndpointArn']
            logger.info(f"Created target endpoint: {endpoint_arn}")
            return endpoint_arn
        except Exception as e:
            logger.error(f"Failed to create target endpoint: {e}")
            raise

    def test_endpoint_connection(self, endpoint_arn):
        """Test connectivity to an endpoint."""
        try:
            response = self.client.test_connection(
                EndpointArn=endpoint_arn,
                EndpointType='source'
            )
            status = response['ConnectionStatus']
            logger.info(f"Endpoint test status: {status}")
            return status
        except Exception as e:
            logger.error(f"Endpoint test failed: {e}")
            raise


class DMSReplicationManager:
    """Manage DMS replication instances and tasks."""

    def __init__(self, region='us-east-1'):
        self.client = boto3.client('dms', region_name=region)

    def create_replication_instance(self, config):
        """Create a DMS replication instance."""
        try:
            response = self.client.create_replication_instance(
                ReplicationInstanceIdentifier=config['identifier'],
                ReplicationInstanceClass=config.get('instance_class', 'dms.r5.large'),
                AllocatedStorage=config.get('storage', 100),
                MultiAZ=config.get('multi_az', True),
                EngineVersion=config.get('engine_version', '3.5.1'),
                MinAllocatedStorage=config.get('min_storage', 50),
                MaxAllocatedStorage=config.get('max_storage', 500),
                VpcSecurityGroupIds=config.get('security_groups', []),
                ReplicationSubnetGroupIdentifier=config.get('subnet_group'),
                PubliclyAccessible=False,
                Tags=config.get('tags', [])
            )
            instance_arn = response['ReplicationInstance']['ReplicationInstanceArn']
            logger.info(f"Created replication instance: {instance_arn}")
            return instance_arn
        except Exception as e:
            logger.error(f"Failed to create replication instance: {e}")
            raise

    def create_migration_task(self, config):
        """Create a DMS migration task with full load + CDC."""
        try:
            response = self.client.create_replication_task(
                ReplicationTaskIdentifier=config['identifier'],
                SourceEndpointArn=config['source_arn'],
                TargetEndpointArn=config['target_arn'],
                ReplicationInstanceArn=config['replication_instance_arn'],
                MigrationType=config.get('migration_type', 'full-load-and-cdc'),
                TableMappings=json.dumps(config['table_mappings']),
                ReplicationTaskSettings=json.dumps(config.get('task_settings', {})),
                Tags=config.get('tags', [])
            )
            task_arn = response['ReplicationTask']['ReplicationTaskArn']
            logger.info(f"Created migration task: {task_arn}")
            return task_arn
        except Exception as e:
            logger.error(f"Failed to create migration task: {e}")
            raise

    def get_task_status(self, task_arn):
        """Get current status of a migration task."""
        try:
            response = self.client.describe_replication_tasks(
                Filters=[{'Name': 'replication-task-arn', 'Values': [task_arn]}]
            )
            tasks = response.get('ReplicationTasks', [])
            if tasks:
                status = tasks[0]['Status']
                stats = tasks[0].get('ReplicationTaskStats', {})
                logger.info(f"Task status: {status}, Latency: {stats.get('CDCLatencyTarget', 'N/A')}")
                return {'status': status, 'stats': stats}
        except Exception as e:
            logger.error(f"Failed to get task status: {e}")
            raise

Replication Instance Sizing

Instance ClassvCPUsRAMUse CaseThroughput
dms.t3.micro21 GBTesting/development~10 MB/s
dms.t3.medium24 GBSmall databases (<100 GB)~50 MB/s
dms.r5.large216 GBMedium databases (100 GB - 1 TB)~100 MB/s
dms.r5.2xlarge864 GBLarge databases (1-10 TB)~300 MB/s
dms.r5.4xlarge16128 GBVery large databases (>10 TB)~500 MB/s

Mathematical Formulas

Post-Migration Validation

import boto3
import hashlib
import logging
from datetime import datetime

logger = logging.getLogger(__name__)

class MigrationValidator:
    """Validate data integrity after database migration."""

    def __init__(self, redshift_cluster, database, db_user):
        self.redshift = boto3.client('redshift-data')
        self.cluster = redshift_cluster
        self.database = database
        self.db_user = db_user

    def execute_query(self, sql):
        """Execute SQL on Redshift and return results."""
        try:
            response = self.redshift.execute_statement(
                ClusterIdentifier=self.cluster,
                Database=self.database,
                DbUser=self.db_user,
                Sql=sql
            )
            statement_id = response['Id']

            # Wait for completion
            while True:
                status = self.redshift.describe_statement(Id=statement_id)
                if status['Status'] in ['FINISHED', 'FAILED', 'ABORTED']:
                    break

            if status['Status'] == 'FAILED':
                raise Exception(f"Query failed: {status.get('Error')}")

            return self.redshift.get_statement_result(Id=statement_id)
        except Exception as e:
            logger.error(f"Query execution failed: {e}")
            raise

    def validate_row_counts(self, source_counts):
        """Compare row counts between source and target."""
        results = []
        for table, expected_count in source_counts.items():
            result = self.execute_query(f"SELECT COUNT(*) FROM {table}")
            actual_count = int(result['Records'][0][0]['longValue'])
            match = actual_count == expected_count
            results.append({
                'table': table,
                'expected': expected_count,
                'actual': actual_count,
                'match': match
            })
            logger.info(f"{table}: expected={expected_count}, actual={actual_count}, match={match}")
        return results

    def validate_checksums(self, tables):
        """Compare checksums for critical tables."""
        results = []
        for table in tables:
            result = self.execute_query(
                f"SELECT MD5(CAST(COUNT(*) AS VARCHAR) || MAX(updated_at)) FROM {table}"
            )
            checksum = result['Records'][0][0]['stringValue']
            results.append({'table': table, 'checksum': checksum})
            logger.info(f"{table} checksum: {checksum}")
        return results

    def validate_sample_data(self, table, key_column, sample_ids):
        """Compare sample rows between source and target."""
        ids_str = ','.join(str(i) for i in sample_ids)
        result = self.execute_query(
            f"SELECT * FROM {table} WHERE {key_column} IN ({ids_str}) ORDER BY {key_column}"
        )
        return result['Records']

Performance Considerations

FactorImpactRecommendation
LOB ColumnsSlow replicationSet LobMaxSize appropriately
Primary KeyCDC performanceEnsure PK exists on all tables
Table CountParallel threadsEach table uses one thread
Network LatencyThroughputUse VPC peering or Direct Connect
Index CreationLoad speedDisable indexes during load
Transaction VolumeCDC lagSize instance for peak throughput
Collation MismatchFailuresConfigure HandleCollationDiff
Schema ChangesTask failuresStop, apply, resume task

Security Considerations

LayerControlImplementation
AuthenticationIAM RolesDMS service role for AWS access
Database AuthNative credentialsUsername/password for source/target
Encryption at RestSSE-KMSReplication instance + storage
Encryption in TransitSSL/TLSSslMode: require on endpoints
NetworkVPC + Security GroupsPrivate subnets, restricted ports
AuditCloudTrailAll DMS API calls logged
SecretsSecrets ManagerStore DB credentials securely
MonitoringCloudWatchCDC lag, throughput, errors

Interview Questions & Answers

Q1: What is the difference between homogeneous and heterogeneous migrations?

Answer: A homogeneous migration involves moving data between databases of the same engine type (e.g., MySQL to MySQL, Oracle to Oracle). The schema is compatible, so only DMS is needed. A heterogeneous migration involves different engine types (e.g., Oracle to PostgreSQL), requiring SCT to convert the schema before DMS can migrate the data. SCT analyzes the source schema, converts stored procedures, views, and table definitions, and produces an assessment report showing what can be automated and what requires manual intervention.

Q2: Explain the three DMS migration types.

Answer: Full Load copies all data from source to target in a single pass — suitable when downtime is acceptable. CDC (Change Data Capture) captures ongoing changes from database transaction logs (redo logs, WAL, binlog) and applies them to the target in near real-time. Full Load + CDC is the recommended production pattern: the full load establishes the baseline, then CDC captures changes made during and after the full load, ensuring the target stays synchronized with minimal downtime during cutover.

Q3: How does CDC work in DMS?

Answer: DMS CDC reads the source database's transaction logs to capture changes. It extracts INSERT, UPDATE, and DELETE operations and applies them to the target. This approach minimizes impact on the source database since it reads logs rather than querying the database directly. For Oracle, it reads redo logs; for PostgreSQL, WAL; for MySQL, binlog. CDC requires the source database to have sufficient log retention to cover the migration window.

Q4: When would you use AWS SCT?

Answer: SCT is required when migrating between different database engines (heterogeneous migration). It analyzes the source schema, converts stored procedures, views, and table definitions to be compatible with the target engine, and generates an assessment report. SCT categorizes elements as automatic (handled by SCT), action required (needs manual review), or blocked (cannot be converted). For Oracle to PostgreSQL migrations, SCT handles most PL/SQL conversion but may require manual tuning for complex procedures.

Q5: What factors affect replication instance sizing in DMS?

Answer: Source database size (larger databases need more memory), transaction volume (high write throughput requires more CPU), number of tables (each table uses a separate thread), LOB columns (consume significant bandwidth), and network latency (higher latency requires more buffering). The general rule is: start with dms.r5.large for databases under 1 TB, scale to dms.r5.2xlarge for 1-10 TB, and dms.r5.4xlarge for databases over 10 TB.

Q6: How do you handle schema changes during migration?

Answer: DMS can handle minor schema changes like adding nullable columns automatically. For major changes (dropping columns, changing data types), you need to: 1) Stop the migration task, 2) Apply the schema change to both source and target, 3) Re-validate the table mappings, 4) Resume or recreate the task. For ongoing replication, coordinate schema changes with migration windows. Use DMS schema conversion tracking to detect and handle drift.

Q7: What is the maximum LOB size DMS can handle?

Answer: By default, DMS limits LOB data to 32 KB. You can configure the LobMaxSize setting to handle larger LOBs up to 1 GB, but this impacts performance because DMS must query the source for LOB values rather than reading them from the log. For very large objects, consider using S3 as an intermediary or application-level extraction. The trade-off is between LOB completeness and replication throughput.

Q8: How do you minimize downtime during migration?

Answer: 1) Use Full Load + CDC pattern, 2) Set up CDC before starting the full load to capture changes from the beginning, 3) Keep the source database running during migration, 4) Validate the target thoroughly before cutover, 5) Switch application connections to the target during a brief maintenance window, 6) The actual downtime is limited to the time it takes to switch connection strings and verify the target is current. Plan the cutover during low-traffic periods and have a rollback plan ready.

Common Pitfalls

PitfallConsequenceSolution
No primary key on tablesFull table scans on CDC updatesEnsure PK exists before migration
Insufficient LOB sizeTruncated LOB dataSet LobMaxSize based on actual data
Missing network connectivityEndpoint connection failuresTest connectivity before task creation
Collation mismatchesCharacter encoding errorsConfigure HandleCollationDiff
Schema drift during migrationTask failuresMonitor and coordinate schema changes
No validation post-migrationUndetected data corruptionRun row counts + checksums
Under-sized replication instanceCDC lag, slow throughputRight-size based on workload
No rollback planExtended downtime on failurePrepare rollback procedures

QuizBox

See Also

šŸ”’

Premium Content

AWS Data Migration for Data Engineers

You've previewed the first section. Unlock this full lesson and 900+ advanced tutorials with a Premium plan.

šŸŽÆEnd-to-end Projects
šŸ’¼Interview Prep
šŸ“œCertificates
šŸ¤Community Access

Already a member? Log in

Advertisement