Runbook Source DBA — Database Migration Tool v2.0 · 2026-07-08 · All Engines

Live Migration Runbook — All Engines

Zero-downtime cutover guide for SQL Server, MySQL, PostgreSQL, MongoDB, Cosmos DB, and DynamoDB.
Covers on-prem VMs, EC2, AWS RDS, Azure managed services, and Atlas. Retention minimum: 30 days on all engines.

SQL Server / Azure SQL / RDS MySQL / MariaDB / Aurora PostgreSQL / Aurora PG MongoDB / Atlas Cosmos DB DynamoDB

What Is Live Migration?

Live migration keeps the target continuously in sync with the source so application downtime during cutover is measured in seconds. Two sequential phases apply to all engines:

1Full Load

Bulk-copy all existing rows using PK-range seeding (relational) or cursor batching (document). Checkpoint-resumable. Source stays fully online.

2Change Stream Catch-up

After the full load, switch to the engine's native change stream. The tool tails changes at sub-second latency and applies them to the target as plain DML.

i
Change-stream technology by engine:
SQL Server → CDC (cdc_tail_loop)  |  MySQL → Binary Log (ROW format)  |  PostgreSQL → WAL logical replication  |  MongoDB → Change Streams / oplog  |  Cosmos DB → Change Feed  |  DynamoDB → DynamoDB Streams

Engine Support Matrix

EngineDeploymentChange MechanismMin RetentionAgent?Tool Mode
SQL Server 2016–2022On-prem / VM / EC2CDC (native)30 daysRequiredCDC Stream
SQL Server on RDSAWS RDSCDC (managed Agent)30 daysManaged by RDSCDC Stream
Azure SQL DatabaseAzure PaaSCDC (limited) or CT Sync30 daysNo — use CT SyncCT Sync preferred
MySQL 5.7 / 8.0On-prem / VM / EC2Binary Log ROW format30 daysN/ABinlog Stream
MySQL / Aurora MySQLAWS RDS / AuroraBinary Log (param group)30 daysN/ABinlog Stream
Azure DB for MySQLAzure PaaS FlexibleBinary Log30 daysN/ABinlog Stream
PostgreSQL 10+On-prem / VM / EC2WAL logical replication30-day equivN/AWAL Stream
PostgreSQL / Aurora PGAWS RDS / AuroraWAL (param group)30-day equivN/AWAL Stream
Azure DB for PostgreSQLAzure PaaS FlexibleWAL (wal_level=logical)30-day equivN/AWAL Stream
MongoDB 3.6+On-prem / VM / EC2 / AtlasChange Streams (oplog)30 daysN/AChange Stream
Cosmos DB Core APIAzure PaaSChange Feed (built-in)PermanentN/AChange Feed
DynamoDBAWS managedDynamoDB Streams24h max (hard limit)N/ADynamoDB Stream
!
DynamoDB exception: AWS enforces a hard 24-hour retention that cannot be extended. Design the migration so the stream is never paused more than 12 hours. If paused longer a reseed is required.

Retention Policy — Minimum 30 Days

30
Days minimum retention on all change-stream mechanisms.
Protects against weekend outages, emergency freezes, incident response windows, and time needed to investigate and re-seed a failed stream. Most engine defaults are 3–7 days and must be explicitly increased.
!
If the stream stops for longer than the retention window the checkpoint position becomes invalid. The tool will error with "LSN out of range" / "binlog position no longer available" / "replication slot invalidated". A full reseed is then required and can take hours on large databases. Setting 30 days eliminates this risk for any operational pause under one month.

Cutover Flow (All Engines)

🗄
Source
(LIVE)
📋
Full Load
(Phase 1)
🔄
Change Stream
(Phase 2)
Quiesce
Source
🕐
Wait Lag
= 0
Stop Stream
Flip Apps
🎯
Target
(NEW LIVE)

Downtime = time between "Quiesce Source" and "Flip Apps" — typically 10–60 seconds. Sequence is identical across all engines; only the change mechanism differs.


🔷
SQL Server — CDC Stream
On-Premises · VM · EC2 · AWS RDS · Azure SQL Database

SQL Server — Prerequisites

RequirementValue / CheckBlocking?
SQL Server version2012+ (CDC net changes)Yes
SQL Server AgentRunning — required for CDC capture + cleanup jobsYes
Recovery modelFULL or BULK_LOGGED — not SIMPLEYes
Migration logindb_owner or db_datareader + SELECT on cdc schemaYes
Log disk space≥ 30 days of daily log growthPlan ahead
ODBC Driver 17+Required on migration hostYes
USE EndriasBridge;
GO
-- Agent status
SELECT servicename, status_desc FROM sys.dm_server_services WHERE servicename LIKE 'SQL Server Agent%';
-- Recovery model
SELECT name, recovery_model_desc, log_reuse_wait_desc FROM sys.databases WHERE name = DB_NAME();
-- CDC status
SELECT name, is_cdc_enabled FROM sys.databases WHERE name = DB_NAME();
-- Login check
SELECT IS_MEMBER('db_owner') AS is_db_owner, SUSER_SNAME() AS login;
-- Tables without PK
SELECT s.name+'.'+t.name AS table_no_pk
FROM   sys.tables t JOIN sys.schemas s ON t.schema_id=s.schema_id
LEFT JOIN sys.indexes i ON i.object_id=t.object_id AND i.is_primary_key=1
WHERE  s.name NOT IN('cdc','sys') AND i.object_id IS NULL;

Grant permissions

USE EndriasBridge;
GO
ALTER ROLE db_owner ADD MEMBER [Migration];              -- source
ALTER ROLE db_owner ADD MEMBER [MigrationUser];       -- target (Azure SQL / RDS)

SQL Server — Enable CDC on Database

USE EndriasBridge;
GO
EXEC sys.sp_cdc_enable_db;
GO
-- Grant cdc schema after it is created
GRANT SELECT ON SCHEMA::cdc TO [Migration];
GO
-- Verify
SELECT name, is_cdc_enabled FROM sys.databases WHERE name = DB_NAME();
SELECT name, enabled FROM msdb.dbo.sysjobs WHERE name LIKE 'cdc.EndriasBridge%';
!
Azure SQL Database — mandatory extra step. sp_cdc_enable_db does not auto-create the capture job on Azure SQL. You must call sp_cdc_add_job immediately after or CDC will silently never poll the transaction log.
-- Azure SQL Database ONLY — run immediately after sp_cdc_enable_db
-- (on-prem / EC2 / RDS: SQL Agent creates the job automatically — skip this)
EXEC sys.sp_cdc_add_job @job_type = N'capture';
GO
!
Containment blocks CDC enablement. If sp_cdc_enable_db fails with a containment-related error, the database is set to CONTAINMENT = PARTIAL. Fix: drop every contained database user (SELECT name FROM sys.database_principals WHERE authentication_type_desc = 'DATABASE'), then run ALTER DATABASE EndriasBridge SET CONTAINMENT = NONE from master (requires single-user mode), then re-run sp_cdc_enable_db. The ALTER will fail if even one contained user remains — verify zero rows before proceeding.

SQL Server — Enable CDC per Table

USE EndriasBridge;
GO
-- Generate enable statements for all tables not yet CDC-enabled
SELECT
    'EXEC sys.sp_cdc_enable_table @source_schema=N'''+s.name+''','
    +'@source_name=N'''+t.name+''','
    +'@role_name=NULL,@supports_net_changes=1;' AS stmt
FROM   sys.tables t
JOIN   sys.schemas s        ON t.schema_id=s.schema_id
LEFT JOIN cdc.change_tables ct ON ct.source_object_id=t.object_id
WHERE  s.name NOT IN('cdc','sys') AND ct.capture_instance IS NULL
ORDER BY s.name, t.name;
-- Paste and execute the generated statements, then verify:
SELECT s.name AS schema_name, t.name AS table_name,
       ct.capture_instance, ct.supports_net_changes
FROM   cdc.change_tables ct
JOIN   sys.tables  t ON ct.source_object_id=t.object_id
JOIN   sys.schemas s ON t.schema_id=s.schema_id
ORDER BY s.name, t.name;
-- supports_net_changes must = 1 on all rows

SQL Server — Set 30-Day Retention

!
Default retention is 4,320 minutes (3 days). Set to 43,200 minutes (30 days) immediately after enabling CDC. Any pause longer than 3 days would otherwise force a full reseed.
USE EndriasBridge;
GO

-- Cleanup: 30 days retention, higher threshold for large tables
EXEC sys.sp_cdc_change_job
    @job_type  = 'cleanup',
    @retention = 43200,   -- 30 days x 1440 min/day
    @threshold = 50000;  -- rows per cleanup cycle (default 5000)

-- Capture: reduce polling to 1 second for lower latency
EXEC sys.sp_cdc_change_job
    @job_type        = 'capture',
    @pollinginterval = 1;

-- Restart jobs to apply new settings
EXEC msdb.dbo.sp_stop_job  N'cdc.EndriasBridge_cleanup';
EXEC msdb.dbo.sp_start_job N'cdc.EndriasBridge_cleanup';
EXEC msdb.dbo.sp_stop_job  N'cdc.EndriasBridge_capture';
EXEC msdb.dbo.sp_start_job N'cdc.EndriasBridge_capture';
GO

-- Verify
SELECT job_type, retention, threshold, pollinginterval FROM cdc.cdc_jobs;
-- cleanup.retention must show 43200
i
Monitor log growth weekly: SELECT * FROM sys.dm_db_log_space_usage. On high-churn databases ensure the log drive holds at least 30 days of daily change volume.

SQL Server on AWS RDS — Notes

!RDS differences
  • SQL Server Agent is available on RDS (managed by AWS) — no manual start needed
  • sp_cdc_enable_db and sp_cdc_enable_table work normally
  • RDS automated backup retention: set to 35 days in RDS console (max allowed)
  • Multi-AZ failover: CDC resumes automatically after failover when capture job restarts
  • Monitor FreeStorageSpace CloudWatch metric for log growth
CLIBackup retention via CLI
# Set backup retention to 35 days (RDS max)
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --backup-retention-period 35 \
  --apply-immediately

-- Also set CDC job retention:
EXEC sys.sp_cdc_change_job
  @job_type='cleanup', @retention=43200;

Azure SQL Database — CT Sync (Recommended)

!
SQL Server Agent is not user-accessible in Azure SQL Database. CDC is supported but Agent is managed by Microsoft. Recommended: CT Sync (Change Tracking) — no Agent dependency, fully supported, 30-day retention configurable.

Enable Change Tracking (CT Sync)

-- Database level: 30-day retention
ALTER DATABASE EndriasBridge
SET CHANGE_TRACKING = ON
    (CHANGE_RETENTION = 30 DAYS, AUTO_CLEANUP = ON);
GO

-- Generate per-table enable statements
SELECT 'ALTER TABLE '+s.name+'.'+t.name
      +' ENABLE CHANGE_TRACKING WITH (TRACK_COLUMNS_UPDATED=ON);'
FROM   sys.tables t
JOIN   sys.schemas s ON t.schema_id=s.schema_id
WHERE  s.name NOT IN('cdc','sys')
ORDER BY s.name, t.name;

Or: CDC on Azure SQL DB (if Agent-managed CDC is acceptable)

EXEC sys.sp_cdc_enable_db;
GO
-- Required on Azure SQL: manually add the capture job (not auto-created unlike on-prem)
EXEC sys.sp_cdc_add_job @job_type = N'capture';
GO
EXEC sys.sp_cdc_change_job @job_type='cleanup', @retention=43200;
GO
-- Note: Cannot restart Agent jobs manually on Azure SQL DB

SQL Server — Verify and Start Stream

i
Full load and CDC streaming are two separate steps — do NOT start streaming immediately when migration begins.
The correct sequence is:
  1. CDC must be enabled on the source database before the full load starts. This is a one-time setup step, not something you do per-run.
  2. Start Migration (full load) — MigrationBridge records the current sys.fn_cdc_get_max_lsn() as the LSN anchor before copying a single row. The source stays fully live and accepting writes throughout.
  3. Full load completes — all existing rows are on the target.
  4. Then start CDC Stream — the tool replays every change that accumulated on the transaction log during the full load from that saved LSN forward, catches up to real-time, then enters sub-second tail mode. No changes are missed even if the full load took 90 minutes and thousands of rows changed on the source during that time.
If you start CDC streaming immediately (before running the full load), you get only changes from "now" forward — all pre-existing rows will be missing on the target. That is only valid when the target is already pre-seeded by other means.

Pre-stream verification checklist

  1. Confirm is_cdc_enabled = 1 and all tables show supports_net_changes = 1
  2. Confirm capture Agent job is Running in msdb.dbo.sysjobactivity
  3. Confirm cdc.cdc_jobs.retention = 43200
  4. Run full load: Source DBA → Migrate tab → Sync Mode = CDC Stream → click Start Migration
    Tool saves LSN anchor at this point. Full load runs. Source stays live.
  5. Wait for full load to complete (watch Migration Log — all tables show row counts)
  6. Start CDC Stream — tool replays changes from the saved LSN, then enters tail mode
  7. Validate: run a test UPDATE on source, confirm it appears on target within 1 second
USE EndriasBridge;
-- Quick validation after streaming starts: check lag
SELECT
    sys.fn_cdc_get_max_lsn()   AS source_head_lsn,
    sys.fn_cdc_get_min_lsn(N'Person_Address') AS capture_from_lsn;
-- source_head_lsn should equal (or trail by milliseconds) the last LSN the tool processed

-- Test change to confirm replication
UPDATE Person.Address SET City = 'StreamTest' WHERE AddressID = 1;
-- Verify on PostgreSQL target: SELECT city FROM person.address WHERE addressid = 1;
-- Expected: 'StreamTest' within ~1 second

🐬
MySQL / MariaDB — Binary Log Stream
On-Premises · VM · EC2 · AWS RDS · Aurora MySQL · Azure DB for MySQL

MySQL — Prerequisites

ParameterRequired ValueWhy
binlog_formatROWRow-level changes required — STATEMENT / MIXED not supported
log_binONBinary logging must be enabled
server_idUnique non-zero integerRequired for replication protocol
binlog_row_imageFULL (recommended)MINIMAL omits unchanged columns causing UPDATE gaps
expire_logs_days (5.7) /
binlog_expire_logs_seconds (8.0)
30 days / 2,592,000 sec30-day minimum retention requirement
Migration userREPLICATION SLAVE + REPLICATION CLIENTRequired to read binlog stream
-- Check current settings
SHOW VARIABLES LIKE 'binlog_format';             -- Must be ROW
SHOW VARIABLES LIKE 'log_bin';                   -- Must be ON
SHOW VARIABLES LIKE 'server_id';                 -- Must be non-zero
SHOW VARIABLES LIKE 'binlog_row_image';          -- Recommend FULL
SHOW VARIABLES LIKE 'expire_logs_days';          -- 5.7: must be 30
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds'; -- 8.0: must be 2592000
SHOW MASTER STATUS;                               -- Current binlog file + position

MySQL On-Prem / VM / EC2 — Setup

Step 1 — Edit my.cnf

# /etc/mysql/mysql.conf.d/mysqld.cnf  (Ubuntu/Debian)
# /etc/my.cnf                         (RHEL/CentOS/Amazon Linux)

[mysqld]
server-id                    = 1
log_bin                      = /var/lib/mysql/mysql-bin
binlog_format                = ROW
binlog_row_image             = FULL
max_binlog_size              = 512M

# MySQL 5.7:
expire_logs_days             = 30

# MySQL 8.0+ (replaces expire_logs_days):
binlog_expire_logs_seconds   = 2592000   # 30 days x 86400

Step 2 — Restart and verify

sudo systemctl restart mysql    # Ubuntu/Debian
sudo systemctl restart mysqld   # RHEL/CentOS
mysql -u root -p -e "SHOW VARIABLES LIKE 'binlog_format';"
mysql -u root -p -e "SHOW MASTER STATUS;"

Step 3 — Create replication user

CREATE USER 'migration'@'%' IDENTIFIED BY 'StrongP@ssw0rd!';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'migration'@'%';
GRANT SELECT ON mysql.* TO 'migration'@'%';
FLUSH PRIVILEGES;

MySQL on AWS RDS / Aurora — Setup

i
On RDS and Aurora, my.cnf is not editable. Use Parameter Groups (AWS console or CLI).

Parameter Group

# AWS CLI
aws rds modify-db-parameter-group \
    --db-parameter-group-name my-mysql8-params \
    --parameters \
      "ParameterName=binlog_format,ParameterValue=ROW,ApplyMethod=immediate" \
      "ParameterName=binlog_row_image,ParameterValue=FULL,ApplyMethod=immediate" \
      "ParameterName=binlog_expire_logs_seconds,ParameterValue=2592000,ApplyMethod=immediate"

Retain binlogs for 30 days (RDS stored procedure)

-- This overrides the parameter group for log file retention
CALL mysql.rds_set_configuration('binlog retention hours', 720);  -- 720h = 30 days

-- Verify
CALL mysql.rds_show_configuration;
-- Look for: binlog retention hours = 720

Create replication user on RDS

CREATE USER 'migration'@'%' IDENTIFIED BY 'StrongP@ssw0rd!';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'migration'@'%';
FLUSH PRIVILEGES;
!
Aurora MySQL Serverless v1 does NOT support binlog replication. Use Aurora Serverless v2 or provisioned Aurora MySQL.

Azure Database for MySQL Flexible Server — Setup

# Azure CLI
az mysql flexible-server parameter set \
    --resource-group myRG --server-name my-mysql-flex \
    --name binlog_format --value ROW

az mysql flexible-server parameter set \
    --resource-group myRG --server-name my-mysql-flex \
    --name binlog_expire_logs_seconds --value 2592000

# Verify in portal: Server parameters → search "binlog"

MySQL — Retention Summary

DeploymentSettingValue (30 days)How
On-prem / VM / EC2 MySQL 5.7expire_logs_days30my.cnf + restart
On-prem / VM / EC2 MySQL 8.0binlog_expire_logs_seconds2,592,000my.cnf + restart
AWS RDS MySQLmysql.rds_set_configuration720 hoursSQL CALL, no restart
AWS Aurora MySQLmysql.rds_set_configuration720 hoursSQL CALL
Azure DB for MySQL Flexiblebinlog_expire_logs_seconds2,592,000Azure CLI / portal

🐘
PostgreSQL — WAL Logical Replication
On-Premises · VM · EC2 · AWS RDS · Aurora PostgreSQL · Azure DB for PostgreSQL

PostgreSQL — Prerequisites

ParameterRequired ValueWhy
wal_levellogicalMust be logical for replication slots
max_replication_slots≥ number of source databasesOne slot per migration connection
max_wal_senders≥ 10WAL streaming connections
wal_keep_size (PG 13+)≥ 51,200 MBWAL backstop when no slot active
Replication roleREPLICATION or superuserRequired to create replication slot
pg_hba.confAllow replication from migration host CIDRConnection guard
SHOW wal_level;
SHOW max_replication_slots;
SELECT * FROM pg_replication_slots;

PostgreSQL On-Prem / VM / EC2 — Setup

Step 1 — postgresql.conf

# /etc/postgresql/15/main/postgresql.conf
wal_level               = logical
max_replication_slots   = 10
max_wal_senders         = 10
wal_keep_size           = 51200    # 50 GB backstop if slot dropped

Step 2 — pg_hba.conf

# Allow replication from migration host
host    replication     migration_user  10.0.0.0/24     scram-sha-256

Step 3 — Create replication user

CREATE USER migration_user WITH REPLICATION LOGIN PASSWORD 'StrongP@ssw0rd!';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO migration_user;

Step 4 — Restart and create slot

sudo systemctl restart postgresql

-- Create logical replication slot (tool creates this automatically)
SELECT pg_create_logical_replication_slot('sourcedba_slot', 'pgoutput');

-- Monitor retained WAL
SELECT slot_name, active,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM   pg_replication_slots;
!
Active replication slots hold WAL indefinitely. If the stream is stopped without dropping the slot, WAL accumulates until disk exhaustion. Monitor retained_wal weekly. Drop the slot when migration is complete.

PostgreSQL on AWS RDS / Aurora — Setup

# Parameter Group (reboot required for static params)
aws rds modify-db-parameter-group \
    --db-parameter-group-name my-pg15-params \
    --parameters \
      "ParameterName=rds.logical_replication,ParameterValue=1,ApplyMethod=pending-reboot" \
      "ParameterName=max_replication_slots,ParameterValue=10,ApplyMethod=pending-reboot" \
      "ParameterName=max_wal_senders,ParameterValue=10,ApplyMethod=pending-reboot"

aws rds reboot-db-instance --db-instance-identifier my-pg-db
-- After reboot: verify
SHOW wal_level;   -- Must show: logical

-- Create replication user (use rds_replication role, not SUPERUSER)
CREATE USER migration_user WITH PASSWORD 'StrongP@ssw0rd!';
GRANT rds_replication TO migration_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO migration_user;

Azure DB for PostgreSQL Flexible Server — Setup

# Azure CLI
az postgres flexible-server parameter set \
    --resource-group myRG --server-name my-pg-flex \
    --name wal_level --value logical

az postgres flexible-server parameter set \
    --resource-group myRG --server-name my-pg-flex \
    --name max_replication_slots --value 10

az postgres flexible-server restart \
    --resource-group myRG --name my-pg-flex
CREATE USER migration_user WITH REPLICATION LOGIN PASSWORD 'StrongP@ssw0rd!';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO migration_user;

PostgreSQL — WAL Retention (30-Day Equivalent)

!
PostgreSQL has no time-based WAL cleanup setting. Retention is controlled by active replication slots (hold WAL indefinitely while active) and wal_keep_size (backstop when no slot). Set both.
DeploymentSettingValueNotes
On-prem / VM / EC2wal_keep_size51,200 MBpostgresql.conf backstop
On-prem / VM / EC2Replication slotActive = holds WALMonitor retained_wal weekly
AWS RDS / Aurora PGrds.logical_replication=1Active slot holds WALRDS managed
Azure DB PG Flexiblewal_level=logical + slotActive slot holds WALMonitor via pg_replication_slots
-- Weekly check: alert if retained_wal > 20 GB or slot age > 25 days
SELECT
    slot_name, active,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
FROM   pg_replication_slots;

🌿
MongoDB — Change Streams
On-Premises · VM · EC2 · MongoDB Atlas

MongoDB — Prerequisites (Replica Set Required)

!
MongoDB Change Streams require a Replica Set or Sharded Cluster. Standalone instances do not support change streams. Convert to a single-node replica set first.
RequirementValueCheck
Replica setRequired (rs0, rs1, etc.)rs.status()
MongoDB version3.6+ (Change Streams GA)db.version()
Oplog sizeSized for 30-day window (see below)db.printReplicationInfo()
Migration userreadAnyDatabase + clusterMonitordb.getUser('migration')

Convert standalone to single-node replica set

# mongod.conf — add replication section
replication:
  replSetName: "rs0"

# Restart then initiate
mongosh --eval "rs.initiate()"

Create migration user

use admin
db.createUser({
  user: "migration",
  pwd:  "StrongP@ssw0rd!",
  roles: [
    { role: "readAnyDatabase", db: "admin" },
    { role: "clusterMonitor",  db: "admin" }
  ]
})

MongoDB — Oplog Retention (30-Day Window)

i
MongoDB oplog is a capped collection sized by disk space, not time. MongoDB 4.4+ adds a minimum retention window in hours. Set both.
// MongoDB 4.4+ — set 30-day minimum window (720 hours)
use admin
db.adminCommand({
  replSetResizeOplog: 1,
  minRetentionHours: 720
})

// All versions — resize oplog (adjust size for your write volume)
db.adminCommand({ replSetResizeOplog: 1, size: 102400 })  // 100 GB in MB
# mongod.conf — persistent settings
replication:
  replSetName: "rs0"
  oplogSizeMB: 102400          # 100 GB
  oplogMinRetentionHours: 720  # 30 days (MongoDB 4.4+)
// Verify oplog window
rs.printReplicationInfo()
// Expected: "log length start to end: 2592000secs (720hrs)"

MongoDB Atlas — Change Streams

Atlas clusters are always replica sets. Change Streams available by default. Use M30+ with adequate storage for the 30-day oplog window requirement. Create migration user via Atlas UI: Database Access → readAnyDatabase + clusterMonitor.
# Connection string for Source DBA (Atlas):
# mongodb+srv://migration:<pwd>@cluster0.abcde.mongodb.net/?retryWrites=true&w=majority

🌌
Cosmos DB — Change Feed
Azure PaaS — Core (SQL) API / Mongo API

Cosmos DB — Change Feed

Change Feed is built into Cosmos DB — no setup required. Retention is permanent. No window to configure. Read permissions on the container are sufficient.
ItemBehaviour
RetentionPermanent — change feed persists indefinitely
DeletionsNot in Change Feed by default — use soft-delete pattern
Starting positionBeginning of time, specific timestamp, or "now"
OrderingGuaranteed per logical partition key only
PermissionsRead on the container (RBAC or connection string key)

Deletion handling — soft-delete pattern

// Cosmos DB Change Feed does not emit deleted documents.
// Mark deletions with a flag; tool reads isDeleted=true and issues DELETE on target.
{
  "id": "doc-123",
  "isDeleted": true,
  "deletedAt": "2026-07-08T00:00:00Z"
}
DynamoDB Streams
AWS Managed — 24-hour platform hard limit

DynamoDB Streams

!
AWS enforces a hard 24-hour retention on DynamoDB Streams. This cannot be extended. Design the migration so the stream is never paused more than 12 hours. If paused longer → reseed required for tables with changes in the gap.

Enable streams

# NEW_AND_OLD_IMAGES required for full UPDATE tracking
aws dynamodb update-table \
    --table-name MyTable \
    --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES

aws dynamodb describe-table --table-name MyTable \
    --query "Table.StreamSpecification"

IAM permissions

{
  "Effect": "Allow",
  "Action": [
    "dynamodb:GetRecords", "dynamodb:GetShardIterator",
    "dynamodb:DescribeStream", "dynamodb:ListStreams",
    "dynamodb:Scan", "dynamodb:Query"
  ],
  "Resource": [
    "arn:aws:dynamodb:us-east-1:123456789:table/MyTable",
    "arn:aws:dynamodb:us-east-1:123456789:table/MyTable/stream/*"
  ]
}
Stream View TypeUse CaseDELETEs?
KEYS_ONLYAudit onlyKey only
NEW_IMAGEINSERT + UPDATE onlyNo content
OLD_IMAGEDELETE trackingPre-delete image
NEW_AND_OLD_IMAGESRequired for full migrationBoth images

Pre-Cutover Checklist (All Engines)

Execute Cutover (All Engines)

!
Once application writes stop on the source the clock is running. Complete the entire sequence in under 5 minutes.
Stop application writes to source
Application team — maintenance mode / drain connections

Do NOT stop the change stream yet. It must keep running to drain in-flight changes.

Wait for lag = 0
Watch Replication tab in Source DBA

SQL Server

SELECT sys.fn_cdc_get_max_lsn() AS max_lsn;

MySQL

SHOW MASTER STATUS;

PostgreSQL

SELECT pg_current_wal_lsn();

MongoDB

rs.printReplicationInfo()
Final row count spot-check
Source vs Target — highest-change tables

SQL Server / MySQL / PG: SELECT COUNT(*) FROM schema.table on both.
MongoDB: db.collection.countDocuments({}) on both.
Cosmos / DynamoDB: Compare tab in Source DBA.

Stop change stream
Source DBA → Migrate or Replication tab → Stop Stream

Tool saves final position checkpoint. Resume is possible if rollback is needed.

Flip applications to target
Update connection strings / DNS — bring app online

Monitor error rates for 15 minutes. Keep source online in read-only mode for 30 days (rollback window).

Post-Cutover Validation


Troubleshooting

SSSQL Server: LSN out of range — reseed required

Stream stopped for longer than cleanup retention. Fix: set retention to 43,200 min, reseed with Retry Errors on Projects dashboard.

MYMySQL: binlog position no longer available

Binlog purged before 30-day window. Fix: set expire_logs_days=30 or binlog_expire_logs_seconds=2592000, then run full reseed from last checkpoint.

PGPostgreSQL: replication slot invalidated

Slot dropped or WAL recycled past slot's restart_lsn. Drop slot: SELECT pg_drop_replication_slot('sourcedba_slot'). Increase wal_keep_size, recreate slot, reseed.

MGMongoDB: ChangeStreamHistoryLost

Oplog window insufficient. Fix: increase oplog size via replSetResizeOplog + set oplogMinRetentionHours=720, then reseed.

DYDynamoDB: gap in stream — changes missed

Stream paused > 24 hours (platform hard limit). Full reseed required for affected tables. No workaround for 24h limit — plan accordingly.

FKSQL Server: FK violations on child tables

Parent table copy failed (e.g. udt_cols bug fixed in Change 122). Fix parent first, then Retry Errors. Child inserts succeed once parent rows exist on target.

Rollback Plan

!
Keep the source running in read-only / standby mode for 30 days post-cutover (except DynamoDB — see 24h note).

Cutover aborted before flip

  1. Re-enable writes to source — no data lost
  2. Stop change stream in tool
  3. Investigate, fix, re-attempt

Rollback after flip

  1. Stop application writes to target
  2. Reverse migration in Source DBA: swap source ↔ target connections
  3. Run Full or Incremental sync from target back to source
  4. Flip applications back to source

Cleanup after successful migration (SQL Server)

USE EndriasBridge;
GO
EXEC sys.sp_cdc_disable_db;
GO
SELECT name, is_cdc_enabled FROM sys.databases WHERE name=DB_NAME();

Cleanup after successful migration (PostgreSQL)

-- CRITICAL: drop the slot or WAL accumulates indefinitely
SELECT pg_drop_replication_slot('sourcedba_slot');

Retention Quick-Reference — All Engines

EngineDeploymentSettingValue (30 days)Command / Location
SQL ServerOn-prem / VM / EC2 / RDSsp_cdc_change_job @retention43,200 minEXEC sys.sp_cdc_change_job @job_type='cleanup', @retention=43200
Azure SQL DB (CDC)Azure PaaSsp_cdc_change_job @retention43,200 minSame; Agent managed internally
Azure SQL DB (CT)Azure PaaSCHANGE_RETENTION30 DAYSSET CHANGE_TRACKING = ON (CHANGE_RETENTION=30 DAYS)
MySQL 5.7On-prem / VM / EC2expire_logs_days30my.cnf + restart
MySQL 8.0On-prem / VM / EC2binlog_expire_logs_seconds2,592,000my.cnf + restart
MySQL / AuroraAWS RDS / Auroramysql.rds_set_configuration720 hoursCALL mysql.rds_set_configuration('binlog retention hours', 720)
MySQL FlexibleAzure PaaSbinlog_expire_logs_seconds2,592,000Azure CLI parameter set
PostgreSQLOn-prem / VM / EC2wal_keep_size + active slot51,200 MB + slotpostgresql.conf + replication slot active
PostgreSQLAWS RDS / AuroraActive replication slotWAL held by slotSlot active; RDS retains WAL
PostgreSQL FlexibleAzure PaaSActive replication slotWAL held by slotwal_level=logical + slot active
MongoDBOn-prem / VM / EC2oplogMinRetentionHours720 hoursreplSetResizeOplog: 1, minRetentionHours: 720
MongoDB AtlasAtlasCluster tier storageM30+ with adequate diskAtlas console / CLI
Cosmos DBAzure PaaSChange Feed (permanent)No limitNo action required
DynamoDBAWS managedDynamoDB Streams24h hard limitCannot be extended — design migration for < 12h pauses

Source DBA — Live Migration Runbook (All Engines) · v2.0 · 2026-07-08 · SQL Server · MySQL · PostgreSQL · MongoDB · Cosmos DB · DynamoDB · Minimum retention: 30 days