Why AWS DMS Is Rarely the Best MySQL Migration Option
For a same-engine MySQL move, native binlog replication beats AWS DMS on the two things that matter: data fidelity and operational simplicity. Here is the reasoning, and the war stories.
Updated July, 2026 • 6 min read
Introduction
AWS Database Migration Service (DMS) is the branded, managed answer to "how do I move a database", so it tends to get picked by default. For a homogeneous, same-engine MySQL migration, that default is usually wrong.
DMS is a logical, row-reconstructing pipeline. It reads rows from the source, holds them in an internal representation, and re-encodes them on the way to the target. That is a genuine feature when you are crossing engines (Oracle to Postgres) or transforming schemas. It is a liability when your only goal is to move MySQL bytes from A to B unchanged.
I was engaged to test and document a MySQL migration off another cloud onto AWS RDS MySQL 8.0. A prior light assessment had validated DMS as the approach. Testing it against the real schema changed the recommendation completely. This is why.
The setup
- Source: MySQL on another cloud provider (with a read replica available).
- Target: a fresh AWS RDS MySQL 8.0 instance.
- Scale: 250+ tables, on the order of 1 TB on disk. A handful of tables held the bulk of the data, and the two biggest held 30+ million and 10+ million rows of binary-encoded application data.
The killer objection: DMS re-encodes character data
Here is the whole argument in one line. DMS reads logical rows, holds them internally, and re-encodes them to the target column's character set on write. Native binlog replication ships the row bytes exactly as they were written.
That mattered here because the schema was a character-set museum: hundreds of utf8mb3 columns, over 200 latin1 columns, and a single utf8mb4 column.
The target was correctly standardised on utf8mb4. Converting utf8mb3 to utf8mb4 is a safe subset operation. The problem was those latin1 columns, because a lot of them were not text at all. They held binary payloads:
- The largest table, at 30+ million rows, was 100% non-ASCII: a binary payload living in a latin1 column.
- Two more tables, at 10+ million rows each, held binary byte encodings.
- Serialised objects, metadata blobs, config, tokens, and hashes were scattered across dozens more columns.
latin1 bytes in the 0x80 to 0xFF range are valid latin1 but not valid UTF-8. If DMS pulls those bytes and re-encodes latin1 to utf8mb4 on the target, one of two things happens: the insert fails, or, worse, it silently "fixes" the bytes and corrupts binary data the application can no longer decode. You would not find out until something failed to render in production.
Binlog replication has no opinion about character sets. It replays the exact byte sequence the source committed. That is precisely what you want when a column is a binary payload wearing a latin1 label.
Audit the bytes, not the column type
The interesting finding was not "we have latin1 columns". It was that the character-set label lied about the content. Columns named things like status, action, or state, which should be plain ASCII enums, were 100% non-ASCII across tens of thousands of rows. Most likely serialised symbols, or UTF-8 bytes written through a mismatched connection charset over the application's lifetime.
The general lesson: before you trust any character-set-transforming migration tool, audit what is actually in the bytes, not what the column type claims. I wrote a script that scanned every latin1 column for bytes above 0x7F and bucketed the results:
- Binary or encoded payloads: do not touch.
- Tokens and hashes: safe.
- Suspicious enum columns: ask the dev team.
- Genuine user text: safe to convert.
Only the last bucket is a candidate for character-set conversion. DMS would have treated all four identically.
The other friction DMS adds
Character set was the disqualifier. The assessment also surfaced a stack of secondary DMS-specific work that native replication simply makes disappear:
- Primary keys. DMS change data capture needs a primary key per table. Three tables had none (a migrations bookkeeping table, a small selector table, and an empty table). With DMS those are full-load only, excluded from ongoing capture, and you reason about each individually. Binlog replication in ROW format replicates them regardless.
- LOB handling. Dozens of tables had TEXT or MEDIUMTEXT columns. DMS forces a choice between full LOB mode (correct, slow) and limited LOB mode (fast, but it truncates anything past a size limit). You size that limit against your widest realistic value and hope. Native replication has no LOB mode to tune.
- Foreign keys. Dozens of foreign-key constraints. DMS does not replicate the constraints themselves, so you script them separately and apply them after the full load with checks disabled to avoid ordering failures. More moving parts to get wrong.
- Parallel load tuning. The big tables need per-table parallel-load rules, load-order priorities, and a task-settings document that runs to dozens of lines of memory limits, commit rates, and buffer sizes. Every knob is a way to get subtly wrong behaviour.
None of this is insurmountable. The point is that it is all incidental complexity that exists only because you chose a logical pipeline for a job that did not need one.
What I did instead
A dump-and-load to seed, then native binlog replication to catch up and stay in sync, then a short cutover window. The mechanics are a separate write-up (see the companion how-to, Migrating MySQL to RDS with dump-and-load plus native replication), but the shape is:
- Take a consistent dump from the replica that also records the master's binlog position.
- Load it into RDS from an EC2 instance.
- Point RDS at the source master and let binlog replication apply the delta since the dump.
- Verify every table with
CHECKSUM TABLE.
Verification is where byte-exactness earns its keep
CHECKSUM TABLE on every table, source versus target, batched into parallel groups, runs in about an hour for a data set this size. When the checksums match on the binary latin1 tables, you have positive proof the binary bytes arrived unchanged. That is the evidence a re-encoding pipeline structurally cannot give you, because it changed the bytes by design.
When DMS actually is the right call
To be fair to the tool, DMS earns its place when:
- You are crossing engines and need schema and type conversion. The logical pipeline is the whole point.
- You need continuous capture with in-flight transformation, filtering, or re-partitioning.
- You cannot get binlog access on the source, so native replication is off the table.
- The source and target character sets genuinely match and the data is clean text, so re-encoding is a no-op and you value the managed orchestration.
The failure mode is reaching for DMS as the default for homogeneous MySQL just because it is the "migration service". For same-engine moves, native replication is simpler, cheaper, and byte-exact. Match the tool to the job.
Takeaways
- A migration tool that re-encodes character sets is a data-transformation tool. If you do not want your data transformed, do not use one.
- Audit the bytes, not the column type. latin1 columns full of serialised objects and binary payloads are common and invisible to the schema.
CHECKSUM TABLEis your proof of fidelity. Design the migration so you can run it and have it match.- RDS is a managed instance: replication is driven by stored procedures, not
CHANGE REPLICATION SOURCE TO. - Never trust MySQL 8.0 cached row estimates to validate a load. Count, or checksum.
- Budget migration time on load throughput, which is RDS-write-bound, not on raw data size.