Business Challenge
The migration plan has one sentence that everything else depends on: DMS keeps the target in sync, so the cutover window is minutes, not hours. It is written early, by someone reading a product page, and it is never revisited.
On the night, the application is stopped, the team waits for replication to drain, and the wait does not end when anybody expected. Somebody opens a console to find out how far behind the target is, which is the first time in the project anyone has asked.
AWS is unusually direct about this, in a note on the CDC page itself: “AWS DMS CDC does not provide real-time replication.” Latency varies with source workload, network conditions, replication instance resources, target ingestion capacity and data characteristics, and “There are no SLAs for CDC latency.” It “can increase to several minutes or longer”.
That is not a caveat about degraded operation. It is the guarantee. A freeze window sized on the assumption of second-level lag is sized against something AWS never offered, and the number you need — your latency, on your data, under your load — can only come from measurement.
A full-load task can migrate views, or a combination of tables and views. A CDC-only task, or a full-load task that starts CDC when it completes, migrates “only tables from the source”.
Most migrations use full load plus CDC, because that is the pattern that keeps the window short. That choice quietly removes views from the migration. The target comes up with every row present and correct, and an application that queries a view fails on something that looks nothing like a data problem.
“When the task is created, AWS DMS marks the CDC start point, and it can't be changed. To use a different CDC start point, create a new task.”
This is the parameter people get wrong under pressure, at 2am, having just realised the bulk load finished at a different moment than they assumed. There is no correcting it in place. The only remedy is a new task, which means re-establishing where the change stream should begin — the exact problem you were trying to solve.
On Oracle, starting a CDC task from an SCN or timestamp means you
“miss the results of any open transactions and fail to migrate these
results” — transactions that began before the start position and
committed after it. From DMS 3.5.1 the openTransactionWindow endpoint
setting handles them, by shifting the capture position back by a window you specify
in minutes.
Nothing reports the loss. The rows are simply not there, and they belong to long-running transactions — which in most estates means the batch jobs, the ones whose absence surfaces at month end rather than at cutover.
Architecture
DMS reads the source engine's transaction log through that engine's own API. Everything that follows — what can be captured, how you address a point in the stream, what "start here" even means — is inherited from the log, not from DMS.
One mechanism per engine, and the differences leak
Oracle uses LogMiner or the binary reader against online or archive redo logs, addressed
by SCN. SQL Server uses MS-Replication or MS-CDC and reads the transaction log through
fn_dblog() or fn_dump_dblog(), addressed by LSN. MySQL reads
row-based binlogs, addressed by file and position. PostgreSQL creates a logical
replication slot and reads through the test_decoding plugin.
Each requires source configuration before any of it works — Oracle needs supplemental logging, MySQL needs row-level binary logging — and each imposes its own limits. The clearest is PostgreSQL: it “doesn't support a custom CDC start time”, because the engine has no way to map a timestamp to an LSN the way Oracle and SQL Server can. A runbook written around "start replication from 21:00" is not portable between engines, and the failure appears only when you try it.
Addressing a point in the stream
Three ways to say where CDC begins: a custom start time, which DMS converts to a native point; a native start point stated directly in the engine's own terms; or a checkpoint from a previous task. AWS's own caution is that a timestamp “can indicate multiple native points in the transaction log”, which is why the native form exists.
Checkpoints are the underused one. A running task writes recovery checkpoints, and a
checkpoint can start another task at that same position — the supported way
to fan a migration out to a second target without re-loading, or to recover a failed task
at a known point. Setting TaskRecoveryTableEnabled writes them to an
awsdms_txn_state table on the target, which matters because checkpoint
information held only by the task “is lost if the task is deleted”.
Bidirectional replication is not multi-master
DMS can replicate A to B and B to A, with LoopbackPreventionSettings stopping
each task re-applying the other's changes. AWS states the boundary plainly: it
“isn't intended as a full multi-master solution” and
“doesn't include conflict detection or resolution”. The intended use is
data that is operationally segregated — node A owns its rows and node B never
writes them.
Two constraints come with it. Loopback prevention “tracks only data manipulation
language (DML) statements”, so DDL will loop unless one task filters it out. And
such tasks “don't support committing changes in batches”:
BatchApplyEnabled must be false, which removes the main throughput
optimisation available to a busy task.
Why This Architecture Holds Up
Every limit above is documented, and not one of them raises an error. That is the property that makes them worth designing around rather than discovering.
A task running eight minutes behind reports the same healthy status as one running eight seconds behind. A task that omitted every view reports success. A task that missed a long-running transaction reports success. The feedback you get is that replication is working — which is true, and not the question you were asking.
The gap is between two readings of the same phrase. "Ongoing replication" is accurate: changes flow continuously. The plan needs something stronger — a bounded, known lag at a specific moment — and that is a property of your workload, measured, not a property of the service.
Key Architecture Decisions
| Decision | Take this | Because |
|---|---|---|
| Sizing the freeze window | Measure CDC latency under production-like load and size from the observed tail, not the average | There is no latency SLA. The only number that means anything is one you produced |
| Views, sequences, procedures | Handle outside CDC — Schema Conversion, or applied by hand and verified | A task that starts CDC migrates tables only, and reports success having done so |
| Choosing the start point | Decide before creating the task; prefer a native point or checkpoint over a timestamp | It cannot be changed afterwards, and a timestamp can map to several points in the log |
| Oracle sources | Set openTransactionWindow to a window wider than your longest transaction |
Otherwise transactions open at the start position are silently dropped |
| Recovering or fanning out | Enable TaskRecoveryTableEnabled so checkpoints persist on the target |
Checkpoint information is lost when the task is deleted |
| Two-way replication | Only where write ownership is genuinely segregated by node | No conflict detection or resolution exists; conflicts corrupt rather than fail |
| Verifying the result | Enable data validation, and on both tasks in a bidirectional pair | It is the only mechanism that compares source and target rather than reporting task health |
Closing Thought
DMS is a good service, and none of this is a criticism of it. The documentation states every limit here plainly, including the sentence most likely to change a migration plan: there are no SLAs for CDC latency.
The failure mode is not the tool. It is that "the target is kept in sync" gets written into a plan as an assumption, and the assumption is never converted into a number. Convert it early — measure the lag, on your data, under load — and the rest of the cutover design follows from something real.
Comments