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
Real-World Project Structure
A production database migration project follows a structured approach:
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 Class | vCPUs | RAM | Use Case | Throughput |
|---|---|---|---|---|
| dms.t3.micro | 2 | 1 GB | Testing/development | ~10 MB/s |
| dms.t3.medium | 2 | 4 GB | Small databases (<100 GB) | ~50 MB/s |
| dms.r5.large | 2 | 16 GB | Medium databases (100 GB - 1 TB) | ~100 MB/s |
| dms.r5.2xlarge | 8 | 64 GB | Large databases (1-10 TB) | ~300 MB/s |
| dms.r5.4xlarge | 16 | 128 GB | Very 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
| Factor | Impact | Recommendation |
|---|---|---|
| LOB Columns | Slow replication | Set LobMaxSize appropriately |
| Primary Key | CDC performance | Ensure PK exists on all tables |
| Table Count | Parallel threads | Each table uses one thread |
| Network Latency | Throughput | Use VPC peering or Direct Connect |
| Index Creation | Load speed | Disable indexes during load |
| Transaction Volume | CDC lag | Size instance for peak throughput |
| Collation Mismatch | Failures | Configure HandleCollationDiff |
| Schema Changes | Task failures | Stop, apply, resume task |
Security Considerations
| Layer | Control | Implementation |
|---|---|---|
| Authentication | IAM Roles | DMS service role for AWS access |
| Database Auth | Native credentials | Username/password for source/target |
| Encryption at Rest | SSE-KMS | Replication instance + storage |
| Encryption in Transit | SSL/TLS | SslMode: require on endpoints |
| Network | VPC + Security Groups | Private subnets, restricted ports |
| Audit | CloudTrail | All DMS API calls logged |
| Secrets | Secrets Manager | Store DB credentials securely |
| Monitoring | CloudWatch | CDC 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
| Pitfall | Consequence | Solution |
|---|---|---|
| No primary key on tables | Full table scans on CDC updates | Ensure PK exists before migration |
| Insufficient LOB size | Truncated LOB data | Set LobMaxSize based on actual data |
| Missing network connectivity | Endpoint connection failures | Test connectivity before task creation |
| Collation mismatches | Character encoding errors | Configure HandleCollationDiff |
| Schema drift during migration | Task failures | Monitor and coordinate schema changes |
| No validation post-migration | Undetected data corruption | Run row counts + checksums |
| Under-sized replication instance | CDC lag, slow throughput | Right-size based on workload |
| No rollback plan | Extended downtime on failure | Prepare rollback procedures |