<!-- Source: https://dev.dbmigratepro.com/docs/continuous-migration -->

# Continuous migration

A one-off migration copies your database once. A **continuous migration** keeps copying: it
syncs a target database on another server with your source, on a repeating cadence, until you
tell it to stop. When you are ready, you **promote** the target — and that is your cutover.

Two things it is for:

- **Moving providers with almost no downtime.** Sync until the target is minutes behind, then
  promote during a short window instead of a long maintenance outage.
- **A warm standby you control.** A second, current copy of your database on a different
  provider — the thing you want to already exist on the day you need it.

## What you need

Two connection URLs. That is the whole setup.

There is no agent to install, no extension to enable, and no replication user to create. We
read from your source the way any client would, and write to your target the same way. If you
can connect to both databases, you can run a continuous migration between them.

This is a deliberate design constraint rather than a limitation we have not got to yet — see
[What it does not do](#what-it-does-not-do) below.

## How it works

Each pass does two things:

1. **Ships what changed.** A change cursor (a monotonic column such as `updated_at`, or one you
   nominate) marks how far the last pass got, so a pass moves only new and updated rows rather
   than the whole table.
2. **Reconciles the key set, periodically.** A cursor cannot see a `DELETE` — a deleted row
   leaves nothing behind to notice. So on a repeating schedule we compare *primary keys* between
   source and target and remove what is gone. This reads keys, not rows, so it stays cheap even
   on a large table.

By default that reconciliation runs **once a day**, which is the balance we settled on: a row
that lingers a little on a standby is recoverable, a row that was never copied is not.

### Sync cadence

| Plan | A target re-syncs every |
|---|---|
| Pro | 15 minutes |
| Business | 5 minutes |

Cadence is the difference between the plans, because passes — not bytes — are what a continuous
migration costs to run. Both plans include an **unlimited number** of targets and meter the data
identically.

### Verifying updates the cursor missed

If a row is updated without its cursor column moving — an `UPDATE` that leaves `updated_at`
alone — a cursor-based pass will not see it. Turning on **verify hashes** makes each
reconciliation compare a per-row hash as well as the key, and re-ship rows whose contents differ.
It costs a hash per row scanned, so it is a setting rather than the default.

## Promoting

Promotion is the cutover: it stops syncing and hands you the target as a database in its own
right. Point your application at it.

A promoted target no longer syncs, and it is no longer counted as a continuous migration — so
promoting is also how you finish one.

## What it costs

Continuous migration is included with **any paid plan**, with no limit on the number of targets.

The **data is metered**, at $2.50/GB per month — the same rate as backups. "Unlimited"
describes the number of targets you can run, not the volume you can sync: a plan buys the
feature and the seats, and every GB kept in sync is billed. There is no allowance to run out of
and no cliff to fall off.

Promoting or deleting a target stops its meter.

## What it does not do

Being straight about the boundaries, because they are deliberate and they are not going to move:

- **Same-engine only.** PostgreSQL stays in sync with PostgreSQL, MySQL with MySQL. To move
  *between* engines, run a one-off [migration](/docs/supported-migrations) — that path converts
  as it copies, which is a different job from keeping two databases identical.
- **No change-data-capture streaming.** Reading a database's replication stream (logical
  decoding, binlog) needs privileged configuration on your source that a connection URL cannot
  grant — which would break the "paste a URL" premise the product is built on. It also carries a
  risk we are not willing to hand you: an inactive or lagging replication slot makes PostgreSQL
  retain write-ahead log files until the **source** runs out of disk. A backup tool that can take
  down the database it is protecting is not a backup tool.
- **Not a transactional replica.** The target is minutes behind, not milliseconds, and passes are
  not transactionally consistent snapshots of the source. For a cutover or a standby that is the
  right trade; for read-your-writes traffic it is not.

## Getting started

Create one from **Continuous migration → New**, or over the API:

```bash
curl -X POST https://dev.dbmigratepro.com/api/replicas \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"source_db_url":"postgresql://…@source/app",
       "target_db_url":"postgresql://…@target/app"}'
```

Leave `frequency` out and it uses the fastest cadence your plan allows. The first pass copies
everything; every pass after it moves only what changed.
