← All cloud projects
CASE STUDY us-east-1 2026
CLOUD PROJECT · 2026

DMS migrates rows, not databases

A MySQL migration to RDS that reported complete success and left the application unable to connect. Every row arrived. Zero users, grants, stored procedures or triggers did. Proven by killing the source database after cutover — which is the only way to know the cutover was real.

AWS DMSRDS MySQLEC2SSM

Built, measured and torn down on 7 August 2026 in us-east-1. Total cost $0.15. The infrastructure no longer exists — the code and write-up are in the repo.

The setup

An employee-directory app on MariaDB (MySQL-compatible), running on an EC2 instance as the stand-in for an on-premises server. RDS MySQL as the target. AWS Database Migration Service moving the data, then the application’s connection string cut over to RDS.

On-prem stand-in · EC2Employeedirectory appMariaDBsourceus-east-1DMSreplication instanceRDS MySQLtargetOperatorscriptsbeforereadfull loadafter cutoverusers + grantskill source

Data path: source MariaDB → DMS full load → RDS MySQL, rows only. Cutover: the app's connection string moves to RDS once users and grants are recreated by hand. Proof: the source is killed and the app keeps working.

requestasync / eventdeploy / controlfailure / failover Hover, tap or Tab to a component to see what it does and why. Swipe sideways for the whole diagram.

An EC2 instance rather than a database on my laptop, which is where the course starts, because that cannot work: DMS runs inside AWS and has no route to a database behind home NAT. Exposing a home database to the internet to fix that would be worse than the lab is worth.

What happened

DMS reported the task complete. Full load, 100%, no errors. Every row was in RDS and the counts matched.

The application then failed on its first connection.

DMS migrates table data. It does not migrate users, grants, stored procedures, triggers, events or views. The app’s database user simply did not exist on the target. Nothing in the task status suggests this — from the migration service’s point of view the job was a complete success, and by its own definition it was.

The fix was to recreate the credentials and grants by hand on the target before cutover. Obvious in hindsight. Not obvious at 100% completion with a green status.

The finding

“The migration succeeded” and “the application works” are different claims, and only one of them is on the DMS console.

That gap is where migration projects actually fail. The data is the part everyone plans for and the part the tooling handles. Everything around the data — the accounts that read it, the procedures that transform it, the jobs that maintain it — is the part you discover at cutover, in a maintenance window, with people waiting.

Proving the cutover was real

Verification here was the interesting part. A migration “looks” done as soon as the app is pointed at the new database — but if the old one is still reachable, connection pooling, a cached DNS entry or a config file you missed can leave you unknowingly still serving from the source.

So after cutover I killed the source database entirely and confirmed the application kept working. That’s the only test that distinguishes “cut over” from “appears to have cut over”.

The traps that bill you

RDS deletion protection blocks deletion and the button just greys out — no explanation. This is the classic “why is my bill still running” trap, and it’s why the teardown script disables protection before attempting delete.

A kept final snapshot keeps billing after the instance is gone.

The DMS replication instance bills hourly whether or not a task is running. It’s easy to leave one idle after the migration completes; the task finishing is not the resource stopping. Delete order is task → replication instance → endpoints, and it’s dependency-enforced.

What it cost

$0.15 for the afternoon. The same stack left running is about $28/month — RDS at roughly $15 and the replication instance at $13. Both are invisible in the EC2 console, which is where people look.